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

gis: Merged revisions 7574-7583,7585-7586,7590-7602,7614-7615,7619-7625,7629,7632-7636 via svnmerge from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7642 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2008-06-16 00:34:17 +00:00
parent 4ec80c4333
commit 842dae0ed5
68 changed files with 1563 additions and 441 deletions

View File

@ -57,6 +57,7 @@ answer newbie questions, and generally made Django that much better:
David Ascher <http://ascher.ca/> David Ascher <http://ascher.ca/>
Jökull Sólberg Auðunsson <jokullsolberg@gmail.com> Jökull Sólberg Auðunsson <jokullsolberg@gmail.com>
Arthur <avandorp@gmail.com> Arthur <avandorp@gmail.com>
av0000@mail.ru
David Avsajanishvili <avsd05@gmail.com> David Avsajanishvili <avsd05@gmail.com>
axiak@mit.edu axiak@mit.edu
Niran Babalola <niran@niran.org> Niran Babalola <niran@niran.org>
@ -78,6 +79,7 @@ answer newbie questions, and generally made Django that much better:
brut.alll@gmail.com brut.alll@gmail.com
btoll@bestweb.net btoll@bestweb.net
Jonathan Buchanan <jonathan.buchanan@gmail.com> Jonathan Buchanan <jonathan.buchanan@gmail.com>
Keith Bussell <kbussell@gmail.com>
Juan Manuel Caicedo <juan.manuel.caicedo@gmail.com> Juan Manuel Caicedo <juan.manuel.caicedo@gmail.com>
Trevor Caira <trevor@caira.com> Trevor Caira <trevor@caira.com>
Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com> Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com>
@ -142,6 +144,7 @@ answer newbie questions, and generally made Django that much better:
Bill Fenner <fenner@gmail.com> Bill Fenner <fenner@gmail.com>
Stefane Fermgier <sf@fermigier.com> Stefane Fermgier <sf@fermigier.com>
Afonso Fernández Nogueira <fonzzo.django@gmail.com> Afonso Fernández Nogueira <fonzzo.django@gmail.com>
J. Pablo Fernandez <pupeno@pupeno.com>
Matthew Flanagan <http://wadofstuff.blogspot.com> Matthew Flanagan <http://wadofstuff.blogspot.com>
Eric Floehr <eric@intellovations.com> Eric Floehr <eric@intellovations.com>
Vincent Foley <vfoleybourgon@yahoo.ca> Vincent Foley <vfoleybourgon@yahoo.ca>
@ -359,13 +362,14 @@ answer newbie questions, and generally made Django that much better:
Makoto Tsuyuki <mtsuyuki@gmail.com> Makoto Tsuyuki <mtsuyuki@gmail.com>
tt@gurgle.no tt@gurgle.no
David Tulig <david.tulig@gmail.com> David Tulig <david.tulig@gmail.com>
Amit Upadhyay Amit Upadhyay <http://www.amitu.com/blog/>
Geert Vanderkelen Geert Vanderkelen
I.S. van Oostveen <v.oostveen@idca.nl> I.S. van Oostveen <v.oostveen@idca.nl>
viestards.lists@gmail.com viestards.lists@gmail.com
George Vilches <gav@thataddress.com> George Vilches <gav@thataddress.com>
Vlado <vlado@labath.org> Vlado <vlado@labath.org>
Milton Waddams Milton Waddams
Chris Wagner <cw264701@ohio.edu>
wam-djangobug@wamber.net wam-djangobug@wamber.net
Wang Chun <wangchun@exoweb.net> Wang Chun <wangchun@exoweb.net>
Filip Wasilewski <filip.wasilewski@gmail.com> Filip Wasilewski <filip.wasilewski@gmail.com>

View File

@ -11,9 +11,10 @@ gettext_noop = lambda s: s
DEBUG = False DEBUG = False
TEMPLATE_DEBUG = False TEMPLATE_DEBUG = False
# True if BaseHandler.get_response() should propagate raw exceptions
# rather than catching them. This is useful under some testing siutations, # Whether the framework should propagate raw exceptions rather than catching
# and should never be used on a live site. # them. This is useful under some testing siutations and should never be used
# on a live site.
DEBUG_PROPAGATE_EXCEPTIONS = False DEBUG_PROPAGATE_EXCEPTIONS = False
# Whether to use the "Etag" header. This saves bandwidth but slows down performance. # Whether to use the "Etag" header. This saves bandwidth but slows down performance.
@ -289,7 +290,7 @@ SESSION_COOKIE_DOMAIN = None # A string like ".lawren
SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only). SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only).
SESSION_COOKIE_PATH = '/' # The path of the session cookie. SESSION_COOKIE_PATH = '/' # The path of the session cookie.
SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request. SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request.
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether sessions expire when a user closes his browser. SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether a user's session cookie expires when the Web browser is closed.
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data
SESSION_FILE_PATH = None # Directory to store session files if using the file session module. If None, the backend will use a sensible default. SESSION_FILE_PATH = None # Directory to store session files if using the file session module. If None, the backend will use a sensible default.

View File

