diff options
| author | Justin Bronn <jbronn@gmail.com> | 2008-06-16 00:34:17 +0000 |
|---|---|---|
| committer | Justin Bronn <jbronn@gmail.com> | 2008-06-16 00:34:17 +0000 |
| commit | 842dae0ed59cc7566cf35090093222ea3a61ec79 (patch) | |
| tree | 9602bc18a8d3a7da81b2009ba8e7d51276bd1841 /django | |
| parent | 4ec80c4333b618fc1ef2a02c7b8ca8719792f25f (diff) | |
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
Diffstat (limited to 'django')
31 files changed, 658 insertions, 352 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 8eea31b0db..9f24f0c2df 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -11,9 +11,10 @@ gettext_noop = lambda s: s 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, -# and should never be used on a live site. + +# Whether the framework should propagate raw exceptions rather than catching +# them. This is useful under some testing siutations and should never be used +# on a live site. DEBUG_PROPAGATE_EXCEPTIONS = False # 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_PATH = '/' # The path of the session cookie. 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_FILE_PATH = None # Directory to store session files if using the file session module. If None, the backend will use a sensible default. diff --git a/django/conf/locale/es/LC_MESSAGES/django.mo b/django/conf/locale/es/LC_MESSAGES/django.mo Binary files differindex 9437a3b483..f07a280c14 100644 --- a/django/conf/locale/es/LC_MESSAGES/django.mo +++ b/django/conf/locale/es/LC_MESSAGES/django.mo diff --git a/django/conf/locale/es/LC_MESSAGES/django.po b/django/conf/locale/es/LC_MESSAGES/django.po index 1bb9491696..db86ac3b53 100644 --- a/django/conf/locale/es/LC_MESSAGES/django.po +++ b/django/conf/locale/es/LC_MESSAGES/django.po @@ -5,201 +5,200 @@ msgid "" msgstr "" "Project-Id-Version: Django\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" -"Last-Translator: Django Spanish Group <django-i18n@googlegroups.com>\n" -"Language-Team: Spanish <django-i18n@googlegroups.com>\n" +"Last-Translator: Django Spanish Translation Team <django-cat@googlegroups.com>\n" +"Language-Team: Django Spanish Translation Team <django-cat@googlegroups.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: conf/global_settings.py:43 +#: conf/global_settings.py:44 msgid "Arabic" msgstr "Árabe" -#: conf/global_settings.py:44 +#: conf/global_settings.py:45 msgid "Bengali" msgstr "Bengalí" -#: conf/global_settings.py:45 +#: conf/global_settings.py:46 msgid "Bulgarian" msgstr "Búlgaro" -#: conf/global_settings.py:46 +#: conf/global_settings.py:47 msgid "Catalan" msgstr "Catalán" -#: conf/global_settings.py:47 +#: conf/global_settings.py:48 msgid "Czech" msgstr "Checo" -#: conf/global_settings.py:48 +#: conf/global_settings.py:49 msgid "Welsh" msgstr "Galés" -#: conf/global_settings.py:49 +#: conf/global_settings.py:50 msgid "Danish" msgstr "Danés" -#: conf/global_settings.py:50 +#: conf/global_settings.py:51 msgid "German" msgstr "Alemán" -#: conf/global_settings.py:51 +#: conf/global_settings.py:52 msgid "Greek" msgstr "Griego" -#: conf/global_settings.py:52 +#: conf/global_settings.py:53 msgid "English" msgstr "Inglés" -#: conf/global_settings.py:53 +#: conf/global_settings.py:54 msgid "Spanish" msgstr "Español" -#: conf/global_settings.py:54 +#: conf/global_settings.py:55 msgid "Argentinean Spanish" msgstr "Español Argentino" -#: conf/global_settings.py:55 +#: conf/global_settings.py:56 msgid "Basque" msgstr "Vasco" -#: conf/global_settings.py:56 +#: conf/global_settings.py:57 msgid "Persian" msgstr "Persa" -#: conf/global_settings.py:57 +#: conf/global_settings.py:58 msgid "Finnish" msgstr "Finés" -#: conf/global_settings.py:58 +#: conf/global_settings.py:59 msgid "French" msgstr "Francés" -#: conf/global_settings.py:59 +#: conf/global_settings.py:60 msgid "Irish" msgstr "Irlandés" -#: conf/global_settings.py:60 +#: conf/global_settings.py:61 msgid "Galician" msgstr "Gallego" -#: conf/global_settings.py:61 +#: conf/global_settings.py:62 msgid "Hungarian" msgstr "Húngaro" -#: conf/global_settings.py:62 +#: conf/global_settings.py:63 msgid "Hebrew" msgstr "Hebreo" -#: conf/global_settings.py:63 +#: conf/global_settings.py:64 msgid "Croatian" msgstr "Croata" -#: conf/global_settings.py:64 +#: conf/global_settings.py:65 msgid "Icelandic" msgstr "Islandés" -#: conf/global_settings.py:65 +#: conf/global_settings.py:66 msgid "Italian" msgstr "Italiano" -#: conf/global_settings.py:66 +#: conf/global_settings.py:67 msgid "Japanese" msgstr "Japonés" -#: conf/global_settings.py:67 +#: conf/global_settings.py:68 msgid "Georgian" msgstr "Georgiano" -#: conf/global_settings.py:68 +#: conf/global_settings.py:69 msgid "Korean" msgstr "Koreano" -#: conf/global_settings.py:69 +#: conf/global_settings.py:70 msgid "Khmer" msgstr "Khmer" -#: conf/global_settings.py:70 +#: conf/global_settings.py:71 msgid "Kannada" msgstr "Kannada" -#: conf/global_settings.py:71 +#: conf/global_settings.py:72 msgid "Latvian" msgstr "Latvio" -#: conf/global_settings.py:72 +#: conf/global_settings.py:73 msgid "Macedonian" msgstr "Macedonio" -#: conf/global_settings.py:73 +#: conf/global_settings.py:74 msgid "Dutch" -msgstr "Alemán" +msgstr "Holandés" -#: conf/global_settings.py:74 +#: conf/global_settings.py:75 msgid "Norwegian" msgstr "Noruego" -#: conf/global_settings.py:75 +#: conf/global_settings.py:76 msgid "Polish" msgstr "Polaco" -#: conf/global_settings.py:76 +#: conf/global_settings.py:77 msgid "Portugese" msgstr "Portugés" -#: conf/global_settings.py:77 -#, fuzzy +#: conf/global_settings.py:78 msgid "Brazilian Portuguese" -msgstr "Portugés" +msgstr "Portugés Brasileño" -#: conf/global_settings.py:78 +#: conf/global_settings.py:79 msgid "Romanian" msgstr "Rumano" -#: conf/global_settings.py:79 +#: conf/global_settings.py:80 msgid "Russian" msgstr "Ruso" -#: conf/global_settings.py:80 +#: conf/global_settings.py:81 msgid "Slovak" msgstr "Eslovaco" -#: conf/global_settings.py:81 +#: conf/global_settings.py:82 msgid "Slovenian" msgstr "Esloveno" -#: conf/global_settings.py:82 +#: conf/global_settings.py:83 msgid "Serbian" msgstr "Serbio" -#: conf/global_settings.py:83 +#: conf/global_settings.py:84 msgid "Swedish" msgstr "Sueco" -#: conf/global_settings.py:84 +#: conf/global_settings.py:85 msgid "Tamil" msgstr "Tamil" -#: conf/global_settings.py:85 +#: conf/global_settings.py:86 msgid "Telugu" msgstr "Telugu" -#: conf/global_settings.py:86 +#: conf/global_settings.py:87 msgid "Turkish" msgstr "Turco" -#: conf/global_settings.py:87 +#: conf/global_settings.py:88 msgid "Ukrainian" msgstr "Ucraniano" -#: conf/global_settings.py:88 +#: conf/global_settings.py:89 msgid "Simplified Chinese" msgstr "Chino simplificado" -#: conf/global_settings.py:89 +#: conf/global_settings.py:90 msgid "Traditional Chinese" msgstr "Chino tradicional" @@ -329,7 +328,7 @@ msgstr "" #: contrib/admin/templates/admin/base.html:26 msgid "Welcome," -msgstr "Bienvenido," +msgstr "Bienvenido/a," #: contrib/admin/templates/admin/base.html:28 #: 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/index.html:28 -msgid "Add" +msgid "Añadir" msgstr "Agregar" #: contrib/admin/templates/admin/change_form.html:20 @@ -387,7 +386,7 @@ msgstr "Orden:" #: contrib/admin/templates/admin/change_list.html:11 #, python-format 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/submit_line.html:3 @@ -463,7 +462,7 @@ msgid "" "database tables have been created, and make sure the database is readable by " "the appropriate user." 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 " "usuario apropiado." @@ -476,12 +475,12 @@ msgstr "Usuario:" #: contrib/admin/templates/admin/login.html:20 #: contrib/comments/templates/comments/form.html:8 msgid "Password:" -msgstr "Clave:" +msgstr "Contraseña:" #: contrib/admin/templates/admin/login.html:25 #: contrib/admin/views/decorators.py:31 msgid "Log in" -msgstr "Identificarse" +msgstr "Iniciar sesión" #: contrib/admin/templates/admin/object_history.html:17 msgid "Date/time" @@ -509,11 +508,11 @@ msgstr "" #: contrib/admin/templates/admin/pagination.html:10 msgid "Show all" -msgstr "Mostrarlo todo" +msgstr "Mostrar todo" #: contrib/admin/templates/admin/search_form.html:8 msgid "Go" -msgstr "Buscar" +msgstr "Ir" #: contrib/admin/templates/admin/search_form.html:10 #, 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/change_password.html:39 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 #, python-format @@ -599,22 +598,22 @@ msgstr "" "<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 " "favoritos.\n" -"Ahora puede escoger el bookmarklet desde cualquier página en el sitio.\n" -"Observer que algunos de estos bookmarklets precisan que esté viendo\n" -"el sitio desde un computador señalado como \"interno\" (hable\n" -"con su administrador de sistemas si no está seguro de si el suyo lo es).</" +"Ahora puede escoger el bookmarklet desde cualquier página del sitio.\n" +"Observe que algunos de estos bookmarklets precisan que esté viendo\n" +"el sitio desde un ordenador señalado como \"interno\" (hable\n" +"con su administrador de sistemas si no está seguro si el suyo lo es).</" "p>\n" #: contrib/admin/templates/admin_doc/bookmarklets.html:18 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 msgid "" "Jumps you from any page to the documentation for the view that generates " "that page." 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 msgid "Show object ID" @@ -625,8 +624,8 @@ msgid "" "Shows the content-type and unique ID for pages that represent a single " "object." msgstr "" -"Muestra el tipo de contenido e ID unívoco de las páginas que representan un " -"único objeto." +"Muestra el tipo de contenido e ID único de las páginas que representan un " +"simple objeto." #: contrib/admin/templates/admin_doc/bookmarklets.html:24 msgid "Edit this object (current window)" @@ -635,7 +634,7 @@ msgstr "Editar este objeto (ventana actual)" #: contrib/admin/templates/admin_doc/bookmarklets.html:25 msgid "Jumps to the admin page for pages that represent a single object." 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." #: contrib/admin/templates/admin_doc/bookmarklets.html:27 @@ -649,85 +648,85 @@ msgstr "" #: contrib/admin/templates/registration/logged_out.html:8 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 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_form.html:3 #: contrib/admin/templates/registration/password_change_form.html:5 #: contrib/admin/templates/registration/password_change_form.html:9 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:9 msgid "Password change successful" -msgstr "Cambio de clave exitoso" +msgstr "Cambio de contraseña exitoso" #: contrib/admin/templates/registration/password_change_done.html:11 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 msgid "" "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." msgstr "" -"Por favor, introduzca su clave antigua, por seguridad, y después introduzca " -"la nueva clave dos veces para verificar que la ha escrito correctamente." +"Por favor, introduzca su contraseña antigua, por seguridad, y después introduzca " +"la nueva contraseña dos veces para verificar que la ha escrito correctamente." #: contrib/admin/templates/registration/password_change_form.html:16 msgid "Old password:" -msgstr "Clave antigua:" +msgstr "Contraseña antigua:" #: contrib/admin/templates/registration/password_change_form.html:18 msgid "New password:" -msgstr "Clave nueva:" +msgstr "Contraseña nueva:" #: contrib/admin/templates/registration/password_change_form.html:20 msgid "Confirm password:" -msgstr "Confirme clave:" +msgstr "Confirme contraseña:" #: contrib/admin/templates/registration/password_change_form.html:22 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_form.html:4 #: contrib/admin/templates/registration/password_reset_form.html:6 #: contrib/admin/templates/registration/password_reset_form.html:10 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:10 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 msgid "" "We've e-mailed a new password to the e-mail address you submitted. You " "should be receiving it shortly." 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." #: contrib/admin/templates/registration/password_reset_email.html:2 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 #, python-format 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 #, python-format 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 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 " "your password and e-mail the new one to you." 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." #: 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 msgid "Reset my password" -msgstr "Recuperar mi clave" +msgstr "Restablecer mi contraseña" #: contrib/admin/templates/widget/date_time.html:3 msgid "Date:" @@ -798,7 +797,7 @@ msgstr "Añadir usuario" #: contrib/admin/views/auth.py:58 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 #, python-format @@ -810,7 +809,7 @@ msgid "" "Please enter a correct username and password. Note that both fields are case-" "sensitive." 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." #: contrib/admin/views/decorators.py:69 @@ -818,7 +817,7 @@ msgid "" "Please log in again, because your session has expired. Don't worry: Your " "submission has been saved." 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." #: contrib/admin/views/decorators.py:76 @@ -827,7 +826,7 @@ msgid "" "cookies, reload this page, and try again." msgstr "" "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 #, python-format @@ -937,7 +936,7 @@ msgstr "Ruta de fichero" #: contrib/admin/views/doc.py:303 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 msgid "IP address" @@ -987,17 +986,17 @@ msgstr "Sitio administrativo" #: contrib/admin/views/main.py:280 contrib/admin/views/main.py:365 #, python-format 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 #, python-format msgid "Add %s" -msgstr "Agregar %s" +msgstr "Añadir %s" #: contrib/admin/views/main.py:344 #, python-format 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:348 core/validators.py:283 @@ -1029,7 +1028,7 @@ msgstr "Se modificó con éxito el %(name)s \"%(obj)s\"." msgid "" "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." 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 #, python-format @@ -1087,7 +1086,7 @@ msgid "" "Your Web browser doesn't appear to have cookies enabled. Cookies are " "required for logging in." 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." #: contrib/auth/forms.py:62 @@ -1105,17 +1104,17 @@ msgstr "" #: contrib/auth/forms.py:107 #, python-format msgid "Password reset on %s" -msgstr "Clave restablecida en %s" +msgstr "Contraseña restablecida en %s" #: contrib/auth/forms.py:117 msgid "The two 'new password' fields didn't match." 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 msgid "Your old password was entered incorrectly. Please enter it again." msgstr "" -"Tu contraseña antigua es incorrecta. Por favor, vuelve a introducirla " +"Tu contraseña antigua es incorrecta. Por favor, vuelva a introducirla " "correctamente." #: contrib/auth/models.py:73 contrib/auth/models.py:93 @@ -1138,15 +1137,15 @@ msgstr "permisos" msgid "group" msgstr "grupo" -#: contrib/auth/models.py:98 contrib/auth/models.py:141 +#: contrib/auth/models.py:98 contrib/auth/models.py:148 msgid "groups" msgstr "grupos" -#: contrib/auth/models.py:131 +#: contrib/auth/models.py:138 msgid "username" msgstr "nombre de usuario" -#: contrib/auth/models.py:131 +#: contrib/auth/models.py:138 msgid "" "Required. 30 characters or fewer. Alphanumeric characters only (letters, " "digits and underscores)." @@ -1154,23 +1153,23 @@ msgstr "" "Requerido. 30 caracteres o menos. Sólo caracteres alfanuméricos (letras, " "dígitos y guiones bajos)." -#: contrib/auth/models.py:132 +#: contrib/auth/models.py:139 msgid "first name" -msgstr "nombre" +msgstr "nombre propio" -#: contrib/auth/models.py:133 +#: contrib/auth/models.py:140 msgid "last name" msgstr "apellidos" -#: contrib/auth/models.py:134 +#: contrib/auth/models.py:141 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" -msgstr "clave" +msgstr "contraseña" -#: contrib/auth/models.py:135 +#: contrib/auth/models.py:142 msgid "" "Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change " "password form</a>." @@ -1178,32 +1177,31 @@ msgstr "" "Use'[algo]$[sal]$[hash hexadecimal]' o use <a href=\"password/\">el " "formulario para cambiar la contraseña</a>." -#: contrib/auth/models.py:136 +#: contrib/auth/models.py:143 msgid "staff status" msgstr "es staff" -#: contrib/auth/models.py:136 +#: contrib/auth/models.py:143 msgid "Designates whether the user can log into this admin site." 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" msgstr "activo" -#: contrib/auth/models.py:137 -#, fuzzy +#: contrib/auth/models.py:144 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." msgstr "" -"Indica si el usuario puede entrar en este sitio de administración. Desmarque " -"esto en lugar de borrar la cuenta." +"Indica si el usuario puede ser tratado como activo. Desmarque " +"esta opción en lugar de borrar la cuenta." -#: contrib/auth/models.py:138 +#: contrib/auth/models.py:145 msgid "superuser status" msgstr "es superusuario" -#: contrib/auth/models.py:138 +#: contrib/auth/models.py:145 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -1211,15 +1209,15 @@ msgstr "" "Indica que este usuario tiene todos los permisos sin asignárselos " "explícitamente." -#: contrib/auth/models.py:139 +#: contrib/auth/models.py:146 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" -msgstr "fecha de creación" +msgstr "fecha de alta" -#: contrib/auth/models.py:142 +#: contrib/auth/models.py:149 msgid "" "In addition to the permissions manually assigned, this user will also get " "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á " "todos los permisos de los grupos en los que esté." -#: contrib/auth/models.py:143 +#: contrib/auth/models.py:150 msgid "user permissions" -msgstr "permisos" +msgstr "permisos de usuario" -#: contrib/auth/models.py:147 +#: contrib/auth/models.py:154 msgid "user" msgstr "usuario" -#: contrib/auth/models.py:148 +#: contrib/auth/models.py:155 msgid "users" msgstr "usuarios" -#: contrib/auth/models.py:154 +#: contrib/auth/models.py:161 msgid "Personal info" msgstr "Información personal" -#: contrib/auth/models.py:155 +#: contrib/auth/models.py:162 msgid "Permissions" msgstr "Permisos" -#: contrib/auth/models.py:156 +#: contrib/auth/models.py:163 msgid "Important dates" msgstr "Fechas importantes" -#: contrib/auth/models.py:157 +#: contrib/auth/models.py:164 msgid "Groups" msgstr "Grupos" -#: contrib/auth/models.py:316 +#: contrib/auth/models.py:323 msgid "message" msgstr "mensaje" @@ -1278,39 +1276,39 @@ msgstr "comentario" #: contrib/comments/models.py:74 msgid "rating #1" -msgstr "calificación 1" +msgstr "puntuación 1" #: contrib/comments/models.py:75 msgid "rating #2" -msgstr "calificación 2" +msgstr "puntuación 2" #: contrib/comments/models.py:76 msgid "rating #3" -msgstr "calificación 3" +msgstr "puntuación 3" #: contrib/comments/models.py:77 msgid "rating #4" -msgstr "calificación 4" +msgstr "puntuación 4" #: contrib/comments/models.py:78 msgid "rating #5" -msgstr "calificación 5" +msgstr "puntuación 5" #: contrib/comments/models.py:79 msgid "rating #6" -msgstr "calificación 6" +msgstr "puntuación 6" #: contrib/comments/models.py:80 msgid "rating #7" -msgstr "calificación 7" +msgstr "puntuación 7" #: contrib/comments/models.py:81 msgid "rating #8" -msgstr "calificación 8" +msgstr "puntuación 8" #: contrib/comments/models.py:86 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 msgid "date/time submitted" @@ -1329,8 +1327,8 @@ msgid "" "Check this box if the comment is inappropriate. A \"This comment has been " "removed\" message will be displayed instead." msgstr "" -"Marque esta caja si el comentario es inapropiado. En su lugar se mostrará " -"\"Este comentario ha sido eliminado\"." +"Marque esta opción si el comentario es inapropiado. En su lugar se mostrará " +"el mensaje \"Este comentario ha sido eliminado\"." #: contrib/comments/models.py:96 msgid "comments" @@ -1426,15 +1424,15 @@ msgstr "Marca de %r" #: contrib/comments/models.py:300 msgid "deletion date" -msgstr "fecha de eliminación" +msgstr "fecha de borrado" #: contrib/comments/models.py:303 msgid "moderator deletion" -msgstr "eliminación de moderador" +msgstr "borrado del moderador" #: contrib/comments/models.py:304 msgid "moderator deletions" -msgstr "eliminaciones de moderador" +msgstr "eliminaciones del moderador" #: contrib/comments/models.py:308 #, python-format @@ -1447,7 +1445,7 @@ msgstr "¿Has olvidado tu contraseña?" #: contrib/comments/templates/comments/form.html:12 msgid "Ratings" -msgstr "Calificaciones" +msgstr "Puntuaciones" #: contrib/comments/templates/comments/form.html:12 #: contrib/comments/templates/comments/form.html:23 @@ -1461,7 +1459,7 @@ msgstr "Opcional" #: contrib/comments/templates/comments/form.html:23 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/freeform.html:5 @@ -1519,7 +1517,7 @@ msgstr "" #: contrib/comments/views/comments.py:190 #: contrib/comments/views/comments.py:283 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:287 @@ -1530,7 +1528,7 @@ msgstr "No se proporcionó uno o más de los siguientes campos requeridos" #: contrib/comments/views/comments.py:289 msgid "Somebody tampered with the comment form (security violation)" msgstr "" -"Alguien está jugando con el formulario de comentarios (violación de " +"Alguien realizó manipulaciones con el formulario de comentarios (violación de " "seguridad)" #: contrib/comments/views/comments.py:208 @@ -1557,11 +1555,11 @@ msgstr "ID de comentario no válido" #: contrib/comments/views/karma.py:27 msgid "No voting for yourself" -msgstr "No puedes votarte tú mismo" +msgstr "No puedes votarte a ti mismo" #: contrib/contenttypes/models.py:67 msgid "python model class name" -msgstr "nombre de módulo python" +msgstr "nombre de la clase modelo de python" #: contrib/contenttypes/models.py:71 msgid "content type" @@ -1588,7 +1586,7 @@ msgstr "contenido" #: contrib/flatpages/models.py:12 msgid "enable comments" -msgstr "admitir comentarios" +msgstr "habilitar comentarios" #: contrib/flatpages/models.py:13 msgid "template name" @@ -1624,19 +1622,19 @@ msgstr "Opciones avanzadas" #: contrib/humanize/templatetags/humanize.py:19 msgid "th" -msgstr "th" +msgstr "º" #: contrib/humanize/templatetags/humanize.py:19 msgid "st" -msgstr "st" +msgstr "º" #: contrib/humanize/templatetags/humanize.py:19 msgid "nd" -msgstr "nd" +msgstr "º" #: contrib/humanize/templatetags/humanize.py:19 msgid "rd" -msgstr "rd" +msgstr "º" #: contrib/humanize/templatetags/humanize.py:51 #, python-format @@ -1649,15 +1647,15 @@ msgstr[1] "%(value).1f millión" #, python-format msgid "%(value).1f billion" msgid_plural "%(value).1f billion" -msgstr[0] "%(value).1f billión" -msgstr[1] "%(value).1f billión" +msgstr[0] "%(value).1f billón" +msgstr[1] "%(value).1f billón" #: contrib/humanize/templatetags/humanize.py:57 #, python-format msgid "%(value).1f trillion" msgid_plural "%(value).1f trillion" -msgstr[0] "%(value).1f trillión" -msgstr[1] "%(value).1f trillión" +msgstr[0] "%(value).1f trillón" +msgstr[1] "%(value).1f trillón" #: contrib/humanize/templatetags/humanize.py:73 msgid "one" @@ -3507,7 +3505,7 @@ msgid "" "This should be an absolute path, excluding the domain name. Example: '/" "events/search/'." 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/'." #: contrib/redirects/models.py:9 @@ -3575,7 +3573,7 @@ msgid "" "This value must contain only letters, numbers, underscores, dashes or " "slashes." 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 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 #, python-format 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 #, python-format @@ -3932,25 +3930,25 @@ msgstr "Introduzca un nombre de fichero válido" #: db/models/fields/__init__.py:981 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 #, python-format msgid "Please enter a valid %s." 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." msgstr "Separe múltiples IDs con comas." -#: db/models/fields/related.py:723 +#: db/models/fields/related.py:748 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" "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 msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -4405,16 +4403,5 @@ msgstr "Se actualizó con éxito el %(verbose_name)s." #: views/generic/create_update.py:184 #, python-format msgid "The %(verbose_name)s was deleted." -msgstr "El %(verbose_name)s ha sido eliminado." - -#~ msgid "Brazilian" -#~ msgstr "Brasileño" - -#~ msgid "Gaeilge" -#~ msgstr "Gaeilge" +msgstr "El %(verbose_name)s ha sido borrado." -#~ 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." diff --git a/django/contrib/auth/create_superuser.py b/django/contrib/auth/create_superuser.py index 7b6cefd268..7b58678b78 100644 --- a/django/contrib/auth/create_superuser.py +++ b/django/contrib/auth/create_superuser.py @@ -1,94 +1,8 @@ """ -Helper function for creating superusers in the authentication system. - -If run from the command line, this module lets you create a superuser -interactively. +Create a superuser from the command line. Deprecated; use manage.py +createsuperuser instead. """ -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__": - createsuperuser() + from django.core.management import call_command + call_command("createsuperuser") diff --git a/django/contrib/auth/management.py b/django/contrib/auth/management/__init__.py index 2b4cb8bd19..8394bee5cd 100644 --- a/django/contrib/auth/management.py +++ b/django/contrib/auth/management/__init__.py @@ -32,7 +32,7 @@ def create_permissions(app, created_models, verbosity): def create_superuser(app, created_models, verbosity, **kwargs): 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): 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): " @@ -42,8 +42,10 @@ def create_superuser(app, created_models, verbosity, **kwargs): confirm = raw_input('Please enter either "yes" or "no": ') continue if confirm == 'yes': - do_create() + call_command("createsuperuser", interactive=True) break -dispatcher.connect(create_permissions, signal=signals.post_syncdb) -dispatcher.connect(create_superuser, sender=auth_app, signal=signals.post_syncdb) +if 'create_permissions' not in [i.__name__ for i in dispatcher.getAllReceivers(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)
\ No newline at end of file diff --git a/django/contrib/auth/management/commands/__init__.py b/django/contrib/auth/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/django/contrib/auth/management/commands/__init__.py diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py new file mode 100644 index 0000000000..4299762c74 --- /dev/null +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -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." diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index c8670655d4..f74d1d7761 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -116,6 +116,13 @@ class UserManager(models.Manager): user.save() 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'): "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 diff --git a/django/contrib/auth/tests.py b/django/contrib/auth/tests.py index d369ac524c..81094ca85f 100644 --- a/django/contrib/auth/tests.py +++ b/django/contrib/auth/tests.py @@ -35,4 +35,21 @@ False [] >>> 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'!' """
\ No newline at end of file diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index b8726fd2bd..1063760915 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -4,6 +4,7 @@ import os import random import sys import time +from datetime import datetime, timedelta from django.conf import settings from django.core.exceptions import SuspiciousOperation @@ -128,6 +129,62 @@ class SessionBase(object): _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. def exists(self, session_key): diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py index c3e641e691..7626163a13 100644 --- a/django/contrib/sessions/backends/cache.py +++ b/django/contrib/sessions/backends/cache.py @@ -4,23 +4,23 @@ from django.core.cache import cache class SessionStore(SessionBase): """ - A cache-based session store. + A cache-based session store. """ def __init__(self, session_key=None): self._cache = cache super(SessionStore, self).__init__(session_key) - + def load(self): session_data = self._cache.get(self.session_key) return session_data or {} 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): if self._cache.get(session_key): return True return False - + def delete(self, session_key): self._cache.delete(session_key)
\ No newline at end of file diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py index 0f79d9ee1a..b1c1097865 100644 --- a/django/contrib/sessions/backends/db.py +++ b/django/contrib/sessions/backends/db.py @@ -41,7 +41,7 @@ class SessionStore(SessionBase): Session.objects.create( session_key = self.session_key, 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): diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 2af2312e76..a7b376dde0 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -26,14 +26,14 @@ class SessionMiddleware(object): if accessed: patch_vary_headers(response, ('Cookie',)) 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 expires = None else: - max_age = settings.SESSION_COOKIE_AGE - expires_time = time.time() + settings.SESSION_COOKIE_AGE + max_age = request.session.get_expiry_age() + expires_time = time.time() + max_age 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() response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index b2c664ce7b..0f162b211f 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -88,6 +88,100 @@ False >>> s.pop('some key', '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__': diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 2642ae925e..2559d57104 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -9,6 +9,8 @@ class Command(BaseCommand): help='Specifies the output serialization format for fixtures.'), make_option('--indent', default=None, dest='indent', type='int', 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.' args = '[appname ...]' @@ -16,12 +18,15 @@ class Command(BaseCommand): def handle(self, *app_labels, **options): from django.db.models import get_app, get_apps, get_models - format = options.get('format', 'json') - indent = options.get('indent', None) + format = options.get('format','json') + indent = options.get('indent',None) + exclude = options.get('exclude',[]) show_traceback = options.get('traceback', False) + excluded_apps = [get_app(app_label) for app_label in exclude] + if len(app_labels) == 0: - app_list = get_apps() + app_list = [app for app in get_apps() if app not in excluded_apps] else: app_list = [get_app(app_label) for app_label in app_labels] diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index d06b131d6f..193bb26ccf 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -32,6 +32,7 @@ class Command(BaseCommand): # Keep a count of the installed objects and fixtures fixture_count = 0 object_count = 0 + objects_per_fixture = [] models = set() humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path' @@ -60,11 +61,16 @@ class Command(BaseCommand): else: formats = [] - if verbosity >= 2: - if formats: + if formats: + if verbosity > 1: print "Loading '%s' fixtures..." % fixture_name - else: - print "Skipping fixture '%s': %s is not a known serialization format" % (fixture_name, format) + else: + 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): fixture_dirs = [fixture_name] @@ -93,6 +99,7 @@ class Command(BaseCommand): return else: fixture_count += 1 + objects_per_fixture.append(0) if verbosity > 0: print "Installing %s fixture '%s' from %s." % \ (format, fixture_name, humanize(fixture_dir)) @@ -100,6 +107,7 @@ class Command(BaseCommand): objects = serializers.deserialize(format, fixture) for obj in objects: object_count += 1 + objects_per_fixture[-1] += 1 models.add(obj.object.__class__) obj.save() label_found = True @@ -117,10 +125,23 @@ class Command(BaseCommand): return fixture.close() except: - if verbosity >= 2: + if verbosity > 1: print "No %s fixture '%s' in %s." % \ (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: sequence_sql = connection.ops.sequence_reset_sql(self.style, models) if sequence_sql: @@ -128,12 +149,12 @@ class Command(BaseCommand): print "Resetting sequences" for line in sequence_sql: cursor.execute(line) - + transaction.commit() transaction.leave_transaction_management() if object_count == 0: - if verbosity >= 2: + if verbosity > 1: print "No fixtures found." else: if verbosity > 0: diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index ae92c053ca..dc0a72a2a6 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -34,7 +34,16 @@ class Command(NoArgsCommand): try: __import__(app_name + '.management', {}, {}, ['']) 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 cursor = connection.cursor() diff --git a/django/core/management/sql.py b/django/core/management/sql.py index e7a54acb6f..dfe97cdd0c 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -454,7 +454,7 @@ def custom_sql_for_model(model, style): fp = open(sql_file, 'U') for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)): # Remove any comments from the file - statement = re.sub(ur"--.*[\n\Z]", "", statement) + statement = re.sub(ur"--.*([\n\Z]|$)", "", statement) if statement.strip(): output.append(statement + u";") fp.close() diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index a79497ecec..e22a35815b 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -38,7 +38,7 @@ class Serializer(object): self.start_serialization() for obj in queryset: self.start_object(obj) - for field in obj._meta.fields: + for field in obj._meta.local_fields: if field.serialize: if field.rel is None: if self.selected_fields is None or field.attname in self.selected_fields: diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 063c0ae411..b076e290dd 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -202,8 +202,8 @@ class BaseDatabaseOperations(object): def query_class(self, DefaultQueryClass): """ - Given the default QuerySet class, returns a custom QuerySet class - to use for this backend. Returns None if a custom QuerySet isn't used. + Given the default Query class, returns a custom Query class + to use for this backend. Returns None if a custom Query isn't used. See also BaseDatabaseFeatures.uses_custom_query_class, which regulates whether this method is called at all. """ diff --git a/django/db/models/base.py b/django/db/models/base.py index 0ee225675a..5dd11a9d83 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -290,12 +290,17 @@ class Model(object): meta = cls._meta signal = False - for parent, field in meta.parents.items(): - self.save_base(raw, parent) - setattr(self, field.attname, self._get_pk_val(parent._meta)) + # 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(): + self.save_base(raw, parent) + setattr(self, field.attname, self._get_pk_val(parent._meta)) non_pks = [f for f in meta.local_fields if not f.primary_key] - + # First, try an UPDATE. If that doesn't update anything, do an INSERT. pk_val = self._get_pk_val(meta) # Note: the comparison with '' is required for compatibility with diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 3253ea18e6..c9deac428f 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -182,14 +182,29 @@ class SingleRelatedObjectDescriptor(object): def __set__(self, instance, value): if instance is None: 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 setattr(value, self.related.field.rel.get_related_field().attname, instance) - # Clear the cache, if it exists - try: - delattr(value, self.related.field.get_cache_name()) - except AttributeError: - pass + # Since we already know what the related object is, seed the related + # object caches now, too. This avoids another db hit if you get the + # object you just set. + setattr(instance, self.cache_name, value) + setattr(value, self.related.field.get_cache_name(), instance) class ReverseSingleRelatedObjectDescriptor(object): # This class provides the functionality that makes the related-object @@ -225,6 +240,17 @@ class ReverseSingleRelatedObjectDescriptor(object): def __set__(self, instance, value): if instance is None: 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 try: val = getattr(value, self.field.rel.get_related_field().attname) @@ -232,11 +258,10 @@ class ReverseSingleRelatedObjectDescriptor(object): val = None setattr(instance, self.field.attname, val) - # Clear the cache, if it exists - try: - delattr(instance, self.field.get_cache_name()) - except AttributeError: - pass + # Since we already know what the related object is, seed the related + # object cache now, too. This avoids another db hit if you get the + # object you just set. + setattr(instance, self.field.get_cache_name(), value) class ForeignRelatedObjectsDescriptor(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._pk_val = self.instance._get_pk_val() 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): return superclass.get_query_set(self).filter(**(self.core_filters)) diff --git a/django/db/models/options.py b/django/db/models/options.py index 5802ead081..bc1aec62c1 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -56,8 +56,12 @@ class Options(object): # Next, apply any overridden values from 'class Meta'. if self.meta: meta_attrs = self.meta.__dict__.copy() - del meta_attrs['__module__'] - del meta_attrs['__doc__'] + for name in self.meta.__dict__: + # 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: if attr_name in meta_attrs: setattr(self, attr_name, meta_attrs.pop(attr_name)) @@ -98,7 +102,7 @@ class Options(object): # field. field = self.parents.value_for_index(0) field.primary_key = True - self.pk = field + self.setup_pk(field) else: auto = AutoField(verbose_name='ID', primary_key=True, auto_created=True) diff --git a/django/db/models/query.py b/django/db/models/query.py index 6b341ba9ab..fb6d116a6e 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -292,6 +292,8 @@ class QuerySet(object): Updates all elements in the current QuerySet, setting all the given 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.add_update_values(kwargs) query.execute_sql(None) @@ -306,6 +308,8 @@ class QuerySet(object): code (it requires too much poking around at model internals to be 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.add_update_fields(values) query.execute_sql(None) @@ -509,7 +513,9 @@ class ValuesQuerySet(QuerySet): # names of the model fields to select. def iterator(self): - self.query.trim_extra_select(self.extra_names) + if (not self.extra_names and + len(self.field_names) != len(self.model._meta.fields)): + self.query.trim_extra_select(self.extra_names) names = self.query.extra_select.keys() + self.field_names for row in self.query.results_iter(): yield dict(zip(names, row)) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index a6957bab7b..3044882a86 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -851,7 +851,7 @@ class Query(object): return alias 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 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): continue table = f.rel.to._meta.db_table + if nullable or f.null: + promote = True + else: + promote = False if model: int_opts = opts alias = root_alias @@ -891,12 +895,12 @@ class Query(object): int_opts = int_model._meta alias = self.join((alias, int_opts.db_table, lhs_col, int_opts.pk.column), exclusions=used, - promote=f.null) + promote=promote) else: alias = root_alias alias = self.join((alias, table, f.column, f.rel.get_related_field().column), exclusions=used, - promote=f.null) + promote=promote) used.add(alias) self.related_select_cols.extend([(alias, f2.column) for f2 in f.rel.to._meta.fields]) @@ -905,8 +909,12 @@ class Query(object): next = requested.get(f.name, {}) else: 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, - used, next, restricted) + used, next, restricted, new_nullable) def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, can_reuse=None): diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 7385cd00e1..28436abede 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -2,10 +2,9 @@ Query subclasses which provide extra functionality beyond simple data retrieval. """ -from django.contrib.contenttypes import generic from django.core.exceptions import FieldError 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.where import AND @@ -43,6 +42,7 @@ class DeleteQuery(Query): More than one physical query may be executed if there are a lot of values in pk_list. """ + from django.contrib.contenttypes import generic cls = self.model for related in cls._meta.get_all_related_many_to_many_objects(): if not isinstance(related.field, generic.GenericRelation): @@ -382,4 +382,3 @@ class CountQuery(Query): def get_ordering(self): return () - diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 3e8bfed087..14e54487a3 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -18,7 +18,7 @@ class WhereNode(tree.Node): Used to represent the SQL where-clause. 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 [table_alias, field_name, field_class, lookup_type, value]. However, a diff --git a/django/template/__init__.py b/django/template/__init__.py index e60ff64ebf..5c4ab3052a 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -467,7 +467,7 @@ class FilterExpression(object): >>> len(fe.filters) 2 >>> fe.var - 'variable' + <Variable: 'variable'> This class should never be instantiated outside of the 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 marks):: - >>> c = {'article': {'section':'News'}} + >>> c = {'article': {'section':u'News'}} >>> Variable('article.section').resolve(c) u'News' >>> Variable('article').resolve(c) - {'section': 'News'} + {'section': u'News'} >>> class AClass: pass >>> c = AClass() >>> c.article = AClass() - >>> c.article.section = 'News' + >>> c.article.section = u'News' >>> Variable('article.section').resolve(c) u'News' diff --git a/django/test/client.py b/django/test/client.py index bac28f797b..a15876e6f9 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -1,5 +1,6 @@ import urllib import sys +import os from cStringIO import StringIO from django.conf import settings from django.contrib.auth import authenticate, login @@ -67,7 +68,7 @@ def encode_multipart(boundary, data): if isinstance(value, file): lines.extend([ '--' + 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', '', value.read() @@ -178,10 +179,15 @@ class Client: if e.args != ('500.html',): 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: - 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 response.client = self response.request = request diff --git a/django/test/testcases.py b/django/test/testcases.py index 5589443aed..ee83b960a6 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -128,6 +128,18 @@ class TestCase(unittest.TestCase): self.failUnless(real_count != 0, "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): """ Asserts that a form used to render the response has a specific field diff --git a/django/utils/text.py b/django/utils/text.py index 4670ab47fa..aa190c8c4f 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -118,7 +118,7 @@ def get_valid_filename(s): spaces are converted to underscores; and all non-filename-safe characters are removed. >>> 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(' ', '_') 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')): """ >>> 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') - 'a, b and c' + u'a, b and c' >>> get_text_list(['a', 'b'], 'and') - 'a and b' + u'a and b' >>> get_text_list(['a']) - 'a' + u'a' >>> get_text_list([]) - '' + u'' """ if len(list_) == 0: return u'' 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]+)') def smart_split(text): - """ + r""" Generator that splits a string by spaces, leaving quoted phrases together. Supports both single and double quotes, and supports escaping quotes with backslashes. In the output, strings will keep their initial and trailing quote marks. - >>> list(smart_split('This is "a person\'s" test.')) - ['This', 'is', '"a person\'s"', 'test.'] + >>> list(smart_split(r'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) for bit in smart_split_re.finditer(text): |