@ -5,201 +5,200 @@ 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: 2008-05-26 10:38+0200\n" "POT-Creation-Date: 2008-06-13 16:14+0200\n"
"PO-Revision-Date: 2008-03-30 01:04+0100\n" "PO-Revision-Date: 2008-03-30 01:04+0100\n"
"Last-Translator: Django Spanish Group <django-i18n@googlegroups.com>\n" "Last-Translator: Django Spanish Translation Team <django-cat@googlegroups.com>\n"
"Language-Team: Spanish <django-i18n@googlegroups.com>\n" "Language-Team: Django Spanish Translation Team <django-cat@googlegroups.com>\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=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: conf/global_settings.py:43 #: conf/global_settings.py:44
msgid "Arabic" msgid "Arabic"
msgstr "Árabe" msgstr "Árabe"
#: conf/global_settings.py:44 #: conf/global_settings.py:45
msgid "Bengali" msgid "Bengali"
msgstr "Bengalí" msgstr "Bengalí"
#: conf/global_settings.py:45 #: conf/global_settings.py:46
msgid "Bulgarian" msgid "Bulgarian"
msgstr "Búlgaro" msgstr "Búlgaro"
#: conf/global_settings.py:46 #: conf/global_settings.py:47
msgid "Catalan" msgid "Catalan"
msgstr "Catalán" msgstr "Catalán"
#: conf/global_settings.py:47 #: conf/global_settings.py:48
msgid "Czech" msgid "Czech"
msgstr "Checo" msgstr "Checo"
#: conf/global_settings.py:48 #: conf/global_settings.py:49
msgid "Welsh" msgid "Welsh"
msgstr "Galés" msgstr "Galés"
#: conf/global_settings.py:49 #: conf/global_settings.py:50
msgid "Danish" msgid "Danish"
msgstr "Danés" msgstr "Danés"
#: conf/global_settings.py:50 #: conf/global_settings.py:51
msgid "German" msgid "German"
msgstr "Alemán" msgstr "Alemán"
#: conf/global_settings.py:51 #: conf/global_settings.py:52
msgid "Greek" msgid "Greek"
msgstr "Griego" msgstr "Griego"
#: conf/global_settings.py:52 #: conf/global_settings.py:53
msgid "English" msgid "English"
msgstr "Inglés" msgstr "Inglés"
#: conf/global_settings.py:53 #: conf/global_settings.py:54
msgid "Spanish" msgid "Spanish"
msgstr "Español" msgstr "Español"
#: conf/global_settings.py:54 #: conf/global_settings.py:55
msgid "Argentinean Spanish" msgid "Argentinean Spanish"
msgstr "Español Argentino" msgstr "Español Argentino"
#: conf/global_settings.py:55 #: conf/global_settings.py:56
msgid "Basque" msgid "Basque"
msgstr "Vasco" msgstr "Vasco"
#: conf/global_settings.py:56 #: conf/global_settings.py:57
msgid "Persian" msgid "Persian"
msgstr "Persa" msgstr "Persa"
#: conf/global_settings.py:57 #: conf/global_settings.py:58
msgid "Finnish" msgid "Finnish"
msgstr "Finés" msgstr "Finés"
#: conf/global_settings.py:58 #: conf/global_settings.py:59
msgid "French" msgid "French"
msgstr "Francés" msgstr "Francés"
#: conf/global_settings.py:59 #: conf/global_settings.py:60
msgid "Irish" msgid "Irish"
msgstr "Irlandés" msgstr "Irlandés"
#: conf/global_settings.py:60 #: conf/global_settings.py:61
msgid "Galician" msgid "Galician"
msgstr "Gallego" msgstr "Gallego"
#: conf/global_settings.py:61 #: conf/global_settings.py:62
msgid "Hungarian" msgid "Hungarian"
msgstr "Húngaro" msgstr "Húngaro"
#: conf/global_settings.py:62 #: conf/global_settings.py:63
msgid "Hebrew" msgid "Hebrew"
msgstr "Hebreo" msgstr "Hebreo"
#: conf/global_settings.py:63 #: conf/global_settings.py:64
msgid "Croatian" msgid "Croatian"
msgstr "Croata" msgstr "Croata"
#: conf/global_settings.py:64 #: conf/global_settings.py:65
msgid "Icelandic" msgid "Icelandic"
msgstr "Islandés" msgstr "Islandés"
#: conf/global_settings.py:65 #: conf/global_settings.py:66
msgid "Italian" msgid "Italian"
msgstr "Italiano" msgstr "Italiano"
#: conf/global_settings.py:66 #: conf/global_settings.py:67
msgid "Japanese" msgid "Japanese"
msgstr "Japonés" msgstr "Japonés"
#: conf/global_settings.py:67 #: conf/global_settings.py:68
msgid "Georgian" msgid "Georgian"
msgstr "Georgiano" msgstr "Georgiano"
#: conf/global_settings.py:68 #: conf/global_settings.py:69
msgid "Korean" msgid "Korean"
msgstr "Koreano" msgstr "Koreano"
#: conf/global_settings.py:69 #: conf/global_settings.py:70
msgid "Khmer" msgid "Khmer"
msgstr "Khmer" msgstr "Khmer"
#: conf/global_settings.py:70 #: conf/global_settings.py:71
msgid "Kannada" msgid "Kannada"
msgstr "Kannada" msgstr "Kannada"
#: conf/global_settings.py:71 #: conf/global_settings.py:72
msgid "Latvian" msgid "Latvian"
msgstr "Latvio" msgstr "Latvio"
#: conf/global_settings.py:72 #: conf/global_settings.py:73
msgid "Macedonian" msgid "Macedonian"
msgstr "Macedonio" msgstr "Macedonio"
#: conf/global_settings.py:73
msgid "Dutch"
msgstr "Alemán"
#: conf/global_settings.py:74 #: conf/global_settings.py:74
msgid "Dutch"
msgstr "Holandés"
#: conf/global_settings.py:75
msgid "Norwegian" msgid "Norwegian"
msgstr "Noruego" msgstr "Noruego"
#: conf/global_settings.py:75 #: conf/global_settings.py:76
msgid "Polish" msgid "Polish"
msgstr "Polaco" msgstr "Polaco"
#: conf/global_settings.py:76 #: conf/global_settings.py:77
msgid "Portugese" msgid "Portugese"
msgstr "Portugés" msgstr "Portugés"
#: conf/global_settings.py:77
#, fuzzy
msgid "Brazilian Portuguese"
msgstr "Portugés"
#: conf/global_settings.py:78 #: conf/global_settings.py:78
msgid "Brazilian Portuguese"
msgstr "Portugés Brasileño"
#: conf/global_settings.py:79
msgid "Romanian" msgid "Romanian"
msgstr "Rumano" msgstr "Rumano"
#: conf/global_settings.py:79 #: conf/global_settings.py:80
msgid "Russian" msgid "Russian"
msgstr "Ruso" msgstr "Ruso"
#: conf/global_settings.py:80 #: conf/global_settings.py:81
msgid "Slovak" msgid "Slovak"
msgstr "Eslovaco" msgstr "Eslovaco"
#: conf/global_settings.py:81 #: conf/global_settings.py:82
msgid "Slovenian" msgid "Slovenian"
msgstr "Esloveno" msgstr "Esloveno"
#: conf/global_settings.py:82 #: conf/global_settings.py:83
msgid "Serbian" msgid "Serbian"
msgstr "Serbio" msgstr "Serbio"
#: conf/global_settings.py:83 #: conf/global_settings.py:84
msgid "Swedish" msgid "Swedish"
msgstr "Sueco" msgstr "Sueco"
#: conf/global_settings.py:84 #: conf/global_settings.py:85
msgid "Tamil" msgid "Tamil"
msgstr "Tamil" msgstr "Tamil"
#: conf/global_settings.py:85 #: conf/global_settings.py:86
msgid "Telugu" msgid "Telugu"
msgstr "Telugu" msgstr "Telugu"
#: conf/global_settings.py:86 #: conf/global_settings.py:87
msgid "Turkish" msgid "Turkish"
msgstr "Turco" msgstr "Turco"
#: conf/global_settings.py:87 #: conf/global_settings.py:88
msgid "Ukrainian" msgid "Ukrainian"
msgstr "Ucraniano" msgstr "Ucraniano"
#: conf/global_settings.py:88 #: conf/global_settings.py:89
msgid "Simplified Chinese" msgid "Simplified Chinese"
msgstr "Chino simplificado" msgstr "Chino simplificado"
#: conf/global_settings.py:89 #: conf/global_settings.py:90
msgid "Traditional Chinese" msgid "Traditional Chinese"
msgstr "Chino tradicional" msgstr "Chino tradicional"
@ -329,7 +328,7 @@ msgstr ""
#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/admin/base.html:26
msgid "Welcome," msgid "Welcome,"
msgstr "Bienvenido," msgstr "Bienvenido/a,"
#: contrib/admin/templates/admin/base.html:28 #: contrib/admin/templates/admin/base.html:28
#: contrib/admin/templates/admin_doc/bookmarklets.html:3 #: contrib/admin/templates/admin_doc/bookmarklets.html:3
@ -357,7 +356,7 @@ msgstr "Administración de Django"
#: contrib/admin/templates/admin/change_form.html:14 #: contrib/admin/templates/admin/change_form.html:14
#: contrib/admin/templates/admin/index.html:28 #: contrib/admin/templates/admin/index.html:28
msgid "Add" msgid "Añadir"
msgstr "Agregar" msgstr "Agregar"
#: contrib/admin/templates/admin/change_form.html:20 #: contrib/admin/templates/admin/change_form.html:20
@ -387,7 +386,7 @@ msgstr "Orden:"
#: contrib/admin/templates/admin/change_list.html:11 #: contrib/admin/templates/admin/change_list.html:11
#, python-format #, python-format
msgid "Add %(name)s" msgid "Add %(name)s"
msgstr "Agregar %(name)s" msgstr "Añadir %(name)s"
#: contrib/admin/templates/admin/delete_confirmation.html:8 #: contrib/admin/templates/admin/delete_confirmation.html:8
#: contrib/admin/templates/admin/submit_line.html:3 #: contrib/admin/templates/admin/submit_line.html:3
@ -463,7 +462,7 @@ msgid ""
"database tables have been created, and make sure the database is readable by " "database tables have been created, and make sure the database is readable by "
"the appropriate user." "the appropriate user."
msgstr "" msgstr ""
"Algo va mal con la instalación de la base de datos. Asegúrate que las tablas " "Algo va mal con la instalación de la base de datos. Asegúrese que las tablas "
"necesarias han sido creadas, y que la base de datos puede ser leída por el " "necesarias han sido creadas, y que la base de datos puede ser leída por el "
"usuario apropiado." "usuario apropiado."
@ -476,12 +475,12 @@ msgstr "Usuario:"
#: contrib/admin/templates/admin/login.html:20 #: contrib/admin/templates/admin/login.html:20
#: contrib/comments/templates/comments/form.html:8 #: contrib/comments/templates/comments/form.html:8
msgid "Password:" msgid "Password:"
msgstr "Clave:" msgstr "Contraseña:"
#: contrib/admin/templates/admin/login.html:25 #: contrib/admin/templates/admin/login.html:25
#: contrib/admin/views/decorators.py:31 #: contrib/admin/views/decorators.py:31
msgid "Log in" msgid "Log in"
msgstr "Identificarse" msgstr "Iniciar sesión"
#: contrib/admin/templates/admin/object_history.html:17 #: contrib/admin/templates/admin/object_history.html:17
msgid "Date/time" msgid "Date/time"
@ -509,11 +508,11 @@ msgstr ""
#: contrib/admin/templates/admin/pagination.html:10 #: contrib/admin/templates/admin/pagination.html:10
msgid "Show all" msgid "Show all"
msgstr "Mostrarlo todo" msgstr "Mostrar todo"
#: contrib/admin/templates/admin/search_form.html:8 #: contrib/admin/templates/admin/search_form.html:8
msgid "Go" msgid "Go"
msgstr "Buscar" msgstr "Ir"
#: contrib/admin/templates/admin/search_form.html:10 #: contrib/admin/templates/admin/search_form.html:10
#, python-format #, python-format
@ -568,7 +567,7 @@ msgstr "Contraseña (de nuevo)"
#: contrib/admin/templates/admin/auth/user/add_form.html:24 #: contrib/admin/templates/admin/auth/user/add_form.html:24
#: contrib/admin/templates/admin/auth/user/change_password.html:39 #: contrib/admin/templates/admin/auth/user/change_password.html:39
msgid "Enter the same password as above, for verification." msgid "Enter the same password as above, for verification."
msgstr "Introduzca la misma contraseña que arriba, para verificación" msgstr "Introduzca la misma contraseña que arriba, para verificación."
#: contrib/admin/templates/admin/auth/user/change_password.html:27 #: contrib/admin/templates/admin/auth/user/change_password.html:27
#, python-format #, python-format
@ -599,22 +598,22 @@ msgstr ""
"<p class=\"help\">Para instalar bookmarklets, arrastre el enlace a su barra\n" "<p class=\"help\">Para instalar bookmarklets, arrastre el enlace a su barra\n"
"de favoritos, o pulse con el botón derecho el enlace y añádalo a sus " "de favoritos, o pulse con el botón derecho el enlace y añádalo a sus "
"favoritos.\n" "favoritos.\n"
"Ahora puede escoger el bookmarklet desde cualquier página en el sitio.\n" "Ahora puede escoger el bookmarklet desde cualquier página del sitio.\n"
"Observer que algunos de estos bookmarklets precisan que esté viendo\n" "Observe que algunos de estos bookmarklets precisan que esté viendo\n"
"el sitio desde un computador señalado como \"interno\" (hable\n" "el sitio desde un ordenador señalado como \"interno\" (hable\n"
"con su administrador de sistemas si no está seguro de si el suyo lo es).</" "con su administrador de sistemas si no está seguro si el suyo lo es).</"
"p>\n" "p>\n"
#: contrib/admin/templates/admin_doc/bookmarklets.html:18 #: contrib/admin/templates/admin_doc/bookmarklets.html:18
msgid "Documentation for this page" msgid "Documentation for this page"
msgstr "Documentación de esta página" msgstr "Documentación para esta página"
#: contrib/admin/templates/admin_doc/bookmarklets.html:19 #: contrib/admin/templates/admin_doc/bookmarklets.html:19
msgid "" msgid ""
"Jumps you from any page to the documentation for the view that generates " "Jumps you from any page to the documentation for the view that generates "
"that page." "that page."
msgstr "" msgstr ""
"Le lleva desde cualquier página a la documentación de la vista que la genera." "Lleva desde cualquier página a la documentación de la vista que la genera."
#: contrib/admin/templates/admin_doc/bookmarklets.html:21 #: contrib/admin/templates/admin_doc/bookmarklets.html:21
msgid "Show object ID" msgid "Show object ID"
@ -625,8 +624,8 @@ msgid ""
"Shows the content-type and unique ID for pages that represent a single " "Shows the content-type and unique ID for pages that represent a single "
"object." "object."
msgstr "" msgstr ""
"Muestra el tipo de contenido e ID unívoco de las páginas que representan un " "Muestra el tipo de contenido e ID único de las páginas que representan un "
"único objeto." "simple objeto."
#: contrib/admin/templates/admin_doc/bookmarklets.html:24 #: contrib/admin/templates/admin_doc/bookmarklets.html:24
msgid "Edit this object (current window)" msgid "Edit this object (current window)"
@ -635,7 +634,7 @@ msgstr "Editar este objeto (ventana actual)"
#: contrib/admin/templates/admin_doc/bookmarklets.html:25 #: contrib/admin/templates/admin_doc/bookmarklets.html:25
msgid "Jumps to the admin page for pages that represent a single object." msgid "Jumps to the admin page for pages that represent a single object."
msgstr "" msgstr ""
"Le lleva a la página de administración de páginas que representan un único " "Lleva a la página de administración de páginas que representan un único "
"objeto." "objeto."
#: contrib/admin/templates/admin_doc/bookmarklets.html:27 #: contrib/admin/templates/admin_doc/bookmarklets.html:27
@ -649,85 +648,85 @@ msgstr ""
#: contrib/admin/templates/registration/logged_out.html:8 #: contrib/admin/templates/registration/logged_out.html:8
msgid "Thanks for spending some quality time with the Web site today." msgid "Thanks for spending some quality time with the Web site today."
msgstr "Gracias por el tiempo que ha dedicado al sitio web hoy." msgstr "Gracias por el tiempo que ha dedicado hoy al sitio web."
#: contrib/admin/templates/registration/logged_out.html:10 #: contrib/admin/templates/registration/logged_out.html:10
msgid "Log in again" msgid "Log in again"
msgstr "Identificarse de nuevo" msgstr "Iniciar sesión de nuevo"
#: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_done.html:3
#: contrib/admin/templates/registration/password_change_form.html:3 #: contrib/admin/templates/registration/password_change_form.html:3
#: contrib/admin/templates/registration/password_change_form.html:5 #: contrib/admin/templates/registration/password_change_form.html:5
#: contrib/admin/templates/registration/password_change_form.html:9 #: contrib/admin/templates/registration/password_change_form.html:9
msgid "Password change" msgid "Password change"
msgstr "Cambio de clave" msgstr "Cambio de contraseña"
#: contrib/admin/templates/registration/password_change_done.html:5 #: contrib/admin/templates/registration/password_change_done.html:5
#: contrib/admin/templates/registration/password_change_done.html:9 #: contrib/admin/templates/registration/password_change_done.html:9
msgid "Password change successful" msgid "Password change successful"
msgstr "Cambio de clave exitoso" msgstr "Cambio de contraseña exitoso"
#: contrib/admin/templates/registration/password_change_done.html:11 #: contrib/admin/templates/registration/password_change_done.html:11
msgid "Your password was changed." msgid "Your password was changed."
msgstr "Su clave ha sido cambiada." msgstr "Su contraseña ha sido cambiada."
#: contrib/admin/templates/registration/password_change_form.html:11 #: contrib/admin/templates/registration/password_change_form.html:11
msgid "" msgid ""
"Please enter your old password, for security's sake, and then enter your new " "Please enter your old password, for security's sake, and then enter your new "
"password twice so we can verify you typed it in correctly." "password twice so we can verify you typed it in correctly."
msgstr "" msgstr ""
"Por favor, introduzca su clave antigua, por seguridad, y después introduzca " "Por favor, introduzca su contraseña antigua, por seguridad, y después introduzca "
"la nueva clave dos veces para verificar que la ha escrito correctamente." "la nueva contraseña dos veces para verificar que la ha escrito correctamente."
#: contrib/admin/templates/registration/password_change_form.html:16 #: contrib/admin/templates/registration/password_change_form.html:16
msgid "Old password:" msgid "Old password:"
msgstr "Clave antigua:" msgstr "Contraseña antigua:"
#: contrib/admin/templates/registration/password_change_form.html:18 #: contrib/admin/templates/registration/password_change_form.html:18
msgid "New password:" msgid "New password:"
msgstr "Clave nueva:" msgstr "Contraseña nueva:"
#: contrib/admin/templates/registration/password_change_form.html:20 #: contrib/admin/templates/registration/password_change_form.html:20
msgid "Confirm password:" msgid "Confirm password:"
msgstr "Confirme clave:" msgstr "Confirme contraseña:"
#: contrib/admin/templates/registration/password_change_form.html:22 #: contrib/admin/templates/registration/password_change_form.html:22
msgid "Change my password" msgid "Change my password"
msgstr "Cambiar mi clave" msgstr "Cambiar mi contraseña"
#: contrib/admin/templates/registration/password_reset_done.html:4 #: contrib/admin/templates/registration/password_reset_done.html:4
#: contrib/admin/templates/registration/password_reset_form.html:4 #: contrib/admin/templates/registration/password_reset_form.html:4
#: contrib/admin/templates/registration/password_reset_form.html:6 #: contrib/admin/templates/registration/password_reset_form.html:6
#: contrib/admin/templates/registration/password_reset_form.html:10 #: contrib/admin/templates/registration/password_reset_form.html:10
msgid "Password reset" msgid "Password reset"
msgstr "Recuperar clave" msgstr "Restablecer contraseña"
#: contrib/admin/templates/registration/password_reset_done.html:6 #: contrib/admin/templates/registration/password_reset_done.html:6
#: contrib/admin/templates/registration/password_reset_done.html:10 #: contrib/admin/templates/registration/password_reset_done.html:10
msgid "Password reset successful" msgid "Password reset successful"
msgstr "Recuperación de clave exitosa" msgstr "Restablecimiento de contraseña exitoso"
#: contrib/admin/templates/registration/password_reset_done.html:12 #: contrib/admin/templates/registration/password_reset_done.html:12
msgid "" msgid ""
"We've e-mailed a new password to the e-mail address you submitted. You " "We've e-mailed a new password to the e-mail address you submitted. You "
"should be receiving it shortly." "should be receiving it shortly."
msgstr "" msgstr ""
"Le hemos enviado una clave nueva a la dirección que ha suministrado. Debería " "Le hemos enviado una contraseña nueva a la dirección que ha suministrado. Debería "
"recibirla en breve." "recibirla en breve."
#: contrib/admin/templates/registration/password_reset_email.html:2 #: contrib/admin/templates/registration/password_reset_email.html:2
msgid "You're receiving this e-mail because you requested a password reset" msgid "You're receiving this e-mail because you requested a password reset"
msgstr "Está recibiendo este mensaje debido a que solicitó recuperar la clave" msgstr "Está recibiendo este mensaje debido a que solicitó restablecer la contraseña"
#: contrib/admin/templates/registration/password_reset_email.html:3 #: contrib/admin/templates/registration/password_reset_email.html:3
#, python-format #, python-format
msgid "for your user account at %(site_name)s" msgid "for your user account at %(site_name)s"
msgstr "de su cuenta de usuario en %(site_name)s." msgstr "para su cuenta de usuario en %(site_name)s."
#: contrib/admin/templates/registration/password_reset_email.html:5 #: contrib/admin/templates/registration/password_reset_email.html:5
#, python-format #, python-format
msgid "Your new password is: %(new_password)s" msgid "Your new password is: %(new_password)s"
msgstr "Su nueva clave es: %(new_password)s" msgstr "Su nueva contraseña es: %(new_password)s"
#: contrib/admin/templates/registration/password_reset_email.html:7 #: contrib/admin/templates/registration/password_reset_email.html:7
msgid "Feel free to change this password by going to this page:" msgid "Feel free to change this password by going to this page:"
@ -751,7 +750,7 @@ msgid ""
"Forgotten your password? Enter your e-mail address below, and we'll reset " "Forgotten your password? Enter your e-mail address below, and we'll reset "
"your password and e-mail the new one to you." "your password and e-mail the new one to you."
msgstr "" msgstr ""
"¿Ha olvidado su clave? Introduzca su dirección de correo electrónico, y " "¿Ha olvidado su contraseña? Introduzca su dirección de correo electrónico, y "
"crearemos una nueva que le enviaremos por correo." "crearemos una nueva que le enviaremos por correo."
#: contrib/admin/templates/registration/password_reset_form.html:16 #: contrib/admin/templates/registration/password_reset_form.html:16
@ -760,7 +759,7 @@ msgstr "Dirección de correo electrónico:"
#: contrib/admin/templates/registration/password_reset_form.html:16 #: contrib/admin/templates/registration/password_reset_form.html:16
msgid "Reset my password" msgid "Reset my password"
msgstr "Recuperar mi clave" msgstr "Restablecer mi contraseña"
#: contrib/admin/templates/widget/date_time.html:3 #: contrib/admin/templates/widget/date_time.html:3
msgid "Date:" msgid "Date:"
@ -798,7 +797,7 @@ msgstr "Añadir usuario"
#: contrib/admin/views/auth.py:58 #: contrib/admin/views/auth.py:58
msgid "Password changed successfully." msgid "Password changed successfully."
msgstr "La clave se ha cambiado exitosamente." msgstr "La contraseña se ha cambiado con éxito."
#: contrib/admin/views/auth.py:65 #: contrib/admin/views/auth.py:65
#, python-format #, python-format
@ -810,7 +809,7 @@ msgid ""
"Please enter a correct username and password. Note that both fields are case-" "Please enter a correct username and password. Note that both fields are case-"
"sensitive." "sensitive."
msgstr "" msgstr ""
"Por favor, introduzca un correcto nombre de usuario y contraseña. Note que " "Por favor, introduzca un nombre de usuario correcto y una contraseña. Note que "
"ambos campos son sensibles a mayúsculas/minúsculas." "ambos campos son sensibles a mayúsculas/minúsculas."
#: contrib/admin/views/decorators.py:69 #: contrib/admin/views/decorators.py:69
@ -818,7 +817,7 @@ msgid ""
"Please log in again, because your session has expired. Don't worry: Your " "Please log in again, because your session has expired. Don't worry: Your "
"submission has been saved." "submission has been saved."
msgstr "" msgstr ""
"Por favor, identifíquese de nuevo, porque su sesión ha caducado. No se " "Por favor, inicie sesión de nuevo, ya que su sesión ha caducado. No se "
"preocupe: se ha guardado su envío." "preocupe: se ha guardado su envío."
#: contrib/admin/views/decorators.py:76 #: contrib/admin/views/decorators.py:76
@ -827,7 +826,7 @@ msgid ""
"cookies, reload this page, and try again." "cookies, reload this page, and try again."
msgstr "" msgstr ""
"Parece que su navegador no está configurado para aceptar cookies. Actívelas " "Parece que su navegador no está configurado para aceptar cookies. Actívelas "
"por favor, recargue esta página, e inténtelo de nuevo." ", recargue esta página, e inténtelo de nuevo."
#: contrib/admin/views/decorators.py:89 #: contrib/admin/views/decorators.py:89
#, python-format #, python-format
@ -937,7 +936,7 @@ msgstr "Ruta de fichero"
#: contrib/admin/views/doc.py:303 #: contrib/admin/views/doc.py:303
msgid "Floating point number" msgid "Floating point number"
msgstr "Número decimal" msgstr "Número en coma flotante"
#: contrib/admin/views/doc.py:307 contrib/comments/models.py:89 #: contrib/admin/views/doc.py:307 contrib/comments/models.py:89
msgid "IP address" msgid "IP address"
@ -987,17 +986,17 @@ msgstr "Sitio administrativo"
#: contrib/admin/views/main.py:280 contrib/admin/views/main.py:365 #: contrib/admin/views/main.py:280 contrib/admin/views/main.py:365
#, python-format #, python-format
msgid "You may add another %s below." msgid "You may add another %s below."
msgstr "Puede agregar otro %s abajo." msgstr "Puede añadir otro %s abajo."
#: contrib/admin/views/main.py:298 #: contrib/admin/views/main.py:298
#, python-format #, python-format
msgid "Add %s" msgid "Add %s"
msgstr "Agregar %s" msgstr "Añadir %s"
#: contrib/admin/views/main.py:344 #: contrib/admin/views/main.py:344
#, python-format #, python-format
msgid "Added %s." msgid "Added %s."
msgstr "Agregado %s." msgstr "Añadido %s."
#: contrib/admin/views/main.py:344 contrib/admin/views/main.py:346 #: contrib/admin/views/main.py:344 contrib/admin/views/main.py:346
#: contrib/admin/views/main.py:348 core/validators.py:283 #: contrib/admin/views/main.py:348 core/validators.py:283
@ -1029,7 +1028,7 @@ msgstr "Se modificó con éxito el %(name)s \"%(obj)s\"."
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 ""
"Se agregó con éxito el %(name)s \"%(obj)s. Puede editarlo de nuevo abajo." "Se añadió con éxito el %(name)s \"%(obj)s. Puede editarlo de nuevo abajo."
#: contrib/admin/views/main.py:400 #: contrib/admin/views/main.py:400
#, python-format #, python-format
@ -1087,7 +1086,7 @@ msgid ""
"Your Web browser doesn't appear to have cookies enabled. Cookies are " "Your Web browser doesn't appear to have cookies enabled. Cookies are "
"required for logging in." "required for logging in."
msgstr "" msgstr ""
"Tu navegador de internet parece no tener las cookies habilitadas. Las " "Su navegador no parece tener las cookies habilitadas. Las "
"cookies se necesitan para poder ingresar." "cookies se necesitan para poder ingresar."
#: contrib/auth/forms.py:62 #: contrib/auth/forms.py:62
@ -1105,17 +1104,17 @@ msgstr ""
#: contrib/auth/forms.py:107 #: contrib/auth/forms.py:107
#, python-format #, python-format
msgid "Password reset on %s" msgid "Password reset on %s"
msgstr "Clave restablecida en %s" msgstr "Contraseña restablecida en %s"
#: contrib/auth/forms.py:117 #: contrib/auth/forms.py:117
msgid "The two 'new password' fields didn't match." msgid "The two 'new password' fields didn't match."
msgstr "" msgstr ""
"Las contraseñas introducidas en los campos 'nueva contraseña' no coinciden." "Los dos campos 'nueva contraseña' no coinciden."
#: contrib/auth/forms.py:124 #: contrib/auth/forms.py:124
msgid "Your old password was entered incorrectly. Please enter it again." msgid "Your old password was entered incorrectly. Please enter it again."
msgstr "" msgstr ""
"Tu contraseña antigua es incorrecta. Por favor, vuelve a introducirla " "Tu contraseña antigua es incorrecta. Por favor, vuelva a introducirla "
"correctamente." "correctamente."
#: contrib/auth/models.py:73 contrib/auth/models.py:93 #: contrib/auth/models.py:73 contrib/auth/models.py:93
@ -1138,15 +1137,15 @@ msgstr "permisos"
msgid "group" msgid "group"
msgstr "grupo" msgstr "grupo"
#: contrib/auth/models.py:98 contrib/auth/models.py:141 #: contrib/auth/models.py:98 contrib/auth/models.py:148
msgid "groups" msgid "groups"
msgstr "grupos" msgstr "grupos"
#: contrib/auth/models.py:131 #: contrib/auth/models.py:138
msgid "username" msgid "username"
msgstr "nombre de usuario" msgstr "nombre de usuario"
#: contrib/auth/models.py:131 #: contrib/auth/models.py:138
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)."
@ -1154,23 +1153,23 @@ msgstr ""
"Requerido. 30 caracteres o menos. Sólo caracteres alfanuméricos (letras, " "Requerido. 30 caracteres o menos. Sólo caracteres alfanuméricos (letras, "
"dígitos y guiones bajos)." "dígitos y guiones bajos)."
#: contrib/auth/models.py:132 #: contrib/auth/models.py:139
msgid "first name" msgid "first name"
msgstr "nombre" msgstr "nombre propio"
#: contrib/auth/models.py:133 #: contrib/auth/models.py:140
msgid "last name" msgid "last name"
msgstr "apellidos" msgstr "apellidos"
#: contrib/auth/models.py:134 #: contrib/auth/models.py:141
msgid "e-mail address" msgid "e-mail address"
msgstr "dirección de correo" msgstr "dirección de correo electrónico"
#: contrib/auth/models.py:135 #: contrib/auth/models.py:142
msgid "password" msgid "password"
msgstr "clave" msgstr "contraseña"
#: contrib/auth/models.py:135 #: contrib/auth/models.py:142
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>."
@ -1178,32 +1177,31 @@ msgstr ""
"Use'[algo]$[sal]$[hash hexadecimal]' o use <a href=\"password/\">el " "Use'[algo]$[sal]$[hash hexadecimal]' o use <a href=\"password/\">el "
"formulario para cambiar la contraseña</a>." "formulario para cambiar la contraseña</a>."
#: contrib/auth/models.py:136 #: contrib/auth/models.py:143
msgid "staff status" msgid "staff status"
msgstr "es staff" msgstr "es staff"
#: contrib/auth/models.py:136 #: contrib/auth/models.py:143
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 el usuario puede entrar en este sitio de administración." msgstr "Indica si el usuario puede entrar en este sitio de administración."
#: contrib/auth/models.py:137 #: contrib/auth/models.py:144
msgid "active" msgid "active"
msgstr "activo" msgstr "activo"
#: contrib/auth/models.py:137 #: contrib/auth/models.py:144
#, fuzzy
msgid "" msgid ""
"Designates whether this user should be treated as active. Unselect this " "Designates whether this user should be treated as active. Unselect this "
"instead of deleting accounts." "instead of deleting accounts."
msgstr "" msgstr ""
"Indica si el usuario puede entrar en este sitio de administración. Desmarque " "Indica si el usuario puede ser tratado como activo. Desmarque "
"esto en lugar de borrar la cuenta." "esta opción en lugar de borrar la cuenta."
#: contrib/auth/models.py:138 #: contrib/auth/models.py:145
msgid "superuser status" msgid "superuser status"
msgstr "es superusuario" msgstr "es superusuario"
#: contrib/auth/models.py:138 #: contrib/auth/models.py:145
msgid "" msgid ""
"Designates that this user has all permissions without explicitly assigning " "Designates that this user has all permissions without explicitly assigning "
"them." "them."
@ -1211,15 +1209,15 @@ msgstr ""
"Indica que este usuario tiene todos los permisos sin asignárselos " "Indica que este usuario tiene todos los permisos sin asignárselos "
"explícitamente." "explícitamente."
#: contrib/auth/models.py:139 #: contrib/auth/models.py:146
msgid "last login" msgid "last login"
msgstr "Último registro" msgstr "Último inicio de sesión"
#: contrib/auth/models.py:140 #: contrib/auth/models.py:147
msgid "date joined" msgid "date joined"
msgstr "fecha de creación" msgstr "fecha de alta"
#: contrib/auth/models.py:142 #: contrib/auth/models.py:149
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."
@ -1227,35 +1225,35 @@ msgstr ""
"Además de los permisos asignados manualmente, este usuario también tendrá " "Además de los permisos asignados manualmente, este usuario también tendrá "
"todos los permisos de los grupos en los que esté." "todos los permisos de los grupos en los que esté."
#: contrib/auth/models.py:143 #: contrib/auth/models.py:150
msgid "user permissions" msgid "user permissions"
msgstr "permisos" msgstr "permisos de usuario"
#: contrib/auth/models.py:147 #: contrib/auth/models.py:154
msgid "user" msgid "user"
msgstr "usuario" msgstr "usuario"
#: contrib/auth/models.py:148 #: contrib/auth/models.py:155
msgid "users" msgid "users"
msgstr "usuarios" msgstr "usuarios"
#: contrib/auth/models.py:154 #: contrib/auth/models.py:161
msgid "Personal info" msgid "Personal info"
msgstr "Información personal" msgstr "Información personal"
#: contrib/auth/models.py:155 #: contrib/auth/models.py:162
msgid "Permissions" msgid "Permissions"
msgstr "Permisos" msgstr "Permisos"
#: contrib/auth/models.py:156 #: contrib/auth/models.py:163
msgid "Important dates" msgid "Important dates"
msgstr "Fechas importantes" msgstr "Fechas importantes"
#: contrib/auth/models.py:157 #: contrib/auth/models.py:164
msgid "Groups" msgid "Groups"
msgstr "Grupos" msgstr "Grupos"
#: contrib/auth/models.py:316 #: contrib/auth/models.py:323
msgid "message" msgid "message"
msgstr "mensaje" msgstr "mensaje"
@ -1278,39 +1276,39 @@ msgstr "comentario"
#: contrib/comments/models.py:74 #: contrib/comments/models.py:74
msgid "rating #1" msgid "rating #1"
msgstr "calificación 1" msgstr "puntuación 1"
#: contrib/comments/models.py:75 #: contrib/comments/models.py:75
msgid "rating #2" msgid "rating #2"
msgstr "calificación 2" msgstr "puntuación 2"
#: contrib/comments/models.py:76 #: contrib/comments/models.py:76
msgid "rating #3" msgid "rating #3"
msgstr "calificación 3" msgstr "puntuación 3"
#: contrib/comments/models.py:77 #: contrib/comments/models.py:77
msgid "rating #4" msgid "rating #4"
msgstr "calificación 4" msgstr "puntuación 4"
#: contrib/comments/models.py:78 #: contrib/comments/models.py:78
msgid "rating #5" msgid "rating #5"
msgstr "calificación 5" msgstr "puntuación 5"
#: contrib/comments/models.py:79 #: contrib/comments/models.py:79
msgid "rating #6" msgid "rating #6"
msgstr "calificación 6" msgstr "puntuación 6"
#: contrib/comments/models.py:80 #: contrib/comments/models.py:80
msgid "rating #7" msgid "rating #7"
msgstr "calificación 7" msgstr "puntuación 7"
#: contrib/comments/models.py:81 #: contrib/comments/models.py:81
msgid "rating #8" msgid "rating #8"
msgstr "calificación 8" msgstr "puntuación 8"
#: contrib/comments/models.py:86 #: contrib/comments/models.py:86
msgid "is valid rating" msgid "is valid rating"
msgstr "es calificación válida" msgstr "puntuación válida"
#: contrib/comments/models.py:87 contrib/comments/models.py:179 #: contrib/comments/models.py:87 contrib/comments/models.py:179
msgid "date/time submitted" msgid "date/time submitted"
@ -1329,8 +1327,8 @@ msgid ""
"Check this box if the comment is inappropriate. A \"This comment has been " "Check this box if the comment is inappropriate. A \"This comment has been "
"removed\" message will be displayed instead." "removed\" message will be displayed instead."
msgstr "" msgstr ""
"Marque esta caja si el comentario es inapropiado. En su lugar se mostrará " "Marque esta opción si el comentario es inapropiado. En su lugar se mostrará "
"\"Este comentario ha sido eliminado\"." "el mensaje \"Este comentario ha sido eliminado\"."
#: contrib/comments/models.py:96 #: contrib/comments/models.py:96
msgid "comments" msgid "comments"
@ -1426,15 +1424,15 @@ msgstr "Marca de %r"
#: contrib/comments/models.py:300 #: contrib/comments/models.py:300
msgid "deletion date" msgid "deletion date"
msgstr "fecha de eliminación" msgstr "fecha de borrado"
#: contrib/comments/models.py:303 #: contrib/comments/models.py:303
msgid "moderator deletion" msgid "moderator deletion"
msgstr "eliminación de moderador" msgstr "borrado del moderador"
#: contrib/comments/models.py:304 #: contrib/comments/models.py:304
msgid "moderator deletions" msgid "moderator deletions"
msgstr "eliminaciones de moderador" msgstr "eliminaciones del moderador"
#: contrib/comments/models.py:308 #: contrib/comments/models.py:308
#, python-format #, python-format
@ -1447,7 +1445,7 @@ msgstr "¿Has olvidado tu contraseña?"
#: contrib/comments/templates/comments/form.html:12 #: contrib/comments/templates/comments/form.html:12
msgid "Ratings" msgid "Ratings"
msgstr "Calificaciones" msgstr "Puntuaciones"
#: contrib/comments/templates/comments/form.html:12 #: contrib/comments/templates/comments/form.html:12
#: contrib/comments/templates/comments/form.html:23 #: contrib/comments/templates/comments/form.html:23
@ -1461,7 +1459,7 @@ msgstr "Opcional"
#: contrib/comments/templates/comments/form.html:23 #: contrib/comments/templates/comments/form.html:23
msgid "Post a photo" msgid "Post a photo"
msgstr "Postea una fotografía" msgstr "Publica una fotografía"
#: contrib/comments/templates/comments/form.html:28 #: contrib/comments/templates/comments/form.html:28
#: contrib/comments/templates/comments/freeform.html:5 #: contrib/comments/templates/comments/freeform.html:5
@ -1519,7 +1517,7 @@ msgstr ""
#: contrib/comments/views/comments.py:190 #: contrib/comments/views/comments.py:190
#: contrib/comments/views/comments.py:283 #: contrib/comments/views/comments.py:283
msgid "Only POSTs are allowed" msgid "Only POSTs are allowed"
msgstr "Sólo se admite POST" msgstr "Sólo se admiten POSTs"
#: contrib/comments/views/comments.py:194 #: contrib/comments/views/comments.py:194
#: contrib/comments/views/comments.py:287 #: contrib/comments/views/comments.py:287
@ -1530,7 +1528,7 @@ msgstr "No se proporcionó uno o más de los siguientes campos requeridos"
#: contrib/comments/views/comments.py:289 #: contrib/comments/views/comments.py:289
msgid "Somebody tampered with the comment form (security violation)" msgid "Somebody tampered with the comment form (security violation)"
msgstr "" msgstr ""
"Alguien está jugando con el formulario de comentarios (violación de " "Alguien realizó manipulaciones con el formulario de comentarios (violación de "
"seguridad)" "seguridad)"
#: contrib/comments/views/comments.py:208 #: contrib/comments/views/comments.py:208
@ -1557,11 +1555,11 @@ msgstr "ID de comentario no válido"
#: contrib/comments/views/karma.py:27 #: contrib/comments/views/karma.py:27
msgid "No voting for yourself" msgid "No voting for yourself"
msgstr "No puedes votarte mismo" msgstr "No puedes votarte a ti mismo"
#: contrib/contenttypes/models.py:67 #: contrib/contenttypes/models.py:67
msgid "python model class name" msgid "python model class name"
msgstr "nombre de módulo python" msgstr "nombre de la clase modelo de python"
#: contrib/contenttypes/models.py:71 #: contrib/contenttypes/models.py:71
msgid "content type" msgid "content type"
@ -1588,7 +1586,7 @@ msgstr "contenido"
#: contrib/flatpages/models.py:12 #: contrib/flatpages/models.py:12
msgid "enable comments" msgid "enable comments"
msgstr "admitir comentarios" msgstr "habilitar comentarios"
#: contrib/flatpages/models.py:13 #: contrib/flatpages/models.py:13
msgid "template name" msgid "template name"
@ -1624,19 +1622,19 @@ msgstr "Opciones avanzadas"
#: contrib/humanize/templatetags/humanize.py:19 #: contrib/humanize/templatetags/humanize.py:19
msgid "th" msgid "th"
msgstr "th" msgstr "º"
#: contrib/humanize/templatetags/humanize.py:19 #: contrib/humanize/templatetags/humanize.py:19
msgid "st" msgid "st"
msgstr "st" msgstr "º"
#: contrib/humanize/templatetags/humanize.py:19 #: contrib/humanize/templatetags/humanize.py:19
msgid "nd" msgid "nd"
msgstr "nd" msgstr "º"
#: contrib/humanize/templatetags/humanize.py:19 #: contrib/humanize/templatetags/humanize.py:19
msgid "rd" msgid "rd"
msgstr "rd" msgstr "º"
#: contrib/humanize/templatetags/humanize.py:51 #: contrib/humanize/templatetags/humanize.py:51
#, python-format #, python-format
@ -1649,15 +1647,15 @@ msgstr[1] "%(value).1f millión"
#, 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 billión" msgstr[0] "%(value).1f billón"
msgstr[1] "%(value).1f billión" msgstr[1] "%(value).1f billón"
#: contrib/humanize/templatetags/humanize.py:57 #: contrib/humanize/templatetags/humanize.py:57
#, 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 trillión" msgstr[0] "%(value).1f trillón"
msgstr[1] "%(value).1f trillión" msgstr[1] "%(value).1f trillón"
#: contrib/humanize/templatetags/humanize.py:73 #: contrib/humanize/templatetags/humanize.py:73
msgid "one" msgid "one"
@ -3507,7 +3505,7 @@ msgid ""
"This should be an absolute path, excluding the domain name. Example: '/" "This should be an absolute path, excluding the domain name. Example: '/"
"events/search/'." "events/search/'."
msgstr "" msgstr ""
"Esta ruta debería ser absoluta, excluyendo el nombre de dominio. Ejeplo: '/" "Esta ruta debería ser absoluta, excluyendo el nombre de dominio. Ejemplo: '/"
"events/search/'." "events/search/'."
#: contrib/redirects/models.py:9 #: contrib/redirects/models.py:9
@ -3575,7 +3573,7 @@ msgid ""
"This value must contain only letters, numbers, underscores, dashes or " "This value must contain only letters, numbers, underscores, dashes or "
"slashes." "slashes."
msgstr "" msgstr ""
"Este valor debe contener letras, números, guiones bajos o barras solamente." "Este valor debe contener sólo letras, números, guiones bajos y barras."
#: core/validators.py:80 #: core/validators.py:80
msgid "This value must contain only letters, numbers, underscores or hyphens." msgid "This value must contain only letters, numbers, underscores or hyphens."
@ -3830,7 +3828,7 @@ msgstr "Este campo no es válido."
#: core/validators.py:536 #: core/validators.py:536
#, python-format #, python-format
msgid "Could not retrieve anything from %s." msgid "Could not retrieve anything from %s."
msgstr "No pude obtener nada de %s." msgstr "No se pudo obtener nada de %s."
#: core/validators.py:539 #: core/validators.py:539
#, python-format #, python-format
@ -3932,25 +3930,25 @@ msgstr "Introduzca un nombre de fichero válido"
#: db/models/fields/__init__.py:981 #: db/models/fields/__init__.py:981
msgid "This value must be either None, True or False." msgid "This value must be either None, True or False."
msgstr "Este valor debe ser Verdadero o Falso." msgstr "Este valor debe ser Verdadero, Falso o Ninguno."
#: db/models/fields/related.py:94 #: db/models/fields/related.py:94
#, python-format #, python-format
msgid "Please enter a valid %s." msgid "Please enter a valid %s."
msgstr "Por favor, introduzca un %s válido." msgstr "Por favor, introduzca un %s válido."
#: db/models/fields/related.py:721 #: db/models/fields/related.py:746
msgid "Separate multiple IDs with commas." msgid "Separate multiple IDs with commas."
msgstr "Separe múltiples IDs con comas." msgstr "Separe múltiples IDs con comas."
#: db/models/fields/related.py:723 #: db/models/fields/related.py:748
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 "" msgstr ""
"Mantenga presionado \"Control\", o \"Command\" en un Mac, para seleccionar " "Mantenga presionado \"Control\", o \"Command\" en un Mac, para seleccionar "
"más de uno." "más de una opción."
#: db/models/fields/related.py:770 #: db/models/fields/related.py:795
#, 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 "" msgid_plural ""
@ -4405,16 +4403,5 @@ msgstr "Se actualizó con éxito el %(verbose_name)s."
#: views/generic/create_update.py:184 #: views/generic/create_update.py:184
#, python-format #, python-format
msgid "The %(verbose_name)s was deleted." msgid "The %(verbose_name)s was deleted."
msgstr "El %(verbose_name)s ha sido eliminado." msgstr "El %(verbose_name)s ha sido borrado."
#~ msgid "Brazilian"
#~ msgstr "Brasileño"
#~ msgid "Gaeilge"
#~ msgstr "Gaeilge"
#~ msgid ""
#~ "Enter a postcode. A space is required between the two postcode parts."
#~ msgstr ""
#~ "Introduzca un código postal. Se necesita un espacio entre las dos partes "
#~ "del código."

View File

@ -1,94 +1,8 @@
""" """
Helper function for creating superusers in the authentication system. Create a superuser from the command line. Deprecated; use manage.py
createsuperuser instead.
If run from the command line, this module lets you create a superuser
interactively.
""" """
from django.core import validators
from django.contrib.auth.models import User
import getpass
import os
import sys
import re
RE_VALID_USERNAME = re.compile('\w+$')
def createsuperuser(username=None, email=None, password=None):
"""
Helper function for creating a superuser from the command line. All
arguments are optional and will be prompted-for if invalid or not given.
"""
try:
import pwd
except ImportError:
default_username = ''
else:
# Determine the current system user's username, to use as a default.
default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower()
# Determine whether the default username is taken, so we don't display
# it as an option.
if default_username:
try:
User.objects.get(username=default_username)
except User.DoesNotExist:
pass
else:
default_username = ''
try:
while 1:
if not username:
input_msg = 'Username'
if default_username:
input_msg += ' (Leave blank to use %r)' % default_username
username = raw_input(input_msg + ': ')
if default_username and username == '':
username = default_username
if not RE_VALID_USERNAME.match(username):
sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n")
username = None
continue
try:
User.objects.get(username=username)
except User.DoesNotExist:
break
else:
sys.stderr.write("Error: That username is already taken.\n")
username = None
while 1:
if not email:
email = raw_input('E-mail address: ')
try:
validators.isValidEmail(email, None)
except validators.ValidationError:
sys.stderr.write("Error: That e-mail address is invalid.\n")
email = None
else:
break
while 1:
if not password:
password = getpass.getpass()
password2 = getpass.getpass('Password (again): ')
if password != password2:
sys.stderr.write("Error: Your passwords didn't match.\n")
password = None
continue
if password.strip() == '':
sys.stderr.write("Error: Blank passwords aren't allowed.\n")
password = None
continue
break
except KeyboardInterrupt:
sys.stderr.write("\nOperation cancelled.\n")
sys.exit(1)
u = User.objects.create_user(username, email, password)
u.is_staff = True
u.is_active = True
u.is_superuser = True
u.save()
print "Superuser created successfully."
if __name__ == "__main__": if __name__ == "__main__":
createsuperuser() from django.core.management import call_command
call_command("createsuperuser")

View File

@ -32,7 +32,7 @@ def create_permissions(app, created_models, verbosity):
def create_superuser(app, created_models, verbosity, **kwargs): def create_superuser(app, created_models, verbosity, **kwargs):
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.create_superuser import createsuperuser as do_create from django.core.management import call_command
if User in created_models and kwargs.get('interactive', True): if User in created_models and kwargs.get('interactive', True):
msg = "\nYou just installed Django's auth system, which means you don't have " \ msg = "\nYou just installed Django's auth system, which means you don't have " \
"any superusers defined.\nWould you like to create one now? (yes/no): " "any superusers defined.\nWould you like to create one now? (yes/no): "
@ -42,8 +42,10 @@ def create_superuser(app, created_models, verbosity, **kwargs):
confirm = raw_input('Please enter either "yes" or "no": ') confirm = raw_input('Please enter either "yes" or "no": ')
continue continue
if confirm == 'yes': if confirm == 'yes':
do_create() call_command("createsuperuser", interactive=True)
break break
dispatcher.connect(create_permissions, signal=signals.post_syncdb) if 'create_permissions' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb)]:
dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb) dispatcher.connect(create_permissions, signal=signals.post_syncdb)
if 'create_superuser' not in [i.__name__ for i in dispatcher.getAllReceivers(signal=signals.post_syncdb, sender=auth_app)]:
dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb)

View File

@ -0,0 +1,123 @@
"""
Management utility to create superusers.
"""
import getpass
import os
import re
import sys
from optparse import make_option
from django.contrib.auth.models import User, UNUSABLE_PASSWORD
from django.core import validators
from django.core.management.base import BaseCommand, CommandError
RE_VALID_USERNAME = re.compile('\w+$')
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--username', dest='username', default=None,
help='Specifies the username for the superuser.'),
make_option('--email', dest='email', default=None,
help='Specifies the email address for the superuser.'),
make_option('--noinput', action='store_false', dest='interactive', default=True,
help='Tells Django to NOT prompt the user for input of any kind. ' \
'You must use --username and --email with --noinput, and ' \
'superusers created with --noinput will not be able to log in ' \
'until they\'re given a valid password.'),
)
help = 'Used to create a superuser.'
def handle(self, *args, **options):
username = options.get('username', None)
email = options.get('email', None)
interactive = options.get('interactive')
# Do quick and dirty validation if --noinput
if not interactive:
if not username or not email:
raise CommandError("You must use --username and --email with --noinput.")
if not RE_VALID_USERNAME.match(username):
raise CommandError("Invalid username. Use only letters, digits, and underscores")
try:
validators.isValidEmail(email, None)
except validators.ValidationError:
raise CommandError("Invalid email address.")
password = ''
# Try to determine the current system user's username to use as a default.
try:
import pwd
except ImportError:
default_username = ''
else:
default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower()
# Determine whether the default username is taken, so we don't display
# it as an option.
if default_username:
try:
User.objects.get(username=default_username)
except User.DoesNotExist:
pass
else:
default_username = ''
# Prompt for username/email/password. Enclose this whole thing in a
# try/except to trap for a keyboard interrupt and exit gracefully.
if interactive:
try:
# Get a username
while 1:
if not username:
input_msg = 'Username'
if default_username:
input_msg += ' (Leave blank to use %r)' % default_username
username = raw_input(input_msg + ': ')
if default_username and username == '':
username = default_username
if not RE_VALID_USERNAME.match(username):
sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n")
username = None
continue
try:
User.objects.get(username=username)
except User.DoesNotExist:
break
else:
sys.stderr.write("Error: That username is already taken.\n")
username = None
# Get an email
while 1:
if not email:
email = raw_input('E-mail address: ')
try:
validators.isValidEmail(email, None)
except validators.ValidationError:
sys.stderr.write("Error: That e-mail address is invalid.\n")
email = None
else:
break
# Get a password
while 1:
if not password:
password = getpass.getpass()
password2 = getpass.getpass('Password (again): ')
if password != password2:
sys.stderr.write("Error: Your passwords didn't match.\n")
password = None
continue
if password.strip() == '':
sys.stderr.write("Error: Blank passwords aren't allowed.\n")
password = None
continue
break
except KeyboardInterrupt:
sys.stderr.write("\nOperation cancelled.\n")
sys.exit(1)
User.objects.create_superuser(username, email, password)
print "Superuser created successfully."

View File

@ -116,6 +116,13 @@ class UserManager(models.Manager):
user.save() user.save()
return user return user
def create_superuser(self, username, email, password):
u = self.create_user(username, email, password)
u.is_staff = True
u.is_active = True
u.is_superuser = True
u.save()
def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'): def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
"Generates a random password with the given length and given allowed_chars" "Generates a random password with the given length and given allowed_chars"
# Note that default value of allowed_chars does not have "I" or letters # Note that default value of allowed_chars does not have "I" or letters

View File

@ -35,4 +35,21 @@ False
[] []
>>> a.user_permissions.all() >>> a.user_permissions.all()
[] []
#
# Tests for createsuperuser management command.
# It's nearly impossible to test the interactive mode -- a command test helper
# would be needed (and *awesome*) -- so just test the non-interactive mode.
# This covers most of the important validation, but not all.
#
>>> from django.core.management import call_command
>>> call_command("createsuperuser", noinput=True, username="joe", email="joe@somewhere.org")
Superuser created successfully.
>>> u = User.objects.get(username="joe")
>>> u.email
u'joe@somewhere.org'
>>> u.password
u'!'
""" """

View File

@ -4,6 +4,7 @@ import os
import random import random
import sys import sys
import time import time
from datetime import datetime, timedelta
from django.conf import settings from django.conf import settings
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
@ -128,6 +129,62 @@ class SessionBase(object):
_session = property(_get_session) _session = property(_get_session)
def get_expiry_age(self):
"""Get the number of seconds until the session expires."""
expiry = self.get('_session_expiry')
if not expiry: # Checks both None and 0 cases
return settings.SESSION_COOKIE_AGE
if not isinstance(expiry, datetime):
return expiry
delta = expiry - datetime.now()
return delta.days * 86400 + delta.seconds
def get_expiry_date(self):
"""Get session the expiry date (as a datetime object)."""
expiry = self.get('_session_expiry')
if isinstance(expiry, datetime):
return expiry
if not expiry: # Checks both None and 0 cases
expiry = settings.SESSION_COOKIE_AGE
return datetime.now() + timedelta(seconds=expiry)
def set_expiry(self, value):
"""
Sets a custom expiration for the session. ``value`` can be an integer, a
Python ``datetime`` or ``timedelta`` object or ``None``.
If ``value`` is an integer, the session will expire after that many
seconds of inactivity. If set to ``0`` then the session will expire on
browser close.
If ``value`` is a ``datetime`` or ``timedelta`` object, the session
will expire at that specific future time.
If ``value`` is ``None``, the session uses the global session expiry
policy.
"""
if value is None:
# Remove any custom expiration for this session.
try:
del self['_session_expiry']
except KeyError:
pass
return
if isinstance(value, timedelta):
value = datetime.now() + value
self['_session_expiry'] = value
def get_expire_at_browser_close(self):
"""
Returns ``True`` if the session is set to expire when the browser
closes, and ``False`` if there's an expiry date. Use
``get_expiry_date()`` or ``get_expiry_age()`` to find the actual expiry
date/age, if there is one.
"""
if self.get('_session_expiry') is None:
return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
return self.get('_session_expiry') == 0
# Methods that child classes must implement. # Methods that child classes must implement.
def exists(self, session_key): def exists(self, session_key):

View File

@ -15,7 +15,7 @@ class SessionStore(SessionBase):
return session_data or {} return session_data or {}
def save(self): def save(self):
self._cache.set(self.session_key, self._session, settings.SESSION_COOKIE_AGE) self._cache.set(self.session_key, self._session, self.get_expiry_age())
def exists(self, session_key): def exists(self, session_key):
if self._cache.get(session_key): if self._cache.get(session_key):

View File

@ -41,7 +41,7 @@ class SessionStore(SessionBase):
Session.objects.create( Session.objects.create(
session_key = self.session_key, session_key = self.session_key,
session_data = self.encode(self._session), session_data = self.encode(self._session),
expire_date = datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE) expire_date = self.get_expiry_date()
) )
def delete(self, session_key): def delete(self, session_key):

View File

@ -26,14 +26,14 @@ class SessionMiddleware(object):
if accessed: if accessed:
patch_vary_headers(response, ('Cookie',)) patch_vary_headers(response, ('Cookie',))
if modified or settings.SESSION_SAVE_EVERY_REQUEST: if modified or settings.SESSION_SAVE_EVERY_REQUEST:
if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE: if request.session.get_expire_at_browser_close():
max_age = None max_age = None
expires = None expires = None
else: else:
max_age = settings.SESSION_COOKIE_AGE max_age = request.session.get_expiry_age()
expires_time = time.time() + settings.SESSION_COOKIE_AGE expires_time = time.time() + max_age
expires = cookie_date(expires_time) expires = cookie_date(expires_time)
# Save the seesion data and refresh the client cookie. # Save the session data and refresh the client cookie.
request.session.save() request.session.save()
response.set_cookie(settings.SESSION_COOKIE_NAME, response.set_cookie(settings.SESSION_COOKIE_NAME,
request.session.session_key, max_age=max_age, request.session.session_key, max_age=max_age,

View File

@ -88,6 +88,100 @@ False
>>> s.pop('some key', 'does not exist') >>> s.pop('some key', 'does not exist')
'does not exist' 'does not exist'
#########################
# Custom session expiry #
#########################
>>> from django.conf import settings
>>> from datetime import datetime, timedelta
>>> td10 = timedelta(seconds=10)
# A normal session has a max age equal to settings
>>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE
True
# So does a custom session with an idle expiration time of 0 (but it'll expire
# at browser close)
>>> s.set_expiry(0)
>>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE
True
# Custom session idle expiration time
>>> s.set_expiry(10)
>>> delta = s.get_expiry_date() - datetime.now()
>>> delta.seconds in (9, 10)
True
>>> age = s.get_expiry_age()
>>> age in (9, 10)
True
# Custom session fixed expiry date (timedelta)
>>> s.set_expiry(td10)
>>> delta = s.get_expiry_date() - datetime.now()
>>> delta.seconds in (9, 10)
True
>>> age = s.get_expiry_age()
>>> age in (9, 10)
True
# Custom session fixed expiry date (fixed datetime)
>>> s.set_expiry(datetime.now() + td10)
>>> delta = s.get_expiry_date() - datetime.now()
>>> delta.seconds in (9, 10)
True
>>> age = s.get_expiry_age()
>>> age in (9, 10)
True
# Set back to default session age
>>> s.set_expiry(None)
>>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE
True
# Allow to set back to default session age even if no alternate has been set
>>> s.set_expiry(None)
# We're changing the setting then reverting back to the original setting at the
# end of these tests.
>>> original_expire_at_browser_close = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
>>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# Custom session age
>>> s.set_expiry(10)
>>> s.get_expire_at_browser_close()
False
# Custom expire-at-browser-close
>>> s.set_expiry(0)
>>> s.get_expire_at_browser_close()
True
# Default session age
>>> s.set_expiry(None)
>>> s.get_expire_at_browser_close()
False
>>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = True
# Custom session age
>>> s.set_expiry(10)
>>> s.get_expire_at_browser_close()
False
# Custom expire-at-browser-close
>>> s.set_expiry(0)
>>> s.get_expire_at_browser_close()
True
# Default session age
>>> s.set_expiry(None)
>>> s.get_expire_at_browser_close()
True
>>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = original_expire_at_browser_close
""" """
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -9,6 +9,8 @@ class Command(BaseCommand):
help='Specifies the output serialization format for fixtures.'), help='Specifies the output serialization format for fixtures.'),
make_option('--indent', default=None, dest='indent', type='int', make_option('--indent', default=None, dest='indent', type='int',
help='Specifies the indent level to use when pretty-printing output'), help='Specifies the indent level to use when pretty-printing output'),
make_option('-e', '--exclude', dest='exclude',action='append', default=[],
help='App to exclude (use multiple --exclude to exclude multiple apps).'),
) )
help = 'Output the contents of the database as a fixture of the given format.' help = 'Output the contents of the database as a fixture of the given format.'
args = '[appname ...]' args = '[appname ...]'
@ -16,12 +18,15 @@ class Command(BaseCommand):
def handle(self, *app_labels, **options): def handle(self, *app_labels, **options):
from django.db.models import get_app, get_apps, get_models from django.db.models import get_app, get_apps, get_models
format = options.get('format', 'json') format = options.get('format','json')
indent = options.get('indent', None) indent = options.get('indent',None)
exclude = options.get('exclude',[])
show_traceback = options.get('traceback', False) show_traceback = options.get('traceback', False)
excluded_apps = [get_app(app_label) for app_label in exclude]
if len(app_labels) == 0: if len(app_labels) == 0:
app_list = get_apps() app_list = [app for app in get_apps() if app not in excluded_apps]
else: else:
app_list = [get_app(app_label) for app_label in app_labels] app_list = [get_app(app_label) for app_label in app_labels]

View File

@ -32,6 +32,7 @@ class Command(BaseCommand):
# Keep a count of the installed objects and fixtures # Keep a count of the installed objects and fixtures
fixture_count = 0 fixture_count = 0
object_count = 0 object_count = 0
objects_per_fixture = []
models = set() models = set()
humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path' humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path'
@ -60,11 +61,16 @@ class Command(BaseCommand):
else: else:
formats = [] formats = []
if verbosity >= 2:
if formats: if formats:
if verbosity > 1:
print "Loading '%s' fixtures..." % fixture_name print "Loading '%s' fixtures..." % fixture_name
else: else:
print "Skipping fixture '%s': %s is not a known serialization format" % (fixture_name, format) sys.stderr.write(
self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format." %
(fixture_name, format)))
transaction.rollback()
transaction.leave_transaction_management()
return
if os.path.isabs(fixture_name): if os.path.isabs(fixture_name):
fixture_dirs = [fixture_name] fixture_dirs = [fixture_name]
@ -93,6 +99,7 @@ class Command(BaseCommand):
return return
else: else:
fixture_count += 1 fixture_count += 1
objects_per_fixture.append(0)
if verbosity > 0: if verbosity > 0:
print "Installing %s fixture '%s' from %s." % \ print "Installing %s fixture '%s' from %s." % \
(format, fixture_name, humanize(fixture_dir)) (format, fixture_name, humanize(fixture_dir))
@ -100,6 +107,7 @@ class Command(BaseCommand):
objects = serializers.deserialize(format, fixture) objects = serializers.deserialize(format, fixture)
for obj in objects: for obj in objects:
object_count += 1 object_count += 1
objects_per_fixture[-1] += 1
models.add(obj.object.__class__) models.add(obj.object.__class__)
obj.save() obj.save()
label_found = True label_found = True
@ -117,10 +125,23 @@ class Command(BaseCommand):
return return
fixture.close() fixture.close()
except: except:
if verbosity >= 2: 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 any of the fixtures we loaded contain 0 objects, assume that an
# error was encountered during fixture loading.
if 0 in objects_per_fixture:
sys.stderr.write(
self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)" %
(fixture_name)))
transaction.rollback()
transaction.leave_transaction_management()
return
# If we found even one object in a fixture, we need to reset the
# database sequences.
if object_count > 0: if object_count > 0:
sequence_sql = connection.ops.sequence_reset_sql(self.style, models) sequence_sql = connection.ops.sequence_reset_sql(self.style, models)
if sequence_sql: if sequence_sql:
@ -133,7 +154,7 @@ class Command(BaseCommand):
transaction.leave_transaction_management() transaction.leave_transaction_management()
if object_count == 0: if object_count == 0:
if verbosity >= 2: if verbosity > 1:
print "No fixtures found." print "No fixtures found."
else: else:
if verbosity > 0: if verbosity > 0:

View File

@ -34,7 +34,16 @@ class Command(NoArgsCommand):
try: try:
__import__(app_name + '.management', {}, {}, ['']) __import__(app_name + '.management', {}, {}, [''])
except ImportError, exc: except ImportError, exc:
if not exc.args[0].startswith('No module named management'): # This is slightly hackish. We want to ignore ImportErrors
# if the "management" module itself is missing -- but we don't
# want to ignore the exception if the management module exists
# but raises an ImportError for some reason. The only way we
# can do this is to check the text of the exception. Note that
# we're a bit broad in how we check the text, because different
# Python implementations may not use the same text. CPython
# uses the text "No module named management".
msg = exc.args[0]
if not msg.startswith('No module named management') or 'management' not in msg:
raise raise
cursor = connection.cursor() cursor = connection.cursor()

View File

@ -454,7 +454,7 @@ def custom_sql_for_model(model, style):
fp = open(sql_file, 'U') fp = open(sql_file, 'U')
for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)): for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)):
# Remove any comments from the file # Remove any comments from the file
statement = re.sub(ur"--.*[\n\Z]", "", statement) statement = re.sub(ur"--.*([\n\Z]|$)", "", statement)
if statement.strip(): if statement.strip():
output.append(statement + u";") output.append(statement + u";")
fp.close() fp.close()

View File

@ -38,7 +38,7 @@ class Serializer(object):
self.start_serialization() self.start_serialization()
for obj in queryset: for obj in queryset:
self.start_object(obj) self.start_object(obj)
for field in obj._meta.fields: for field in obj._meta.local_fields:
if field.serialize: if field.serialize:
if field.rel is None: if field.rel is None:
if self.selected_fields is None or field.attname in self.selected_fields: if self.selected_fields is None or field.attname in self.selected_fields:

View File

@ -202,8 +202,8 @@ class BaseDatabaseOperations(object):
def query_class(self, DefaultQueryClass): def query_class(self, DefaultQueryClass):
""" """
Given the default QuerySet class, returns a custom QuerySet class Given the default Query class, returns a custom Query class
to use for this backend. Returns None if a custom QuerySet isn't used. to use for this backend. Returns None if a custom Query isn't used.
See also BaseDatabaseFeatures.uses_custom_query_class, which regulates See also BaseDatabaseFeatures.uses_custom_query_class, which regulates
whether this method is called at all. whether this method is called at all.
""" """

View File

@ -290,6 +290,11 @@ class Model(object):
meta = cls._meta meta = cls._meta
signal = False signal = False
# If we are in a raw save, save the object exactly as presented.
# That means that we don't try to be smart about saving attributes
# that might have come from the parent class - we just save the
# attributes we have been given to the class we have been given.
if not raw:
for parent, field in meta.parents.items(): for parent, field in meta.parents.items():
self.save_base(raw, parent) self.save_base(raw, parent)
setattr(self, field.attname, self._get_pk_val(parent._meta)) setattr(self, field.attname, self._get_pk_val(parent._meta))

View File

@ -182,14 +182,29 @@ class SingleRelatedObjectDescriptor(object):
def __set__(self, instance, value): def __set__(self, instance, value):
if instance is None: if instance is None:
raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
# The similarity of the code below to the code in
# ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch
# of small differences that would make a common base class convoluted.
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
if value is None and self.related.field.null == False:
raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
(instance._meta.object_name, self.related.get_accessor_name()))
elif value is not None and not isinstance(value, self.related.model):
raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
(value, instance._meta.object_name,
self.related.get_accessor_name(), self.related.opts.object_name))
# Set the value of the related field # Set the value of the related field
setattr(value, self.related.field.rel.get_related_field().attname, instance) setattr(value, self.related.field.rel.get_related_field().attname, instance)
# Clear the cache, if it exists # Since we already know what the related object is, seed the related
try: # object caches now, too. This avoids another db hit if you get the
delattr(value, self.related.field.get_cache_name()) # object you just set.
except AttributeError: setattr(instance, self.cache_name, value)
pass setattr(value, self.related.field.get_cache_name(), instance)
class ReverseSingleRelatedObjectDescriptor(object): class ReverseSingleRelatedObjectDescriptor(object):
# This class provides the functionality that makes the related-object # This class provides the functionality that makes the related-object
@ -225,6 +240,17 @@ class ReverseSingleRelatedObjectDescriptor(object):
def __set__(self, instance, value): def __set__(self, instance, value):
if instance is None: if instance is None:
raise AttributeError, "%s must be accessed via instance" % self._field.name raise AttributeError, "%s must be accessed via instance" % self._field.name
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
if value is None and self.field.null == False:
raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' %
(instance._meta.object_name, self.field.name))
elif value is not None and not isinstance(value, self.field.rel.to):
raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
(value, instance._meta.object_name,
self.field.name, self.field.rel.to._meta.object_name))
# Set the value of the related field # Set the value of the related field
try: try:
val = getattr(value, self.field.rel.get_related_field().attname) val = getattr(value, self.field.rel.get_related_field().attname)
@ -232,11 +258,10 @@ class ReverseSingleRelatedObjectDescriptor(object):
val = None val = None
setattr(instance, self.field.attname, val) setattr(instance, self.field.attname, val)
# Clear the cache, if it exists # Since we already know what the related object is, seed the related
try: # object cache now, too. This avoids another db hit if you get the
delattr(instance, self.field.get_cache_name()) # object you just set.
except AttributeError: setattr(instance, self.field.get_cache_name(), value)
pass
class ForeignRelatedObjectsDescriptor(object): class ForeignRelatedObjectsDescriptor(object):
# This class provides the functionality that makes the related-object # This class provides the functionality that makes the related-object
@ -326,7 +351,7 @@ def create_many_related_manager(superclass):
self.target_col_name = target_col_name self.target_col_name = target_col_name
self._pk_val = self.instance._get_pk_val() self._pk_val = self.instance._get_pk_val()
if self._pk_val is None: if self._pk_val is None:
raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % model) raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__)
def get_query_set(self): def get_query_set(self):
return superclass.get_query_set(self).filter(**(self.core_filters)) return superclass.get_query_set(self).filter(**(self.core_filters))

View File

@ -56,8 +56,12 @@ class Options(object):
# Next, apply any overridden values from 'class Meta'. # Next, apply any overridden values from 'class Meta'.
if self.meta: if self.meta:
meta_attrs = self.meta.__dict__.copy() meta_attrs = self.meta.__dict__.copy()
del meta_attrs['__module__'] for name in self.meta.__dict__:
del meta_attrs['__doc__'] # Ignore any private attributes that Django doesn't care about.
# NOTE: We can't modify a dictionary's contents while looping
# over it, so we loop over the *original* dictionary instead.
if name.startswith('_'):
del meta_attrs[name]
for attr_name in DEFAULT_NAMES: for attr_name in DEFAULT_NAMES:
if attr_name in meta_attrs: if attr_name in meta_attrs:
setattr(self, attr_name, meta_attrs.pop(attr_name)) setattr(self, attr_name, meta_attrs.pop(attr_name))
@ -98,7 +102,7 @@ class Options(object):
# field. # field.
field = self.parents.value_for_index(0) field = self.parents.value_for_index(0)
field.primary_key = True field.primary_key = True
self.pk = field self.setup_pk(field)
else: else:
auto = AutoField(verbose_name='ID', primary_key=True, auto = AutoField(verbose_name='ID', primary_key=True,
auto_created=True) auto_created=True)

View File

@ -292,6 +292,8 @@ class QuerySet(object):
Updates all elements in the current QuerySet, setting all the given Updates all elements in the current QuerySet, setting all the given
fields to the appropriate values. fields to the appropriate values.
""" """
assert self.query.can_filter(), \
"Cannot update a query once a slice has been taken."
query = self.query.clone(sql.UpdateQuery) query = self.query.clone(sql.UpdateQuery)
query.add_update_values(kwargs) query.add_update_values(kwargs)
query.execute_sql(None) query.execute_sql(None)
@ -306,6 +308,8 @@ class QuerySet(object):
code (it requires too much poking around at model internals to be code (it requires too much poking around at model internals to be
useful at that level). useful at that level).
""" """
assert self.query.can_filter(), \
"Cannot update a query once a slice has been taken."
query = self.query.clone(sql.UpdateQuery) query = self.query.clone(sql.UpdateQuery)
query.add_update_fields(values) query.add_update_fields(values)
query.execute_sql(None) query.execute_sql(None)
@ -509,6 +513,8 @@ class ValuesQuerySet(QuerySet):
# names of the model fields to select. # names of the model fields to select.
def iterator(self): def iterator(self):
if (not self.extra_names and
len(self.field_names) != len(self.model._meta.fields)):
self.query.trim_extra_select(self.extra_names) self.query.trim_extra_select(self.extra_names)
names = self.query.extra_select.keys() + self.field_names names = self.query.extra_select.keys() + self.field_names
for row in self.query.results_iter(): for row in self.query.results_iter():

View File

@ -851,7 +851,7 @@ class Query(object):
return alias return alias
def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1, def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
used=None, requested=None, restricted=None): used=None, requested=None, restricted=None, nullable=None):
""" """
Fill in the information needed for a select_related query. The current Fill in the information needed for a select_related query. The current
depth is measured as the number of connections away from the root model depth is measured as the number of connections away from the root model
@ -883,6 +883,10 @@ class Query(object):
(not restricted and f.null) or f.rel.parent_link): (not restricted and f.null) or f.rel.parent_link):
continue continue
table = f.rel.to._meta.db_table table = f.rel.to._meta.db_table
if nullable or f.null:
promote = True
else:
promote = False
if model: if model:
int_opts = opts int_opts = opts
alias = root_alias alias = root_alias
@ -891,12 +895,12 @@ class Query(object):
int_opts = int_model._meta int_opts = int_model._meta
alias = self.join((alias, int_opts.db_table, lhs_col, alias = self.join((alias, int_opts.db_table, lhs_col,
int_opts.pk.column), exclusions=used, int_opts.pk.column), exclusions=used,
promote=f.null) promote=promote)
else: else:
alias = root_alias alias = root_alias
alias = self.join((alias, table, f.column, alias = self.join((alias, table, f.column,
f.rel.get_related_field().column), exclusions=used, f.rel.get_related_field().column), exclusions=used,
promote=f.null) promote=promote)
used.add(alias) used.add(alias)
self.related_select_cols.extend([(alias, f2.column) self.related_select_cols.extend([(alias, f2.column)
for f2 in f.rel.to._meta.fields]) for f2 in f.rel.to._meta.fields])
@ -905,8 +909,12 @@ class Query(object):
next = requested.get(f.name, {}) next = requested.get(f.name, {})
else: else:
next = False next = False
if f.null is not None:
new_nullable = f.null
else:
new_nullable = None
self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1, self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1,
used, next, restricted) used, next, restricted, new_nullable)
def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
can_reuse=None): can_reuse=None):

View File

@ -2,10 +2,9 @@
Query subclasses which provide extra functionality beyond simple data retrieval. Query subclasses which provide extra functionality beyond simple data retrieval.
""" """
from django.contrib.contenttypes import generic
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.db.models.sql.constants import * from django.db.models.sql.constants import *
from django.db.models.sql.datastructures import RawValue, Date from django.db.models.sql.datastructures import Date
from django.db.models.sql.query import Query from django.db.models.sql.query import Query
from django.db.models.sql.where import AND from django.db.models.sql.where import AND
@ -43,6 +42,7 @@ class DeleteQuery(Query):
More than one physical query may be executed if there are a More than one physical query may be executed if there are a
lot of values in pk_list. lot of values in pk_list.
""" """
from django.contrib.contenttypes import generic
cls = self.model cls = self.model
for related in cls._meta.get_all_related_many_to_many_objects(): for related in cls._meta.get_all_related_many_to_many_objects():
if not isinstance(related.field, generic.GenericRelation): if not isinstance(related.field, generic.GenericRelation):
@ -382,4 +382,3 @@ class CountQuery(Query):
def get_ordering(self): def get_ordering(self):
return () return ()

View File

@ -18,7 +18,7 @@ class WhereNode(tree.Node):
Used to represent the SQL where-clause. Used to represent the SQL where-clause.
The class is tied to the Query class that created it (in order to create The class is tied to the Query class that created it (in order to create
the corret SQL). the correct SQL).
The children in this tree are usually either Q-like objects or lists of The children in this tree are usually either Q-like objects or lists of
[table_alias, field_name, field_class, lookup_type, value]. However, a [table_alias, field_name, field_class, lookup_type, value]. However, a

View File

@ -467,7 +467,7 @@ class FilterExpression(object):
>>> len(fe.filters) >>> len(fe.filters)
2 2
>>> fe.var >>> fe.var
'variable' <Variable: 'variable'>
This class should never be instantiated outside of the This class should never be instantiated outside of the
get_filters_from_token helper function. get_filters_from_token helper function.
@ -598,15 +598,15 @@ class Variable(object):
a hard-coded string (if it begins and ends with single or double quote a hard-coded string (if it begins and ends with single or double quote
marks):: marks)::
>>> c = {'article': {'section':'News'}} >>> c = {'article': {'section':u'News'}}
>>> Variable('article.section').resolve(c) >>> Variable('article.section').resolve(c)
u'News' u'News'
>>> Variable('article').resolve(c) >>> Variable('article').resolve(c)
{'section': 'News'} {'section': u'News'}
>>> class AClass: pass >>> class AClass: pass
>>> c = AClass() >>> c = AClass()
>>> c.article = AClass() >>> c.article = AClass()
>>> c.article.section = 'News' >>> c.article.section = u'News'
>>> Variable('article.section').resolve(c) >>> Variable('article.section').resolve(c)
u'News' u'News'

View File

@ -1,5 +1,6 @@
import urllib import urllib
import sys import sys
import os
from cStringIO import StringIO from cStringIO import StringIO
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
@ -67,7 +68,7 @@ def encode_multipart(boundary, data):
if isinstance(value, file): if isinstance(value, file):
lines.extend([ lines.extend([
'--' + boundary, '--' + boundary,
'Content-Disposition: form-data; name="%s"; filename="%s"' % (to_str(key), to_str(value.name)), 'Content-Disposition: form-data; name="%s"; filename="%s"' % (to_str(key), to_str(os.path.basename(value.name))),
'Content-Type: application/octet-stream', 'Content-Type: application/octet-stream',
'', '',
value.read() value.read()
@ -178,9 +179,14 @@ class Client:
if e.args != ('500.html',): if e.args != ('500.html',):
raise raise
# Look for a signalled exception and reraise it # Look for a signalled exception, clear the current context
# exception data, then re-raise the signalled exception.
# Also make sure that the signalled exception is cleared from
# the local cache!
if self.exc_info: if self.exc_info:
raise self.exc_info[1], None, self.exc_info[2] exc_info = self.exc_info
self.exc_info = None
raise exc_info[1], None, exc_info[2]
# Save the client and request that stimulated the response # Save the client and request that stimulated the response
response.client = self response.client = self

View File

@ -128,6 +128,18 @@ class TestCase(unittest.TestCase):
self.failUnless(real_count != 0, self.failUnless(real_count != 0,
"Couldn't find '%s' in response" % text) "Couldn't find '%s' in response" % text)
def assertNotContains(self, response, text, status_code=200):
"""
Asserts that a response indicates that a page was retrieved
successfully, (i.e., the HTTP status code was as expected), and that
``text`` doesn't occurs in the content of the response.
"""
self.assertEqual(response.status_code, status_code,
"Couldn't retrieve page: Response code was %d (expected %d)'" %
(response.status_code, status_code))
self.assertEqual(response.content.count(text), 0,
"Response should not contain '%s'" % text)
def assertFormError(self, response, form, field, errors): def assertFormError(self, response, form, field, errors):
""" """
Asserts that a form used to render the response has a specific field Asserts that a form used to render the response has a specific field

View File

@ -118,7 +118,7 @@ def get_valid_filename(s):
spaces are converted to underscores; and all non-filename-safe characters spaces are converted to underscores; and all non-filename-safe characters
are removed. are removed.
>>> get_valid_filename("john's portrait in 2004.jpg") >>> get_valid_filename("john's portrait in 2004.jpg")
'johns_portrait_in_2004.jpg' u'johns_portrait_in_2004.jpg'
""" """
s = force_unicode(s).strip().replace(' ', '_') s = force_unicode(s).strip().replace(' ', '_')
return re.sub(r'[^-A-Za-z0-9_.]', '', s) return re.sub(r'[^-A-Za-z0-9_.]', '', s)
@ -127,15 +127,15 @@ get_valid_filename = allow_lazy(get_valid_filename, unicode)
def get_text_list(list_, last_word=ugettext_lazy(u'or')): def get_text_list(list_, last_word=ugettext_lazy(u'or')):
""" """
>>> get_text_list(['a', 'b', 'c', 'd']) >>> get_text_list(['a', 'b', 'c', 'd'])
'a, b, c or d' u'a, b, c or d'
>>> get_text_list(['a', 'b', 'c'], 'and') >>> get_text_list(['a', 'b', 'c'], 'and')
'a, b and c' u'a, b and c'
>>> get_text_list(['a', 'b'], 'and') >>> get_text_list(['a', 'b'], 'and')
'a and b' u'a and b'
>>> get_text_list(['a']) >>> get_text_list(['a'])
'a' u'a'
>>> get_text_list([]) >>> get_text_list([])
'' u''
""" """
if len(list_) == 0: return u'' if len(list_) == 0: return u''
if len(list_) == 1: return force_unicode(list_[0]) if len(list_) == 1: return force_unicode(list_[0])
@ -198,14 +198,18 @@ javascript_quote = allow_lazy(javascript_quote, unicode)
smart_split_re = re.compile('("(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|[^\\s]+)') smart_split_re = re.compile('("(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|[^\\s]+)')
def smart_split(text): def smart_split(text):
""" r"""
Generator that splits a string by spaces, leaving quoted phrases together. Generator that splits a string by spaces, leaving quoted phrases together.
Supports both single and double quotes, and supports escaping quotes with Supports both single and double quotes, and supports escaping quotes with
backslashes. In the output, strings will keep their initial and trailing backslashes. In the output, strings will keep their initial and trailing
quote marks. quote marks.
>>> list(smart_split('This is "a person\'s" test.')) >>> list(smart_split(r'This is "a person\'s" test.'))
['This', 'is', '"a person\'s"', 'test.'] [u'This', u'is', u'"a person\\\'s"', u'test.']
>>> list(smart_split(r"Another 'person\'s' test."))
[u'Another', u"'person's'", u'test.']
>>> list(smart_split(r'A "\"funky\" style" test.'))
[u'A', u'""funky" style"', u'test.']
""" """
text = force_unicode(text) text = force_unicode(text)
for bit in smart_split_re.finditer(text): for bit in smart_split_re.finditer(text):

View File

@ -263,14 +263,25 @@ Creating superusers
------------------- -------------------
``manage.py syncdb`` prompts you to create a superuser the first time you run ``manage.py syncdb`` prompts you to create a superuser the first time you run
it after adding ``'django.contrib.auth'`` to your ``INSTALLED_APPS``. But if it after adding ``'django.contrib.auth'`` to your ``INSTALLED_APPS``. If you need
you need to create a superuser after that via the command line, you can use the to create a superuser at a later date, you can use a command line utility.
``create_superuser.py`` utility. Just run this command::
**New in Django development version.**::
manage.py createsuperuser --username=joe --email=joe@example.com
You will be prompted for a password. After you enter one, the user will be
created immediately. If you leave off the ``--username`` or the ``--email``
options, it will prompt you for those values.
If you're using an older release of Django, the old way of creating a superuser
on the command line still works::
python /path/to/django/contrib/auth/create_superuser.py python /path/to/django/contrib/auth/create_superuser.py
Make sure to substitute ``/path/to/`` with the path to the Django codebase on ...where ``/path/to`` is the path to the Django codebase on your filesystem. The
your filesystem. ``manage.py`` command is preferred because it figures out the correct path and
environment for you.
Storing additional information about users Storing additional information about users
------------------------------------------ ------------------------------------------

View File

@ -148,15 +148,17 @@ Once you've claimed a ticket, you have a responsibility to work on that ticket
in a reasonably timely fashion. If you don't have time to work on it, either in a reasonably timely fashion. If you don't have time to work on it, either
unclaim it or don't claim it in the first place! unclaim it or don't claim it in the first place!
Core Django developers go through the list of claimed tickets from time to Ticket triagers go through the list of claimed tickets from time to
time, checking whether any progress has been made. If there's no sign of time, checking whether any progress has been made. If there's no sign of
progress on a particular claimed ticket for a week or two after it's been progress on a particular claimed ticket for a week or two, a triager may ask
claimed, we will unclaim it for you so that it's no longer monopolized and you to relinquish the ticket claim so that it's no longer monopolized and
somebody else can claim it. somebody else can claim it.
If you've claimed a ticket and it's taking a long time (days or weeks) to code, If you've claimed a ticket and it's taking a long time (days or weeks) to code,
keep everybody updated by posting comments on the ticket. That way, we'll know keep everybody updated by posting comments on the ticket. If you don't provide
not to unclaim it. More communication is better than less communication! regular updates, and you don't respond to a request for a progress report,
your claim on the ticket may be revoked. As always, more communication is
better than less communication!
Which tickets should be claimed? Which tickets should be claimed?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -234,22 +236,28 @@ Since a picture is worth a thousand words, let's start there:
:width: 590 :width: 590
:alt: Django's ticket workflow :alt: Django's ticket workflow
We've got two roles here: We've got two official roles here:
* Core developers: people with commit access who make the decisions and * Core developers: people with commit access who make the big decisions
write the bulk of the code. and write the bulk of the code.
* Ticket triagers: community members who keep track of tickets, making * Ticket triagers: trusted community members with a proven history of
sure the tickets are always categorized correctly. working with the Django community. As a result of this history, they
have been entrusted by the core developers to make some of the smaller
decisions about tickets.
Second, note the five triage stages: Second, note the five triage stages:
1. A ticket starts as "Unreviewed", meaning that a triager has yet to 1. A ticket starts as "Unreviewed", meaning that nobody has examined
examine the ticket and move it along. the ticket.
2. "Design decision needed" means "this concept requires a design 2. "Design decision needed" means "this concept requires a design
decision," which should be discussed either in the ticket comments or on decision," which should be discussed either in the ticket comments or on
django-developers. `django-developers`_. The "Design decision needed" step will generally
only be used for feature requests. It can also be used for issues
that *might* be bugs, depending on opinion or interpretation. Obvious
bugs (such as crashes, incorrect query results, or non-compliance with a
standard) skip this step and move straight to "Accepted".
3. Once a ticket is ruled to be approved for fixing, it's moved into the 3. Once a ticket is ruled to be approved for fixing, it's moved into the
"Accepted" stage. This stage is where all the real work gets done. "Accepted" stage. This stage is where all the real work gets done.
@ -269,7 +277,7 @@ ticket has or needs in order to be "ready for checkin":
"Has patch" "Has patch"
This means the ticket has an associated patch_. These will be This means the ticket has an associated patch_. These will be
reviewed to see if the patch is "good". reviewed by the triage team to see if the patch is "good".
"Needs documentation" "Needs documentation"
This flag is used for tickets with patches that need associated This flag is used for tickets with patches that need associated
@ -292,7 +300,11 @@ A ticket can be resolved in a number of ways:
Django and the issue is fixed. Django and the issue is fixed.
"invalid" "invalid"
Used if the ticket is found to be incorrect or a user error. Used if the ticket is found to be incorrect. This means that the
issue in the ticket is actually the result of a user error, or
describes a problem with something other than Django, or isn't
a bug report or feature request at all (for example, some new users
submit support queries as tickets).
"wontfix" "wontfix"
Used when a core developer decides that this request is not Used when a core developer decides that this request is not
@ -305,7 +317,8 @@ A ticket can be resolved in a number of ways:
tickets, we keep all the discussion in one place, which helps everyone. tickets, we keep all the discussion in one place, which helps everyone.
"worksforme" "worksforme"
Used when the triage team is unable to replicate the original bug. Used when the the ticket doesn't contain enough detail to replicate
the original bug.
If you believe that the ticket was closed in error -- because you're If you believe that the ticket was closed in error -- because you're
still having the issue, or it's popped up somewhere else, or the triagers have still having the issue, or it's popped up somewhere else, or the triagers have
@ -316,6 +329,55 @@ reopen tickets that have been marked as "wontfix" by core developers.
.. _good patch: `Patch style`_ .. _good patch: `Patch style`_
.. _patch: `Submitting patches`_ .. _patch: `Submitting patches`_
Triage by the general community
-------------------------------
Although the Core Developers and Ticket Triagers make the big decisions in
the ticket triage process, there is also a lot that general community
members can do to help the triage process. In particular, you can help out by:
* Closing "Unreviewed" tickets as "invalid", "worksforme", or "duplicate".
* Promoting "Unreviewed" tickets to "Design Decision Required" if there
is a design decision that needs to be made, or "Accepted" if they are
an obvious bug.
* Correcting the "Needs Tests", "Needs documentation", or "Has Patch" flags
for tickets where they are incorrectly set.
* Checking that old tickets are still valid. If a ticket hasn't seen
any activity in a long time, it's possible that the problem has been
fixed, but the ticket hasn't been closed.
* Contact the owners of tickets that have been claimed, but have not seen
any recent activity. If the owner doesn't respond after a week or so,
remove the owner's claim on the ticket.
* Identifying trends and themes in the tickets. If there a lot of bug reports
about a particular part of Django, it possibly indicates that we need
to consider refactoring that part of the code. If a trend is emerging,
you should raise it for discussion (referencing the relevant tickets)
on `django-developers`_.
However, we do ask that as a general community member working in the
ticket database:
* Please **don't** close tickets as "wontfix". The core developers will
make the final determination of the fate of a ticket, usually after
consultation with the community.
* Please **don't** promote tickets to "Ready for checkin" unless they are
*trivial* changes - for example, spelling mistakes or
broken links in documentation.
* Please **don't** reverse a decision that has been made by a core
developer. If you disagree with a discussion that has been made,
please post a message to `django-developers`_.
* Please be conservative in your actions. If you're unsure if you should
be making a change, don't make the change - leave a comment with your
concerns on the ticket, or post a message to `django-developers`_.
Submitting and maintaining translations Submitting and maintaining translations
======================================= =======================================

View File

@ -1373,6 +1373,17 @@ SQL equivalent::
SELECT ... WHERE id IN (1, 3, 4); SELECT ... WHERE id IN (1, 3, 4);
You can also use a queryset to dynamically evaluate the list of values
instead of providing a list of literal values. The queryset must be
reduced to a list of individual values using the ``values()`` method,
and then converted into a query using the ``query`` attribute::
Entry.objects.filter(blog__in=Blog.objects.filter(name__contains='Cheddar').values('pk').query)
This queryset will be evaluated as subselect statement::
SELET ... WHERE blog.id IN (SELECT id FROM ... WHERE NAME LIKE '%Cheddar%')
startswith startswith
~~~~~~~~~~ ~~~~~~~~~~
@ -2026,6 +2037,37 @@ Each "reverse" operation described in this section has an immediate effect on
the database. Every addition, creation and deletion is immediately and the database. Every addition, creation and deletion is immediately and
automatically saved to the database. automatically saved to the database.
One-to-one relationships
------------------------
One-to-one relationships are very similar to Many-to-one relationships.
If you define a OneToOneField on your model, instances of that model will have
access to the related object via a simple attribute of the model.
For example::
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
The difference comes in reverse queries. The related model in a One-to-one
relationship also has access to a ``Manager`` object; however, that ``Manager``
represents a single object, rather than a collection of objects::
e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object
If no object has been assigned to this relationship, Django will raise
a ``DoesNotExist`` exception.
Instances can be assigned to the reverse relationship in the same way as
you would assign the forward relationship::
e.entrydetail = ed
Many-to-many relationships Many-to-many relationships
-------------------------- --------------------------
@ -2053,12 +2095,6 @@ above example, if the ``ManyToManyField`` in ``Entry`` had specified
``related_name='entries'``, then each ``Author`` instance would have an ``related_name='entries'``, then each ``Author`` instance would have an
``entries`` attribute instead of ``entry_set``. ``entries`` attribute instead of ``entry_set``.
One-to-one relationships
------------------------
The semantics of one-to-one relationships will be changing soon, so we don't
recommend you use them.
How are the backward relationships possible? How are the backward relationships possible?
-------------------------------------------- --------------------------------------------

View File

@ -93,6 +93,31 @@ backend. See the `cache documentation`_ for more information.
.. _cache documentation: ../cache/ .. _cache documentation: ../cache/
createsuperuser
---------------
**New in Django development version**
Creates a superuser account (a user who has all permissions). This is
useful if you need to create an initial superuser account but did not
do so during ``syncdb``, or if you need to programmatically generate
superuser accounts for your site(s).
When run interactively, this command will prompt for a password for
the new superuser account. When run non-interactively, no password
will be set, and the superuser account will not be able to log in until
a password has been manually set for it.
The username and e-mail address for the new account can be supplied by
using the ``--username`` and ``--email`` arguments on the command
line. If either of those is not supplied, ``createsuperuser`` will prompt for
it when running interactively.
This command is only available if Django's `authentication system`_
(``django.contrib.auth``) is installed.
.. _authentication system: ../authentication/
dbshell dbshell
------- -------
@ -139,6 +164,22 @@ dumped.
.. _custom manager: ../model-api/#custom-managers .. _custom manager: ../model-api/#custom-managers
--exclude
~~~~~~~~~
**New in Django development version**
Exclude a specific application from the applications whose contents is
output. For example, to specifically exclude the `auth` application from
the output, you would call::
django-admin.py dumpdata --exclude=auth
If you want to exclude multiple applications, use multiple ``--exclude``
directives::
django-admin.py dumpdata --exclude=auth --exclude=contenttype
--format --format
~~~~~~~~ ~~~~~~~~
@ -313,9 +354,9 @@ The ``dumpdata`` command can be used to generate input for ``loaddata``.
Use ``--verbosity`` to specify the amount of notification and debug information Use ``--verbosity`` to specify the amount of notification and debug information
that ``django-admin.py`` should print to the console. that ``django-admin.py`` should print to the console.
* ``0`` means no input. * ``0`` means no output.
* ``1`` means normal input (default). * ``1`` means normal output (default).
* ``2`` means verbose input. * ``2`` means verbose output.
Example usage:: Example usage::
@ -556,9 +597,9 @@ data files.
Use ``--verbosity`` to specify the amount of notification and debug information Use ``--verbosity`` to specify the amount of notification and debug information
that ``django-admin.py`` should print to the console. that ``django-admin.py`` should print to the console.
* ``0`` means no input. * ``0`` means no output.
* ``1`` means normal input (default). * ``1`` means normal output (default).
* ``2`` means verbose input. * ``2`` means verbose output.
Example usage:: Example usage::
@ -592,9 +633,9 @@ is being executed as an unattended, automated script.
Use ``--verbosity`` to specify the amount of notification and debug information Use ``--verbosity`` to specify the amount of notification and debug information
that ``django-admin.py`` should print to the console. that ``django-admin.py`` should print to the console.
* ``0`` means no input. * ``0`` means no output.
* ``1`` means normal input (default). * ``1`` means normal output (default).
* ``2`` means verbose input. * ``2`` means verbose output.
Example usage:: Example usage::
@ -668,9 +709,9 @@ To run on 1.2.3.4:7000 with a ``test`` fixture::
Use ``--verbosity`` to specify the amount of notification and debug information Use ``--verbosity`` to specify the amount of notification and debug information
that ``django-admin.py`` should print to the console. that ``django-admin.py`` should print to the console.
* ``0`` means no input. * ``0`` means no output.
* ``1`` means normal input (default). * ``1`` means normal output (default).
* ``2`` means verbose input. * ``2`` means verbose output.
Example usage:: Example usage::

View File

@ -228,13 +228,14 @@ Short answer: When we're comfortable with Django's APIs, have added all
features that we feel are necessary to earn a "1.0" status, and are ready to features that we feel are necessary to earn a "1.0" status, and are ready to
begin maintaining backwards compatibility. begin maintaining backwards compatibility.
The merging of Django's `magic-removal branch`_ went a long way toward Django The merging of Django's `Queryset Refactor branch`_ went a long way toward Django
1.0. 1.0. Merging the `Newforms Admin branch` will be another important step.
Of course, you should note that `quite a few production sites`_ use Django in Of course, you should note that `quite a few production sites`_ use Django in
its current status. Don't let the lack of a 1.0 turn you off. its current status. Don't let the lack of a 1.0 turn you off.
.. _magic-removal branch: http://code.djangoproject.com/wiki/RemovingTheMagic .. _Queryset Refactor branch: http://code.djangoproject.com/wiki/QuerysetRefactorBranch
.. _Newforms Admin branch: http://code.djangoproject.com/wiki/NewformsAdminBranch
.. _quite a few production sites: http://code.djangoproject.com/wiki/DjangoPoweredSites .. _quite a few production sites: http://code.djangoproject.com/wiki/DjangoPoweredSites
How can I download the Django documentation to read it offline? How can I download the Django documentation to read it offline?
@ -259,7 +260,9 @@ Where can I find Django developers for hire?
Consult our `developers for hire page`_ for a list of Django developers who Consult our `developers for hire page`_ for a list of Django developers who
would be happy to help you. would be happy to help you.
You might also be interested in posting a job to http://www.gypsyjobs.com/ . You might also be interested in posting a job to http://djangogigs.com/ .
If you want to find Django-capable people in your local area, try
http://djangopeople.net/ .
.. _developers for hire page: http://code.djangoproject.com/wiki/DevelopersForHire .. _developers for hire page: http://code.djangoproject.com/wiki/DevelopersForHire
@ -643,6 +646,81 @@ You can also use the Python API. See `creating users`_ for full info.
.. _creating users: ../authentication/#creating-users .. _creating users: ../authentication/#creating-users
Getting help
============
How do I do X? Why doesn't Y work? Where can I go to get help?
--------------------------------------------------------------
If this FAQ doesn't contain an answer to your question, you might want to
try the `django-users mailing list`_. Feel free to ask any question related
to installing, using, or debugging Django.
If you prefer IRC, the `#django IRC channel`_ on the Freenode IRC network is an
active community of helpful individuals who may be able to solve your problem.
.. _`django-users mailing list`: http://groups.google.com/group/django-users
.. _`#django IRC channel`: irc://irc.freenode.net/django
Why hasn't my message appeared on django-users?
-----------------------------------------------
django-users_ has a lot of subscribers. This is good for the community, as
it means many people are available to contribute answers to questions.
Unfortunately, it also means that django-users_ is an attractive target for
spammers.
In order to combat the spam problem, when you join the django-users_ mailing
list, we manually moderate the first message you send to the list. This means
that spammers get caught, but it also means that your first question to the
list might take a little longer to get answered. We apologize for any
inconvenience that this policy may cause.
.. _django-users: http://groups.google.com/group/django-users
Nobody on django-users answered my question! What should I do?
--------------------------------------------------------------
Try making your question more specific, or provide a better example of your
problem.
As with most open-source mailing lists, the folks on django-users_ are
volunteers. If nobody has answered your question, it may be because nobody
knows the answer, it may be because nobody can understand the question, or it
may be that everybody that can help is busy. One thing you might try is to ask
the question on IRC -- visit the `#django IRC channel`_ on the Freenode IRC
network.
You might notice we have a second mailing list, called django-developers_ --
but please don't e-mail support questions to this mailing list. This list is
for discussion of the development of Django itself. Asking a tech support
question there is considered quite impolite.
.. _django-developers: http://groups.google.com/group/django-developers
I think I've found a bug! What should I do?
-------------------------------------------
Detailed instructions on how to handle a potential bug can be found in our
`Guide to contributing to Django`_.
.. _`Guide to contributing to Django`: ../contributing/#reporting-bugs
I think I've found a security problem! What should I do?
--------------------------------------------------------
If you think you've found a security problem with Django, please send a message
to security@djangoproject.com. This is a private list only open to long-time,
highly trusted Django developers, and its archives are not publicly readable.
Due to the sensitive nature of security issues, we ask that if you think you
have found a security problem, *please* don't send a message to one of the
public mailing lists. Django has a `policy for handling security issues`_;
while a defect is outstanding, we would like to minimize any damage that
could be inflicted through public knowledge of that defect.
.. _`policy for handling security issues`: ../contributing/#reporting-security-issues
Contributing code Contributing code
================= =================
@ -652,7 +730,7 @@ How can I get started contributing code to Django?
Thanks for asking! We've written an entire document devoted to this question. Thanks for asking! We've written an entire document devoted to this question.
It's titled `Contributing to Django`_. It's titled `Contributing to Django`_.
.. _Contributing to Django: ../contributing/ .. _`Contributing to Django`: ../contributing/
I submitted a bug fix in the ticket system several weeks ago. Why are you ignoring my patch? I submitted a bug fix in the ticket system several weeks ago. Why are you ignoring my patch?
-------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------
@ -664,6 +742,11 @@ ignored" and "a ticket has not been attended to yet." Django's ticket system
contains hundreds of open tickets, of various degrees of impact on end-user contains hundreds of open tickets, of various degrees of impact on end-user
functionality, and Django's developers have to review and prioritize. functionality, and Django's developers have to review and prioritize.
On top of that: the people who work on Django are all volunteers. As a result,
the amount of time that we have to work on the framework is limited and will
vary from week to week depending on our spare time. If we're busy, we may not
be able to spend as much time on Django as we might want.
Besides, if your feature request stands no chance of inclusion in Django, we Besides, if your feature request stands no chance of inclusion in Django, we
won't ignore it -- we'll just close the ticket. So if your ticket is still won't ignore it -- we'll just close the ticket. So if your ticket is still
open, it doesn't mean we're ignoring you; it just means we haven't had time to open, it doesn't mean we're ignoring you; it just means we haven't had time to

View File

@ -143,11 +143,14 @@ Further resources
* PDFlib_ is another PDF-generation library that has Python bindings. To * PDFlib_ is another PDF-generation library that has Python bindings. To
use it with Django, just use the same concepts explained in this article. use it with Django, just use the same concepts explained in this article.
* `Pisa HTML2PDF`_ is yet another PDF-generation library. Pisa ships with
an example of how to integrate Pisa with Django.
* HTMLdoc_ is a command-line script that can convert HTML to PDF. It * HTMLdoc_ is a command-line script that can convert HTML to PDF. It
doesn't have a Python interface, but you can escape out to the shell doesn't have a Python interface, but you can escape out to the shell
using ``system`` or ``popen`` and retrieve the output in Python. using ``system`` or ``popen`` and retrieve the output in Python.
* `forge_fdf in Python`_ is a library that fills in PDF forms. * `forge_fdf in Python`_ is a library that fills in PDF forms.
.. _PDFlib: http://www.pdflib.org/ .. _PDFlib: http://www.pdflib.org/
.. _`Pisa HTML2PDF`: http://www.htmltopdf.org/
.. _HTMLdoc: http://www.htmldoc.org/ .. _HTMLdoc: http://www.htmldoc.org/
.. _forge_fdf in Python: http://www.accesspdf.com/article.php/20050421092951834 .. _forge_fdf in Python: http://www.accesspdf.com/article.php/20050421092951834

View File

@ -63,6 +63,41 @@ be serialized.
doesn't specify all the fields that are required by a model, the deserializer doesn't specify all the fields that are required by a model, the deserializer
will not be able to save deserialized instances. will not be able to save deserialized instances.
Inherited Models
~~~~~~~~~~~~~~~~
If you have a model that is defined using an `abstract base class`_, you don't
have to do anything special to serialize that model. Just call the serializer
on the object (or objects) that you want to serialize, and the output will be
a complete representation of the serialized object.
However, if you have a model that uses `multi-table inheritance`_, you also
need to serialize all of the base classes for the model. This is because only
the fields that are locally defined on the model will be serialized. For
example, consider the following models::
class Place(models.Model):
name = models.CharField(max_length=50)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField()
If you only serialize the Restaurant model::
data = serializers.serialize('xml', Restaurant.objects.all())
the fields on the serialized output will only contain the `serves_hot_dogs`
attribute. The `name` attribute of the base class will be ignored.
In order to fully serialize your Restaurant instances, you will need to
serialize the Place models as well::
all_objects = list(Restaurant.objects.all()) + list(Place.objects.all())
data = serializers.serialize('xml', all_objects)
.. _abstract base class: http://www.djangoproject.com/documentation/model-api/#abstract-base-classes
.. _multi-table inheritance: http://www.djangoproject.com/documentation/model-api/#multi-table-inheritance
Deserializing data Deserializing data
------------------ ------------------

View File

@ -80,19 +80,24 @@ attribute, which is a dictionary-like object. You can read it and write to it.
It implements the following standard dictionary methods: It implements the following standard dictionary methods:
* ``__getitem__(key)`` * ``__getitem__(key)``
Example: ``fav_color = request.session['fav_color']`` Example: ``fav_color = request.session['fav_color']``
* ``__setitem__(key, value)`` * ``__setitem__(key, value)``
Example: ``request.session['fav_color'] = 'blue'`` Example: ``request.session['fav_color'] = 'blue'``
* ``__delitem__(key)`` * ``__delitem__(key)``
Example: ``del request.session['fav_color']``. This raises ``KeyError`` Example: ``del request.session['fav_color']``. This raises ``KeyError``
if the given ``key`` isn't already in the session. if the given ``key`` isn't already in the session.
* ``__contains__(key)`` * ``__contains__(key)``
Example: ``'fav_color' in request.session`` Example: ``'fav_color' in request.session``
* ``get(key, default=None)`` * ``get(key, default=None)``
Example: ``fav_color = request.session.get('fav_color', 'red')`` Example: ``fav_color = request.session.get('fav_color', 'red')``
* ``keys()`` * ``keys()``
@ -101,23 +106,70 @@ It implements the following standard dictionary methods:
* ``setdefault()`` (**New in Django development version**) * ``setdefault()`` (**New in Django development version**)
It also has these three methods: It also has these methods:
* ``set_test_cookie()`` * ``set_test_cookie()``
Sets a test cookie to determine whether the user's browser supports Sets a test cookie to determine whether the user's browser supports
cookies. Due to the way cookies work, you won't be able to test this cookies. Due to the way cookies work, you won't be able to test this
until the user's next page request. See "Setting test cookies" below for until the user's next page request. See "Setting test cookies" below for
more information. more information.
* ``test_cookie_worked()`` * ``test_cookie_worked()``
Returns either ``True`` or ``False``, depending on whether the user's Returns either ``True`` or ``False``, depending on whether the user's
browser accepted the test cookie. Due to the way cookies work, you'll browser accepted the test cookie. Due to the way cookies work, you'll
have to call ``set_test_cookie()`` on a previous, separate page request. have to call ``set_test_cookie()`` on a previous, separate page request.
See "Setting test cookies" below for more information. See "Setting test cookies" below for more information.
* ``delete_test_cookie()`` * ``delete_test_cookie()``
Deletes the test cookie. Use this to clean up after yourself. Deletes the test cookie. Use this to clean up after yourself.
* ``set_expiry(value)``
**New in Django development version**
Sets the expiration time for the session. You can pass a number of
different values:
* If ``value`` is an integer, the session will expire after that
many seconds of inactivity. For example, calling
``request.session.set_expiry(300)`` would make the session expire
in 5 minutes.
* If ``value`` is a ``datetime`` or ``timedelta`` object, the
session will expire at that specific date/time.
* If ``value`` is ``0``, the user's session cookie will expire
when the user's Web browser is closed.
* If ``value`` is ``None``, the session reverts to using the global
session expiry policy.
* ``get_expiry_age()``
**New in Django development version**
Returns the number of seconds until this session expires. For sessions
with no custom expiration (or those set to expire at browser close), this
will equal ``settings.SESSION_COOKIE_AGE``.
* ``get_expiry_date()``
**New in Django development version**
Returns the date this session will expire. For sessions with no custom
expiration (or those set to expire at browser close), this will equal the
date ``settings.SESSION_COOKIE_AGE`` seconds from now.
* ``get_expire_at_browser_close()``
**New in Django development version**
Returns either ``True`` or ``False``, depending on whether the user's
session cookie will expire when the user's Web browser is closed.
You can edit ``request.session`` at any point in your view. You can edit it You can edit ``request.session`` at any point in your view. You can edit it
multiple times. multiple times.
@ -278,6 +330,12 @@ browser-length cookies -- cookies that expire as soon as the user closes his or
her browser. Use this if you want people to have to log in every time they open her browser. Use this if you want people to have to log in every time they open
a browser. a browser.
**New in Django development version**
This setting is a global default and can be overwritten at a per-session level
by explicitly calling ``request.session.set_expiry()`` as described above in
`using sessions in views`_.
Clearing the session table Clearing the session table
========================== ==========================

View File

@ -394,6 +394,8 @@ site with ``DEBUG`` turned on.
DEBUG_PROPAGATE_EXCEPTIONS DEBUG_PROPAGATE_EXCEPTIONS
-------------------------- --------------------------
**New in Django development version**
Default: ``False`` Default: ``False``
If set to True, Django's normal exception handling of view functions If set to True, Django's normal exception handling of view functions

View File

@ -822,6 +822,10 @@ useful for testing Web applications:
that ``text`` appears in the content of the response. If ``count`` is that ``text`` appears in the content of the response. If ``count`` is
provided, ``text`` must occur exactly ``count`` times in the response. provided, ``text`` must occur exactly ``count`` times in the response.
``assertNotContains(response, text, status_code=200)``
Asserts that a ``Response`` instance produced the given ``status_code`` and
that ``text`` does not appears in the content of the response.
``assertFormError(response, form, field, errors)`` ``assertFormError(response, form, field, errors)``
Asserts that a field on a form raises the provided list of errors when Asserts that a field on a form raises the provided list of errors when
rendered on the form. rendered on the form.
@ -837,6 +841,12 @@ useful for testing Web applications:
``errors`` is an error string, or a list of error strings, that are ``errors`` is an error string, or a list of error strings, that are
expected as a result of form validation. expected as a result of form validation.
``assertTemplateUsed(response, template_name)``
Asserts that the template with the given name was used in rendering the
response.
The name is a string such as ``'admin/index.html'``.
``assertTemplateNotUsed(response, template_name)`` ``assertTemplateNotUsed(response, template_name)``
Asserts that the template with the given name was *not* used in rendering Asserts that the template with the given name was *not* used in rendering
the response. the response.
@ -846,12 +856,6 @@ useful for testing Web applications:
it redirected to ``expected_url`` (including any GET data), and the subsequent it redirected to ``expected_url`` (including any GET data), and the subsequent
page was received with ``target_status_code``. page was received with ``target_status_code``.
``assertTemplateUsed(response, template_name)``
Asserts that the template with the given name was used in rendering the
response.
The name is a string such as ``'admin/index.html'``.
E-mail services E-mail services
--------------- ---------------

View File

@ -401,8 +401,9 @@ True
# The 'select' argument to extra() supports names with dashes in them, as long # The 'select' argument to extra() supports names with dashes in them, as long
# as you use values(). # as you use values().
>>> Article.objects.filter(pub_date__year=2008).extra(select={'dashed-value': '1'}).values('headline', 'dashed-value') >>> dicts = Article.objects.filter(pub_date__year=2008).extra(select={'dashed-value': '1'}).values('headline', 'dashed-value')
[{'headline': u'Article 11', 'dashed-value': 1}, {'headline': u'Article 12', 'dashed-value': 1}] >>> [sorted(d.items()) for d in dicts]
[[('dashed-value', 1), ('headline', u'Article 11')], [('dashed-value', 1), ('headline', u'Article 12')]]
# If you use 'select' with extra() and names containing dashes on a query # If you use 'select' with extra() and names containing dashes on a query
# that's *not* a values() query, those extra 'select' values will silently be # that's *not* a values() query, those extra 'select' values will silently be

View File

@ -39,6 +39,14 @@ __test__ = {'API_TESTS':"""
# Create an Article. # Create an Article.
>>> a1 = Article(id=None, headline='Django lets you build Web apps easily') >>> a1 = Article(id=None, headline='Django lets you build Web apps easily')
# You can't associate it with a Publication until it's been saved.
>>> a1.publications.add(p1)
Traceback (most recent call last):
...
ValueError: 'Article' instance needs to have a primary key value before a many-to-many relationship can be used.
# Save it!
>>> a1.save() >>> a1.save()
# Associate the Article with a Publication. # Associate the Article with a Publication.

View File

@ -175,6 +175,12 @@ False
>>> Article.objects.filter(reporter__in=[r,r2]).distinct() >>> Article.objects.filter(reporter__in=[r,r2]).distinct()
[<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>] [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]
# You can also use a queryset instead of a literal list of instances.
# The queryset must be reduced to a list of values using values(),
# then converted into a query
>>> Article.objects.filter(reporter__in=Reporter.objects.filter(first_name='John').values('pk').query).distinct()
[<Article: John's second story>, <Article: This is a test>]
# You need two underscores between "reporter" and "id" -- not one. # You need two underscores between "reporter" and "id" -- not one.
>>> Article.objects.filter(reporter_id__exact=1) >>> Article.objects.filter(reporter_id__exact=1)
Traceback (most recent call last): Traceback (most recent call last):

View File

@ -147,8 +147,13 @@ Test constructor for Restaurant.
>>> c.save() >>> c.save()
>>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Ash', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True, rating=4, chef=c) >>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Ash', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True, rating=4, chef=c)
>>> ir.save() >>> ir.save()
>>> ItalianRestaurant.objects.filter(address='1234 W. Ash')
[<ItalianRestaurant: Ristorante Miron the italian restaurant>]
>>> ir.address = '1234 W. Elm' >>> ir.address = '1234 W. Elm'
>>> ir.save() >>> ir.save()
>>> ItalianRestaurant.objects.filter(address='1234 W. Elm')
[<ItalianRestaurant: Ristorante Miron the italian restaurant>]
# Make sure Restaurant and ItalianRestaurant have the right fields in the right # Make sure Restaurant and ItalianRestaurant have the right fields in the right
# order. # order.

View File

@ -80,11 +80,8 @@ DoesNotExist: Restaurant matching query does not exist.
>>> r.place >>> r.place
<Place: Ace Hardware the place> <Place: Ace Hardware the place>
# Set the place back again, using assignment in the reverse direction. Need to # Set the place back again, using assignment in the reverse direction.
# reload restaurant object first, because the reverse set can't update the
# existing restaurant instance
>>> p1.restaurant = r >>> p1.restaurant = r
>>> r.save()
>>> p1.restaurant >>> p1.restaurant
<Restaurant: Demon Dogs the restaurant> <Restaurant: Demon Dogs the restaurant>

View File

@ -110,8 +110,9 @@ __test__ = {'API_TESTS':"""
>>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__contains='bye')).count() >>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__contains='bye')).count()
3 3
>>> list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values()) >>> dicts = list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values())
[{'headline': u'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}] >>> [sorted(d.items()) for d in dicts]
[[('headline', u'Hello and goodbye'), ('id', 3), ('pub_date', datetime.datetime(2005, 11, 29, 0, 0))]]
>>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2]) >>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2])
{1: <Article: Hello>} {1: <Article: Hello>}

View File

@ -34,5 +34,23 @@
"email": "testclient@example.com", "email": "testclient@example.com",
"date_joined": "2006-12-17 07:03:31" "date_joined": "2006-12-17 07:03:31"
} }
},
{
"pk": "3",
"model": "auth.user",
"fields": {
"username": "staff",
"first_name": "Staff",
"last_name": "Member",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"last_login": "2006-12-17 07:03:31",
"groups": [],
"user_permissions": [],
"password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
"email": "testclient@example.com",
"date_joined": "2006-12-17 07:03:31"
}
} }
] ]

View File

@ -63,5 +63,12 @@ a manager method.
>>> DataPoint.objects.values('value').distinct() >>> DataPoint.objects.values('value').distinct()
[{'value': u'thing'}] [{'value': u'thing'}]
We do not support update on already sliced query sets.
>>> DataPoint.objects.all()[:2].update(another_value='another thing')
Traceback (most recent call last):
...
AssertionError: Cannot update a query once a slice has been taken.
""" """
} }

View File

@ -226,15 +226,17 @@ u'some <b>html</b> with alert("You smell") disallowed tags'
>>> striptags(u'some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags') >>> striptags(u'some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags')
u'some html with alert("You smell") disallowed tags' u'some html with alert("You smell") disallowed tags'
>>> dictsort([{'age': 23, 'name': 'Barbara-Ann'}, >>> sorted_dicts = dictsort([{'age': 23, 'name': 'Barbara-Ann'},
... {'age': 63, 'name': 'Ra Ra Rasputin'}, ... {'age': 63, 'name': 'Ra Ra Rasputin'},
... {'name': 'Jonny B Goode', 'age': 18}], 'age') ... {'name': 'Jonny B Goode', 'age': 18}], 'age')
[{'age': 18, 'name': 'Jonny B Goode'}, {'age': 23, 'name': 'Barbara-Ann'}, {'age': 63, 'name': 'Ra Ra Rasputin'}] >>> [sorted(dict.items()) for dict in sorted_dicts]
[[('age', 18), ('name', 'Jonny B Goode')], [('age', 23), ('name', 'Barbara-Ann')], [('age', 63), ('name', 'Ra Ra Rasputin')]]
>>> dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'}, >>> sorted_dicts = dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'},
... {'age': 63, 'name': 'Ra Ra Rasputin'}, ... {'age': 63, 'name': 'Ra Ra Rasputin'},
... {'name': 'Jonny B Goode', 'age': 18}], 'age') ... {'name': 'Jonny B Goode', 'age': 18}], 'age')
[{'age': 63, 'name': 'Ra Ra Rasputin'}, {'age': 23, 'name': 'Barbara-Ann'}, {'age': 18, 'name': 'Jonny B Goode'}] >>> [sorted(dict.items()) for dict in sorted_dicts]
[[('age', 63), ('name', 'Ra Ra Rasputin')], [('age', 23), ('name', 'Barbara-Ann')], [('age', 18), ('name', 'Jonny B Goode')]]
>>> first([0,1,2]) >>> first([0,1,2])
0 0

View File

@ -0,0 +1 @@
This data shouldn't load, as it's of an unknown file format.

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<django-objcts version="1.0">
<objct pk="2" model="fixtures.article">
<field type="CharField" name="headline">Poker on TV is great!</field>
<field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field>
</objct>
</django-objcts>

View File

@ -71,4 +71,27 @@ __test__ = {'API_TESTS':"""
>>> Absolute.load_count >>> Absolute.load_count
1 1
###############################################
# Test for ticket #4371 -- fixture loading fails silently in testcases
# Validate that error conditions are caught correctly
# redirect stderr for the next few tests...
>>> import sys
>>> savestderr = sys.stderr
>>> sys.stderr = sys.stdout
# Loading data of an unknown format should fail
>>> management.call_command('loaddata', 'bad_fixture1.unkn', verbosity=0)
Problem installing fixture 'bad_fixture1': unkn is not a known serialization format.
# Loading a fixture file with invalid data using explicit filename
>>> management.call_command('loaddata', 'bad_fixture2.xml', verbosity=0)
No fixture data found for 'bad_fixture2'. (File format may be invalid.)
# Loading a fixture file with invalid data without file extension
>>> management.call_command('loaddata', 'bad_fixture2', verbosity=0)
No fixture data found for 'bad_fixture2'. (File format may be invalid.)
>>> sys.stderr = savestderr
"""} """}

View File

@ -1,3 +1,7 @@
"""
Regression tests for a few FK bugs: #1578, #6886
"""
from django.db import models from django.db import models
# If ticket #1578 ever slips back in, these models will not be able to be # If ticket #1578 ever slips back in, these models will not be able to be
@ -25,10 +29,48 @@ class Child(models.Model):
__test__ = {'API_TESTS':""" __test__ = {'API_TESTS':"""
>>> Third.AddManipulator().save(dict(id='3', name='An example', another=None)) >>> Third.objects.create(id='3', name='An example')
<Third: Third object> <Third: Third object>
>>> parent = Parent(name = 'fred') >>> parent = Parent(name = 'fred')
>>> parent.save() >>> parent.save()
>>> Child.AddManipulator().save(dict(name='bam-bam', parent=parent.id)) >>> Child.objects.create(name='bam-bam', parent=parent)
<Child: Child object> <Child: Child object>
#
# Tests of ForeignKey assignment and the related-object cache (see #6886)
#
>>> p = Parent.objects.create(name="Parent")
>>> c = Child.objects.create(name="Child", parent=p)
# Look up the object again so that we get a "fresh" object
>>> c = Child.objects.get(name="Child")
>>> p = c.parent
# Accessing the related object again returns the exactly same object
>>> c.parent is p
True
# But if we kill the cache, we get a new object
>>> del c._parent_cache
>>> c.parent is p
False
# Assigning a new object results in that object getting cached immediately
>>> p2 = Parent.objects.create(name="Parent 2")
>>> c.parent = p2
>>> c.parent is p2
True
# Assigning None fails: Child.parent is null=False
>>> c.parent = None
Traceback (most recent call last):
...
ValueError: Cannot assign None: "Child.parent" does not allow null values.
# You also can't assign an object of the wrong type here
>>> c.parent = First(id=1, second=1)
Traceback (most recent call last):
...
ValueError: Cannot assign "<First: First object>": "Child.parent" must be a "Parent" instance.
"""} """}

View File

@ -0,0 +1,120 @@
"""
Regression tests for Model inheritance behaviour.
"""
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Meta:
ordering = ('name',)
def __unicode__(self):
return u"%s the place" % self.name
class Restaurant(Place):
serves_hot_dogs = models.BooleanField()
serves_pizza = models.BooleanField()
def __unicode__(self):
return u"%s the restaurant" % self.name
class ItalianRestaurant(Restaurant):
serves_gnocchi = models.BooleanField()
def __unicode__(self):
return u"%s the italian restaurant" % self.name
class ParkingLot(Place):
# An explicit link to the parent (we can control the attribute name).
parent = models.OneToOneField(Place, primary_key=True, parent_link=True)
capacity = models.IntegerField()
def __unicode__(self):
return u"%s the parking lot" % self.name
__test__ = {'API_TESTS':"""
# Regression for #7350, #7202
# Check that when you create a Parent object with a specific reference to an existent
# child instance, saving the Parent doesn't duplicate the child.
# This behaviour is only activated during a raw save - it is mostly relevant to
# deserialization, but any sort of CORBA style 'narrow()' API would require a
# similar approach.
# Create a child-parent-grandparent chain
>>> place1 = Place(name="Guido's House of Pasta", address='944 W. Fullerton')
>>> place1.save_base(raw=True)
>>> restaurant = Restaurant(place_ptr=place1, serves_hot_dogs=True, serves_pizza=False)
>>> restaurant.save_base(raw=True)
>>> italian_restaurant = ItalianRestaurant(restaurant_ptr=restaurant, serves_gnocchi=True)
>>> italian_restaurant.save_base(raw=True)
# Create a child-parent chain with an explicit parent link
>>> place2 = Place(name='Main St', address='111 Main St')
>>> place2.save_base(raw=True)
>>> park = ParkingLot(parent=place2, capacity=100)
>>> park.save_base(raw=True)
# Check that no extra parent objects have been created.
>>> Place.objects.all()
[<Place: Guido's House of Pasta the place>, <Place: Main St the place>]
>>> dicts = Restaurant.objects.values('name','serves_hot_dogs')
>>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's House of Pasta"), ('serves_hot_dogs', True)]]
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
>>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's House of Pasta"), ('serves_gnocchi', True), ('serves_hot_dogs', True)]]
>>> dicts = ParkingLot.objects.values('name','capacity')
>>> [sorted(d.items()) for d in dicts]
[[('capacity', 100), ('name', u'Main St')]]
# You can also update objects when using a raw save.
>>> place1.name = "Guido's All New House of Pasta"
>>> place1.save_base(raw=True)
>>> restaurant.serves_hot_dogs = False
>>> restaurant.save_base(raw=True)
>>> italian_restaurant.serves_gnocchi = False
>>> italian_restaurant.save_base(raw=True)
>>> place2.name='Derelict lot'
>>> place2.save_base(raw=True)
>>> park.capacity = 50
>>> park.save_base(raw=True)
# No extra parent objects after an update, either.
>>> Place.objects.all()
[<Place: Derelict lot the place>, <Place: Guido's All New House of Pasta the place>]
>>> dicts = Restaurant.objects.values('name','serves_hot_dogs')
>>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's All New House of Pasta"), ('serves_hot_dogs', False)]]
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
>>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
>>> dicts = ParkingLot.objects.values('name','capacity')
>>> [sorted(d.items()) for d in dicts]
[[('capacity', 50), ('name', u'Derelict lot')]]
# If you try to raw_save a parent attribute onto a child object,
# the attribute will be ignored.
>>> italian_restaurant.name = "Lorenzo's Pasta Hut"
>>> italian_restaurant.save_base(raw=True)
# Note that the name has not changed
# - name is an attribute of Place, not ItalianRestaurant
>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
>>> [sorted(d.items()) for d in dicts]
[[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
"""}

View File

@ -0,0 +1,55 @@
"""
Regression tests for proper working of ForeignKey(null=True). Tests these bugs:
* #7369: FK non-null after null relationship on select_related() generates an invalid query
"""
from django.db import models
class SystemInfo(models.Model):
system_name = models.CharField(max_length=32)
class Forum(models.Model):
system_info = models.ForeignKey(SystemInfo)
forum_name = models.CharField(max_length=32)
class Post(models.Model):
forum = models.ForeignKey(Forum, null=True)
title = models.CharField(max_length=32)
def __unicode__(self):
return self.title
class Comment(models.Model):
post = models.ForeignKey(Post, null=True)
comment_text = models.CharField(max_length=250)
def __unicode__(self):
return self.comment_text
__test__ = {'API_TESTS':"""
>>> s = SystemInfo.objects.create(system_name='First forum')
>>> f = Forum.objects.create(system_info=s, forum_name='First forum')
>>> p = Post.objects.create(forum=f, title='First Post')
>>> c1 = Comment.objects.create(post=p, comment_text='My first comment')
>>> c2 = Comment.objects.create(comment_text='My second comment')
# Starting from comment, make sure that a .select_related(...) with a specified
# set of fields will properly LEFT JOIN multiple levels of NULLs (and the things
# that come after the NULLs, or else data that should exist won't).
>>> c = Comment.objects.select_related().get(id=1)
>>> c.post
<Post: First Post>
>>> c = Comment.objects.select_related().get(id=2)
>>> print c.post
None
>>> comments = Comment.objects.select_related('post__forum__system_info').all()
>>> [(c.id, c.post.id) for c in comments]
[(1, 1), (2, None)]
>>> [(c.comment_text, c.post.title) for c in comments]
[(u'My first comment', u'First Post'), (u'My second comment', None)]
"""}

View File

@ -50,4 +50,42 @@ __test__ = {'API_TESTS':"""
<Restaurant: Demon Dogs the restaurant> <Restaurant: Demon Dogs the restaurant>
>>> p1.bar >>> p1.bar
<Bar: Demon Dogs the bar> <Bar: Demon Dogs the bar>
#
# Regression test for #6886 (the related-object cache)
#
# Look up the objects again so that we get "fresh" objects
>>> p = Place.objects.get(name="Demon Dogs")
>>> r = p.restaurant
# Accessing the related object again returns the exactly same object
>>> p.restaurant is r
True
# But if we kill the cache, we get a new object
>>> del p._restaurant_cache
>>> p.restaurant is r
False
# Reassigning the Restaurant object results in an immediate cache update
# We can't use a new Restaurant because that'll violate one-to-one, but
# with a new *instance* the is test below will fail if #6886 regresses.
>>> r2 = Restaurant.objects.get(pk=r.pk)
>>> p.restaurant = r2
>>> p.restaurant is r2
True
# Assigning None fails: Place.restaurant is null=False
>>> p.restaurant = None
Traceback (most recent call last):
...
ValueError: Cannot assign None: "Place.restaurant" does not allow null values.
# You also can't assign an object of the wrong type here
>>> p.restaurant = p
Traceback (most recent call last):
...
ValueError: Cannot assign "<Place: Demon Dogs the place>": "Place.restaurant" must be a "Restaurant" instance.
"""} """}

View File

@ -503,8 +503,15 @@ True
# Despite having some extra aliases in the query, we can still omit them in a # Despite having some extra aliases in the query, we can still omit them in a
# values() query. # values() query.
>>> qs.values('id', 'rank').order_by('id') >>> dicts = qs.values('id', 'rank').order_by('id')
[{'id': 1, 'rank': 2}, {'id': 2, 'rank': 1}, {'id': 3, 'rank': 3}] >>> [sorted(d.items()) for d in dicts]
[[('id', 1), ('rank', 2)], [('id', 2), ('rank', 1)], [('id', 3), ('rank', 3)]]
Bug #7256
# An empty values() call includes all aliases, including those from an extra()
>>> dicts = qs.values().order_by('id')
>>> [sorted(d.items()) for d in dicts]
[[('author_id', 2), ('good', 0), ('id', 1), ('rank', 2)], [('author_id', 3), ('good', 0), ('id', 2), ('rank', 1)], [('author_id', 1), ('good', 1), ('id', 3), ('rank', 3)]]
Bugs #2874, #3002 Bugs #2874, #3002
>>> qs = Item.objects.select_related().order_by('note__note', 'name') >>> qs = Item.objects.select_related().order_by('note__note', 'name')

View File

@ -223,3 +223,23 @@ class ModifyingSaveData(models.Model):
"A save method that modifies the data in the object" "A save method that modifies the data in the object"
self.data = 666 self.data = 666
super(ModifyingSaveData, self).save(raw) super(ModifyingSaveData, self).save(raw)
# Tests for serialization of models using inheritance.
# Regression for #7202, #7350
class AbstractBaseModel(models.Model):
parent_data = models.IntegerField()
class Meta:
abstract = True
class InheritAbstractModel(AbstractBaseModel):
child_data = models.IntegerField()
class BaseModel(models.Model):
parent_data = models.IntegerField()
class InheritBaseModel(BaseModel):
child_data = models.IntegerField()
class ExplicitInheritBaseModel(BaseModel):
parent = models.OneToOneField(BaseModel)
child_data = models.IntegerField()

View File

@ -32,7 +32,7 @@ def data_create(pk, klass, data):
instance = klass(id=pk) instance = klass(id=pk)
instance.data = data instance.data = data
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
return instance return [instance]
def generic_create(pk, klass, data): def generic_create(pk, klass, data):
instance = klass(id=pk) instance = klass(id=pk)
@ -40,31 +40,44 @@ def generic_create(pk, klass, data):
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
for tag in data[1:]: for tag in data[1:]:
instance.tags.create(data=tag) instance.tags.create(data=tag)
return instance return [instance]
def fk_create(pk, klass, data): def fk_create(pk, klass, data):
instance = klass(id=pk) instance = klass(id=pk)
setattr(instance, 'data_id', data) setattr(instance, 'data_id', data)
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
return instance return [instance]
def m2m_create(pk, klass, data): def m2m_create(pk, klass, data):
instance = klass(id=pk) instance = klass(id=pk)
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
instance.data = data instance.data = data
return instance return [instance]
def o2o_create(pk, klass, data): def o2o_create(pk, klass, data):
instance = klass() instance = klass()
instance.data_id = data instance.data_id = data
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
return instance return [instance]
def pk_create(pk, klass, data): def pk_create(pk, klass, data):
instance = klass() instance = klass()
instance.data = data instance.data = data
models.Model.save_base(instance, raw=True) models.Model.save_base(instance, raw=True)
return instance return [instance]
def inherited_create(pk, klass, data):
instance = klass(id=pk,**data)
# This isn't a raw save because:
# 1) we're testing inheritance, not field behaviour, so none
# of the field values need to be protected.
# 2) saving the child class and having the parent created
# automatically is easier than manually creating both.
models.Model.save(instance)
created = [instance]
for klass,field in instance._meta.parents.items():
created.append(klass.objects.get(id=pk))
return created
# A set of functions that can be used to compare # A set of functions that can be used to compare
# test data objects of various kinds # test data objects of various kinds
@ -94,6 +107,11 @@ def pk_compare(testcase, pk, klass, data):
instance = klass.objects.get(data=data) instance = klass.objects.get(data=data)
testcase.assertEqual(data, instance.data) testcase.assertEqual(data, instance.data)
def inherited_compare(testcase, pk, klass, data):
instance = klass.objects.get(id=pk)
for key,value in data.items():
testcase.assertEqual(value, getattr(instance,key))
# Define some data types. Each data type is # Define some data types. Each data type is
# actually a pair of functions; one to create # actually a pair of functions; one to create
# and one to compare objects of that type # and one to compare objects of that type
@ -103,6 +121,7 @@ fk_obj = (fk_create, fk_compare)
m2m_obj = (m2m_create, m2m_compare) m2m_obj = (m2m_create, m2m_compare)
o2o_obj = (o2o_create, o2o_compare) o2o_obj = (o2o_create, o2o_compare)
pk_obj = (pk_create, pk_compare) pk_obj = (pk_create, pk_compare)
inherited_obj = (inherited_create, inherited_compare)
test_data = [ test_data = [
# Format: (data type, PK value, Model Class, data) # Format: (data type, PK value, Model Class, data)
@ -255,6 +274,10 @@ The end."""),
(data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)), (data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)),
(data_obj, 810, ModifyingSaveData, 42), (data_obj, 810, ModifyingSaveData, 42),
(inherited_obj, 900, InheritAbstractModel, {'child_data':37,'parent_data':42}),
(inherited_obj, 910, ExplicitInheritBaseModel, {'child_data':37,'parent_data':42}),
(inherited_obj, 920, InheritBaseModel, {'child_data':37,'parent_data':42}),
] ]
# Because Oracle treats the empty string as NULL, Oracle is expected to fail # Because Oracle treats the empty string as NULL, Oracle is expected to fail
@ -277,13 +300,19 @@ def serializerTest(format, self):
# Create all the objects defined in the test data # Create all the objects defined in the test data
objects = [] objects = []
instance_count = {}
transaction.enter_transaction_management() transaction.enter_transaction_management()
transaction.managed(True) transaction.managed(True)
for (func, pk, klass, datum) in test_data: for (func, pk, klass, datum) in test_data:
objects.append(func[0](pk, klass, datum)) objects.extend(func[0](pk, klass, datum))
instance_count[klass] = 0
transaction.commit() transaction.commit()
transaction.leave_transaction_management() transaction.leave_transaction_management()
# Get a count of the number of objects created for each class
for klass in instance_count:
instance_count[klass] = klass.objects.count()
# Add the generic tagged objects to the object list # Add the generic tagged objects to the object list
objects.extend(Tag.objects.all()) objects.extend(Tag.objects.all())
@ -304,6 +333,11 @@ def serializerTest(format, self):
for (func, pk, klass, datum) in test_data: for (func, pk, klass, datum) in test_data:
func[1](self, pk, klass, datum) func[1](self, pk, klass, datum)
# Assert that the number of objects deserialized is the
# same as the number that was serialized.
for klass, count in instance_count.items():
self.assertEquals(count, klass.objects.count())
def fieldsTest(format, self): def fieldsTest(format, self):
# Clear the database first # Clear the database first
management.call_command('flush', verbosity=0, interactive=False) management.call_command('flush', verbosity=0, interactive=False)

View File

@ -16,5 +16,41 @@
"email": "testclient@example.com", "email": "testclient@example.com",
"date_joined": "2006-12-17 07:03:31" "date_joined": "2006-12-17 07:03:31"
} }
},
{
"pk": "2",
"model": "auth.user",
"fields": {
"username": "inactive",
"first_name": "Inactive",
"last_name": "User",
"is_active": false,
"is_superuser": false,
"is_staff": false,
"last_login": "2006-12-17 07:03:31",
"groups": [],
"user_permissions": [],
"password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
"email": "testclient@example.com",
"date_joined": "2006-12-17 07:03:31"
}
},
{
"pk": "3",
"model": "auth.user",
"fields": {
"username": "staff",
"first_name": "Staff",
"last_name": "Member",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"last_login": "2006-12-17 07:03:31",
"groups": [],
"user_permissions": [],
"password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
"email": "testclient@example.com",
"date_joined": "2006-12-17 07:03:31"
}
} }
] ]

View File

@ -4,6 +4,7 @@ Regression tests for the Test Client, especially the customized assertions.
""" """
from django.test import Client, TestCase from django.test import Client, TestCase
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import SuspiciousOperation
import os import os
class AssertContainsTests(TestCase): class AssertContainsTests(TestCase):
@ -11,12 +12,18 @@ class AssertContainsTests(TestCase):
"Responses can be inspected for content, including counting repeated substrings" "Responses can be inspected for content, including counting repeated substrings"
response = self.client.get('/test_client_regress/no_template_view/') response = self.client.get('/test_client_regress/no_template_view/')
self.assertNotContains(response, 'never')
self.assertContains(response, 'never', 0) self.assertContains(response, 'never', 0)
self.assertContains(response, 'once') self.assertContains(response, 'once')
self.assertContains(response, 'once', 1) self.assertContains(response, 'once', 1)
self.assertContains(response, 'twice') self.assertContains(response, 'twice')
self.assertContains(response, 'twice', 2) self.assertContains(response, 'twice', 2)
try:
self.assertNotContains(response, 'once')
except AssertionError, e:
self.assertEquals(str(e), "Response should not contain 'once'")
try: try:
self.assertContains(response, 'never', 1) self.assertContains(response, 'never', 1)
except AssertionError, e: except AssertionError, e:
@ -288,4 +295,26 @@ class URLEscapingTests(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, 'Hi, Arthur') self.assertEqual(response.content, 'Hi, Arthur')
class ExceptionTests(TestCase):
fixtures = ['testdata.json']
def test_exception_cleared(self):
"#5836 - A stale user exception isn't re-raised by the test client."
login = self.client.login(username='testclient',password='password')
self.failUnless(login, 'Could not log in')
try:
response = self.client.get("/test_client_regress/staff_only/")
self.fail("General users should not be able to visit this page")
except SuspiciousOperation:
pass
# At this point, an exception has been raised, and should be cleared.
# This next operation should be successful; if it isn't we have a problem.
login = self.client.login(username='staff', password='password')
self.failUnless(login, 'Could not log in')
try:
self.client.get("/test_client_regress/staff_only/")
except SuspiciousOperation:
self.fail("Staff should be able to visit this page")

View File

@ -4,6 +4,7 @@ import views
urlpatterns = patterns('', urlpatterns = patterns('',
(r'^no_template_view/$', views.no_template_view), (r'^no_template_view/$', views.no_template_view),
(r'^file_upload/$', views.file_upload_view), (r'^file_upload/$', views.file_upload_view),
(r'^staff_only/$', views.staff_only_view),
(r'^get_view/$', views.get_view), (r'^get_view/$', views.get_view),
url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'), url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
(r'^login_protected_redirect_view/$', views.login_protected_redirect_view) (r'^login_protected_redirect_view/$', views.login_protected_redirect_view)

View File

@ -1,5 +1,8 @@
import os
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError
from django.core.exceptions import SuspiciousOperation
def no_template_view(request): def no_template_view(request):
"A simple view that expects a GET request, and returns a rendered template" "A simple view that expects a GET request, and returns a rendered template"
@ -13,10 +16,21 @@ def file_upload_view(request):
form_data = request.POST.copy() form_data = request.POST.copy()
form_data.update(request.FILES) form_data.update(request.FILES)
if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode): if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode):
# If a file is posted, the dummy client should only post the file name,
# not the full path.
if os.path.dirname(form_data['file_field']['filename']) != '':
return HttpResponseServerError()
return HttpResponse('') return HttpResponse('')
else: else:
return HttpResponseServerError() return HttpResponseServerError()
def staff_only_view(request):
"A view that can only be visited by staff. Non staff members get an exception"
if request.user.is_staff:
return HttpResponse('')
else:
raise SuspiciousOperation()
def get_view(request): def get_view(request):
"A simple login protected view" "A simple login protected view"
return HttpResponse("Hello world") return HttpResponse("Hello world")

View File

@ -118,7 +118,6 @@ def django_tests(verbosity, interactive, test_labels):
get_apps() get_apps()
# Load all the test model apps. # Load all the test model apps.
test_models = []
for model_dir, model_name in get_test_models(): for model_dir, model_name in get_test_models():
model_label = '.'.join([model_dir, model_name]) model_label = '.'.join([model_dir, model_name])
try: try:
@ -142,6 +141,12 @@ def django_tests(verbosity, interactive, test_labels):
model_label = '.'.join([model_dir, model_name]) model_label = '.'.join([model_dir, model_name])
if not test_labels or model_name in test_labels: if not test_labels or model_name in test_labels:
extra_tests.append(InvalidModelTestCase(model_label)) extra_tests.append(InvalidModelTestCase(model_label))
try:
# Invalid models are not working apps, so we cannot pass them into
# the test runner with the other test_labels
test_labels.remove(model_name)
except ValueError:
pass
# Run the test suite, including the extra validation tests. # Run the test suite, including the extra validation tests.
from django.test.simple import run_tests from django.test.simple import run_tests