From 34560a01daee3c42a7c5ec462f38a485cccf4df7 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 15 Dec 2007 01:38:43 +0000 Subject: [PATCH] gis: Merged revisions 6672,6686-6688,6690,6693,6707-6708,6726,6730,6753,6755-6762,6764,6776-6777,6779,6782-6919 via svnmerge from trunk; reverted oracle backend `base.py` due to ikelly's patch in r6905. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6920 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 8 + django/conf/__init__.py | 6 +- django/conf/global_settings.py | 6 +- django/conf/locale/ca/LC_MESSAGES/django.mo | Bin 61655 -> 62260 bytes django/conf/locale/ca/LC_MESSAGES/django.po | 392 +++++------ django/conf/locale/ca/LC_MESSAGES/djangojs.mo | Bin 1654 -> 1675 bytes django/conf/locale/ca/LC_MESSAGES/djangojs.po | 14 +- .../contrib/admin/templatetags/admin_list.py | 2 +- django/contrib/auth/models.py | 2 +- django/contrib/auth/tests.py | 12 + django/contrib/contenttypes/generic.py | 42 +- django/contrib/localflavor/mx/__init__.py | 0 django/contrib/localflavor/mx/forms.py | 14 + django/contrib/localflavor/mx/mx_states.py | 45 ++ django/contrib/localflavor/uk/forms.py | 18 +- django/contrib/localflavor/uk/uk_regions.py | 97 +++ django/contrib/localflavor/za/__init__.py | 0 django/contrib/localflavor/za/forms.py | 57 ++ django/contrib/localflavor/za/za_provinces.py | 13 + django/contrib/markup/templatetags/markup.py | 31 +- django/contrib/markup/tests.py | 9 +- django/contrib/sessions/backends/base.py | 8 + django/contrib/sessions/backends/file.py | 13 +- django/contrib/sessions/models.py | 34 - django/contrib/sessions/tests.py | 13 + django/core/cache/__init__.py | 11 +- django/core/cache/backends/filebased.py | 131 ++-- django/core/cache/backends/locmem.py | 105 ++- django/core/cache/backends/simple.py | 73 -- django/core/exceptions.py | 4 + django/core/handlers/base.py | 5 +- django/core/handlers/modpython.py | 9 +- django/core/handlers/wsgi.py | 4 +- django/core/management/__init__.py | 80 +-- django/core/management/commands/loaddata.py | 6 +- django/core/management/commands/syncdb.py | 5 +- django/core/management/validation.py | 16 +- django/core/serializers/pyyaml.py | 16 +- django/db/__init__.py | 4 +- django/db/backends/oracle/base.py | 36 +- .../db/backends/postgresql_psycopg2/base.py | 2 + django/db/models/base.py | 4 +- django/db/models/fields/__init__.py | 2 +- django/db/models/options.py | 6 +- django/db/models/query.py | 3 +- django/http/__init__.py | 2 +- django/http/utils.py | 5 +- django/middleware/common.py | 58 +- django/newforms/fields.py | 26 +- django/newforms/models.py | 168 ++++- django/shortcuts/__init__.py | 2 +- django/template/context.py | 7 +- django/template/defaultfilters.py | 28 +- django/test/_doctest.py | 11 + django/utils/datastructures.py | 6 +- django/utils/html.py | 24 +- django/utils/maxlength.py | 18 +- django/utils/safestring.py | 21 +- django/views/debug.py | 95 ++- django/views/generic/date_based.py | 2 +- django/views/generic/list_detail.py | 2 +- docs/add_ons.txt | 41 +- docs/authentication.txt | 55 +- docs/cache.txt | 4 + docs/contributing.txt | 19 +- docs/custom_model_fields.txt | 269 +++---- docs/databases.txt | 5 + docs/email.txt | 8 +- docs/form_for_model.txt | 418 +++++++++++ docs/generic_views.txt | 4 +- docs/install.txt | 16 +- docs/localflavor.txt | 654 ++++++++++++++++++ docs/middleware.txt | 19 +- docs/model-api.txt | 5 +- docs/modelforms.txt | 313 +++++++++ docs/newforms.txt | 447 +----------- docs/request_response.txt | 16 +- docs/serialization.txt | 9 +- docs/sessions.txt | 2 + docs/settings.txt | 47 +- docs/shortcuts.txt | 4 +- docs/syndication_feeds.txt | 13 +- docs/templates.txt | 265 ++++--- docs/templates_python.txt | 229 +++--- docs/tutorial02.txt | 50 +- tests/modeltests/empty/models.py | 2 +- tests/modeltests/field_defaults/models.py | 5 + tests/modeltests/generic_relations/models.py | 30 +- tests/modeltests/get_object_or_404/models.py | 2 +- tests/modeltests/invalid_models/models.py | 6 + tests/modeltests/model_forms/models.py | 292 ++++---- tests/modeltests/select_related/models.py | 2 +- tests/modeltests/serializers/models.py | 40 +- tests/modeltests/test_client/models.py | 2 +- tests/modeltests/user_commands/models.py | 4 +- tests/regressiontests/cache/tests.py | 60 +- tests/regressiontests/defaultfilters/tests.py | 12 + tests/regressiontests/forms/localflavor/za.py | 40 ++ tests/regressiontests/forms/tests.py | 2 + tests/regressiontests/i18n/tests.py | 15 +- tests/regressiontests/maxlength/tests.py | 6 +- tests/regressiontests/middleware/__init__.py | 0 tests/regressiontests/middleware/tests.py | 93 +++ tests/regressiontests/middleware/urls.py | 7 + tests/regressiontests/model_regress/models.py | 10 + tests/regressiontests/string_lookup/models.py | 8 +- tests/regressiontests/templates/context.py | 18 + tests/regressiontests/templates/tests.py | 2 + tests/regressiontests/views/views.py | 2 - tests/runtests.py | 3 +- tests/urls.py | 3 + 111 files changed, 3795 insertions(+), 1616 deletions(-) create mode 100644 django/contrib/localflavor/mx/__init__.py create mode 100644 django/contrib/localflavor/mx/forms.py create mode 100644 django/contrib/localflavor/mx/mx_states.py create mode 100644 django/contrib/localflavor/uk/uk_regions.py create mode 100644 django/contrib/localflavor/za/__init__.py create mode 100644 django/contrib/localflavor/za/forms.py create mode 100644 django/contrib/localflavor/za/za_provinces.py delete mode 100644 django/core/cache/backends/simple.py create mode 100644 docs/form_for_model.txt create mode 100644 docs/localflavor.txt create mode 100644 docs/modelforms.txt create mode 100644 tests/regressiontests/forms/localflavor/za.py create mode 100644 tests/regressiontests/middleware/__init__.py create mode 100644 tests/regressiontests/middleware/tests.py create mode 100644 tests/regressiontests/middleware/urls.py create mode 100644 tests/regressiontests/templates/context.py diff --git a/AUTHORS b/AUTHORS index 0768645f1c..ffb9e87690 100644 --- a/AUTHORS +++ b/AUTHORS @@ -89,6 +89,7 @@ answer newbie questions, and generally made Django that much better: Paul Collier Pete Crosier Matt Croydon + Leah Culver flavio.curella@gmail.com Jure Cuhalev John D'Agostino @@ -132,6 +133,7 @@ answer newbie questions, and generally made Django that much better: Jorge Gajon gandalf@owca.info Marc Garcia + Andy Gayton Baishampayan Ghose Dimitris Glezos glin@seznam.cz @@ -171,6 +173,7 @@ answer newbie questions, and generally made Django that much better: junzhang.jn@gmail.com Antti Kaihola Nagy Károly + Erik Karulf Ben Dean Kawamura Ian G. Kelly Thomas Kerpe @@ -190,6 +193,7 @@ answer newbie questions, and generally made Django that much better: Joseph Kocherhans konrad@gwu.edu knox + David Krauth kurtiss@meetro.com lakin.wecker@gmail.com Nick Lane @@ -204,6 +208,7 @@ answer newbie questions, and generally made Django that much better: Waylan Limberg limodou Philip Lindborg + Trey Long msaelices Matt McClanahan Martin Maney @@ -251,6 +256,7 @@ answer newbie questions, and generally made Django that much better: Gustavo Picon Luke Plant plisk + Mihai Preda Daniel Poelzleithner polpak@yahoo.com Jyrki Pulliainen @@ -262,6 +268,7 @@ answer newbie questions, and generally made Django that much better: Massimiliano Ravelli Brian Ray remco@diji.biz + David Reynolds rhettg@gmail.com ricardojbarrios@gmail.com Matt Riggott @@ -281,6 +288,7 @@ answer newbie questions, and generally made Django that much better: Pete Shinners jason.sidabras@gmail.com Jozko Skrablin + Ben Slavin SmileyChris smurf@smurf.noris.de Vsevolod Solovyov diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 0177490df9..c24e87e6ed 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -52,7 +52,7 @@ class LazySettings(object): if not settings_module: # If it's set but is an empty string. raise KeyError except KeyError: - raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE + raise ImportError, "Environment variable %s is undefined so settings cannot be imported." % ENVIRONMENT_VARIABLE # NOTE: This is arguably an EnvironmentError, but that causes problems with Python's interactive help self._target = Settings(settings_module) @@ -63,7 +63,7 @@ class LazySettings(object): argument must support attribute access (__getattr__)). """ if self._target != None: - raise EnvironmentError, 'Settings already configured.' + raise RuntimeError, 'Settings already configured.' holder = UserSettingsHolder(default_settings) for name, value in options.items(): setattr(holder, name, value) @@ -82,7 +82,7 @@ class Settings(object): try: mod = __import__(self.SETTINGS_MODULE, {}, {}, ['']) except ImportError, e: - raise EnvironmentError, "Could not import settings '%s' (Is it on sys.path? Does it have syntax errors?): %s" % (self.SETTINGS_MODULE, e) + raise ImportError, "Could not import settings '%s' (Is it on sys.path? Does it have syntax errors?): %s" % (self.SETTINGS_MODULE, e) # Settings that should be converted into tuples if they're mistakenly entered # as strings. diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 2853d6c618..187eb0645e 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -253,6 +253,10 @@ TRANSACTIONS_MANAGED = False from django import get_version URL_VALIDATOR_USER_AGENT = "Django/%s (http://www.djangoproject.com)" % get_version() +# The tablespaces to use for each model when not specified otherwise. +DEFAULT_TABLESPACE = '' +DEFAULT_INDEX_TABLESPACE = '' + ############## # MIDDLEWARE # ############## @@ -289,7 +293,7 @@ SESSION_FILE_PATH = '/tmp/' # Directory to store ses # The cache backend to use. See the docstring in django.core.cache for the # possible values. -CACHE_BACKEND = 'simple://' +CACHE_BACKEND = 'locmem://' CACHE_MIDDLEWARE_KEY_PREFIX = '' CACHE_MIDDLEWARE_SECONDS = 600 diff --git a/django/conf/locale/ca/LC_MESSAGES/django.mo b/django/conf/locale/ca/LC_MESSAGES/django.mo index fdba04abd26960b8ed635342546275a0337242bb..a39a4551bb62dc3a0e158fa9cbb41e4f2e5e5fdb 100644 GIT binary patch delta 19130 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 delta 18681 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 rDj{)O`^2o\n" +"POT-Creation-Date: 2007-12-02 22:26+0100\n" +"PO-Revision-Date: 2007-12-02 22:32+0100\n" +"Last-Translator: Marc Fargas \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -194,7 +194,7 @@ msgstr "Xinés simplificat" msgid "Traditional Chinese" msgstr "Xinés tradicional" -#: contrib/admin/filterspecs.py:42 +#: contrib/admin/filterspecs.py:44 #, python-format msgid "" "

By %s:

\n" @@ -203,71 +203,71 @@ msgstr "" "

Per %s:

\n" "
    \n" -#: contrib/admin/filterspecs.py:72 contrib/admin/filterspecs.py:90 -#: contrib/admin/filterspecs.py:145 contrib/admin/filterspecs.py:171 +#: contrib/admin/filterspecs.py:74 contrib/admin/filterspecs.py:92 +#: contrib/admin/filterspecs.py:147 contrib/admin/filterspecs.py:173 msgid "All" msgstr "Tots" -#: contrib/admin/filterspecs.py:111 +#: contrib/admin/filterspecs.py:113 msgid "Any date" msgstr "Qualsevol data" -#: contrib/admin/filterspecs.py:112 +#: contrib/admin/filterspecs.py:114 msgid "Today" msgstr "Avui" -#: contrib/admin/filterspecs.py:115 +#: contrib/admin/filterspecs.py:117 msgid "Past 7 days" msgstr "Últims 7 dies" -#: contrib/admin/filterspecs.py:117 +#: contrib/admin/filterspecs.py:119 msgid "This month" msgstr "Aquest mes" -#: contrib/admin/filterspecs.py:119 +#: contrib/admin/filterspecs.py:121 msgid "This year" msgstr "Aquest any" -#: contrib/admin/filterspecs.py:145 newforms/widgets.py:221 -#: oldforms/__init__.py:591 +#: contrib/admin/filterspecs.py:147 newforms/widgets.py:231 +#: oldforms/__init__.py:592 msgid "Yes" msgstr "Si" -#: contrib/admin/filterspecs.py:145 newforms/widgets.py:221 -#: oldforms/__init__.py:591 +#: contrib/admin/filterspecs.py:147 newforms/widgets.py:231 +#: oldforms/__init__.py:592 msgid "No" msgstr "No" -#: contrib/admin/filterspecs.py:152 newforms/widgets.py:221 -#: oldforms/__init__.py:591 +#: contrib/admin/filterspecs.py:154 newforms/widgets.py:231 +#: oldforms/__init__.py:592 msgid "Unknown" msgstr "Desconegut" -#: contrib/admin/models.py:17 +#: contrib/admin/models.py:18 msgid "action time" msgstr "moment de l'acció" -#: contrib/admin/models.py:20 +#: contrib/admin/models.py:21 msgid "object id" msgstr "id del objecte" -#: contrib/admin/models.py:21 +#: contrib/admin/models.py:22 msgid "object repr" msgstr "'repr' de l'objecte" -#: contrib/admin/models.py:22 +#: contrib/admin/models.py:23 msgid "action flag" msgstr "marca de l'acció" -#: contrib/admin/models.py:23 +#: contrib/admin/models.py:24 msgid "change message" msgstr "missatge del canvi" -#: contrib/admin/models.py:26 +#: contrib/admin/models.py:27 msgid "log entry" msgstr "entrada del registre" -#: contrib/admin/models.py:27 +#: contrib/admin/models.py:28 msgid "log entries" msgstr "entrades del registre" @@ -469,7 +469,7 @@ msgid "Password:" msgstr "Contrasenya:" #: contrib/admin/templates/admin/login.html:25 -#: contrib/admin/views/decorators.py:24 +#: contrib/admin/views/decorators.py:25 msgid "Log in" msgstr "Iniciar sessió" @@ -769,17 +769,17 @@ msgstr "Actualment:" msgid "Change:" msgstr "Modificar:" -#: contrib/admin/templatetags/admin_list.py:254 +#: contrib/admin/templatetags/admin_list.py:257 msgid "All dates" msgstr "Totes les dates" -#: contrib/admin/views/auth.py:20 contrib/admin/views/main.py:264 +#: contrib/admin/views/auth.py:20 contrib/admin/views/main.py:267 #, python-format msgid "The %(name)s \"%(obj)s\" was added successfully." msgstr "El/la %(name)s \"%(obj)s\".ha estat agregat/da amb èxit." -#: contrib/admin/views/auth.py:25 contrib/admin/views/main.py:268 -#: contrib/admin/views/main.py:354 +#: contrib/admin/views/auth.py:25 contrib/admin/views/main.py:271 +#: contrib/admin/views/main.py:356 msgid "You may edit it again below." msgstr "Pot editar-lo de nou abaix." @@ -796,7 +796,7 @@ msgstr "Canvi de clau exitós" msgid "Change password: %s" msgstr "Canviar clau: %s" -#: contrib/admin/views/decorators.py:10 contrib/auth/forms.py:60 +#: contrib/admin/views/decorators.py:11 contrib/auth/forms.py:60 msgid "" "Please enter a correct username and password. Note that both fields are case-" "sensitive." @@ -804,7 +804,7 @@ msgstr "" "Si us plau, introdueixi un nom d'usuari i contrasenya vàlids. Tingui en " "compte que tots dos camps son sensibles a majúscules i minúscules." -#: contrib/admin/views/decorators.py:62 +#: contrib/admin/views/decorators.py:63 msgid "" "Please log in again, because your session has expired. Don't worry: Your " "submission has been saved." @@ -812,7 +812,7 @@ msgstr "" "Si us plau, identifiquis de nou doncs la seva sessió ha expirat. No es " "preocupi, el seu enviament està emmagatzemat." -#: contrib/admin/views/decorators.py:69 +#: contrib/admin/views/decorators.py:70 msgid "" "Looks like your browser isn't configured to accept cookies. Please enable " "cookies, reload this page, and try again." @@ -821,247 +821,247 @@ msgstr "" "'cookies' (galetes). Si us plau, habiliti les 'cookies', recarregui aquesta " "pàgina i provi-ho de nou. " -#: contrib/admin/views/decorators.py:83 +#: contrib/admin/views/decorators.py:84 msgid "Usernames cannot contain the '@' character." msgstr "Els noms d'usuari no poden contenir el caracter '@'." -#: contrib/admin/views/decorators.py:85 +#: contrib/admin/views/decorators.py:86 #, python-format msgid "Your e-mail address is not your username. Try '%s' instead." msgstr "" "La seva adreça de correu no és el seu nom d'usuari. Provi '%s' en tot cas." -#: contrib/admin/views/doc.py:47 contrib/admin/views/doc.py:49 -#: contrib/admin/views/doc.py:51 +#: contrib/admin/views/doc.py:48 contrib/admin/views/doc.py:50 +#: contrib/admin/views/doc.py:52 msgid "tag:" msgstr "etiqueta:" -#: contrib/admin/views/doc.py:78 contrib/admin/views/doc.py:80 -#: contrib/admin/views/doc.py:82 +#: contrib/admin/views/doc.py:79 contrib/admin/views/doc.py:81 +#: contrib/admin/views/doc.py:83 msgid "filter:" msgstr "filtre:" -#: contrib/admin/views/doc.py:136 contrib/admin/views/doc.py:138 -#: contrib/admin/views/doc.py:140 +#: contrib/admin/views/doc.py:137 contrib/admin/views/doc.py:139 +#: contrib/admin/views/doc.py:141 msgid "view:" msgstr "vista:" -#: contrib/admin/views/doc.py:165 +#: contrib/admin/views/doc.py:166 #, python-format msgid "App %r not found" msgstr "La aplicació %r no s'ha pogut trobar" -#: contrib/admin/views/doc.py:172 +#: contrib/admin/views/doc.py:173 #, python-format msgid "Model %(name)r not found in app %(label)r" msgstr "El model %(name)r no s'ha trobat en la aplicació %(label)r" -#: contrib/admin/views/doc.py:184 +#: contrib/admin/views/doc.py:185 #, python-format msgid "the related `%(label)s.%(type)s` object" msgstr "el objecte relacionat `%(label)s.%(type)s`" -#: contrib/admin/views/doc.py:184 contrib/admin/views/doc.py:206 -#: contrib/admin/views/doc.py:220 contrib/admin/views/doc.py:225 +#: contrib/admin/views/doc.py:185 contrib/admin/views/doc.py:207 +#: contrib/admin/views/doc.py:221 contrib/admin/views/doc.py:226 msgid "model:" msgstr "model:" -#: contrib/admin/views/doc.py:215 +#: contrib/admin/views/doc.py:216 #, python-format msgid "related `%(label)s.%(name)s` objects" msgstr "objectes relacionats `%(label)s.%(name)s`" -#: contrib/admin/views/doc.py:220 +#: contrib/admin/views/doc.py:221 #, python-format msgid "all %s" msgstr "tots %s" -#: contrib/admin/views/doc.py:225 +#: contrib/admin/views/doc.py:226 #, python-format msgid "number of %s" msgstr "nombre de %s" -#: contrib/admin/views/doc.py:230 +#: contrib/admin/views/doc.py:231 #, python-format msgid "Fields on %s objects" msgstr "Camps en objectes %s" -#: contrib/admin/views/doc.py:292 contrib/admin/views/doc.py:303 -#: contrib/admin/views/doc.py:305 contrib/admin/views/doc.py:311 -#: contrib/admin/views/doc.py:312 contrib/admin/views/doc.py:314 +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:304 +#: contrib/admin/views/doc.py:306 contrib/admin/views/doc.py:312 +#: contrib/admin/views/doc.py:313 contrib/admin/views/doc.py:315 msgid "Integer" msgstr "Enter" -#: contrib/admin/views/doc.py:293 +#: contrib/admin/views/doc.py:294 msgid "Boolean (Either True or False)" msgstr "Booleà (Verdader o Fals)" -#: contrib/admin/views/doc.py:294 contrib/admin/views/doc.py:313 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:314 #, python-format msgid "String (up to %(max_length)s)" msgstr "Cadena (de fins a %(max_length)s)" -#: contrib/admin/views/doc.py:295 +#: contrib/admin/views/doc.py:296 msgid "Comma-separated integers" msgstr "Enters separats per comes" -#: contrib/admin/views/doc.py:296 +#: contrib/admin/views/doc.py:297 msgid "Date (without time)" msgstr "Data (sense hora)" -#: contrib/admin/views/doc.py:297 +#: contrib/admin/views/doc.py:298 msgid "Date (with time)" msgstr "Data (amb hora)" -#: contrib/admin/views/doc.py:298 +#: contrib/admin/views/doc.py:299 msgid "Decimal number" msgstr "Número decimal" -#: contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:300 msgid "E-mail address" msgstr "Adreça de correu electrònic" -#: contrib/admin/views/doc.py:300 contrib/admin/views/doc.py:301 -#: contrib/admin/views/doc.py:304 +#: contrib/admin/views/doc.py:301 contrib/admin/views/doc.py:302 +#: contrib/admin/views/doc.py:305 msgid "File path" msgstr "Ruta del fitxer" -#: contrib/admin/views/doc.py:302 +#: contrib/admin/views/doc.py:303 msgid "Floating point number" msgstr "Número amb punt de coma flotant" -#: contrib/admin/views/doc.py:306 contrib/comments/models.py:85 +#: contrib/admin/views/doc.py:307 contrib/comments/models.py:85 msgid "IP address" msgstr "Adreça IP" -#: contrib/admin/views/doc.py:308 +#: contrib/admin/views/doc.py:309 msgid "Boolean (Either True, False or None)" msgstr "Booleà (Verdader, Fals o 'None' (cap))" -#: contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 msgid "Relation to parent model" msgstr "Relació amb el model pare" -#: contrib/admin/views/doc.py:310 +#: contrib/admin/views/doc.py:311 msgid "Phone number" msgstr "Número de telèfon" -#: contrib/admin/views/doc.py:315 +#: contrib/admin/views/doc.py:316 msgid "Text" msgstr "Texte" -#: contrib/admin/views/doc.py:316 +#: contrib/admin/views/doc.py:317 msgid "Time" msgstr "Hora" -#: contrib/admin/views/doc.py:317 contrib/flatpages/models.py:7 +#: contrib/admin/views/doc.py:318 contrib/flatpages/models.py:7 msgid "URL" msgstr "URL" -#: contrib/admin/views/doc.py:318 +#: contrib/admin/views/doc.py:319 msgid "U.S. state (two uppercase letters)" msgstr "Estat dels E.U.A. (dos lletres majúscules)" -#: contrib/admin/views/doc.py:319 +#: contrib/admin/views/doc.py:320 msgid "XML text" msgstr "Texte XML" -#: contrib/admin/views/doc.py:345 +#: contrib/admin/views/doc.py:346 #, python-format msgid "%s does not appear to be a urlpattern object" msgstr "%s no sembla ser un objecte 'urlpattern'" -#: contrib/admin/views/main.py:230 +#: contrib/admin/views/main.py:233 msgid "Site administration" msgstr "Lloc administratiu" -#: contrib/admin/views/main.py:278 contrib/admin/views/main.py:363 +#: contrib/admin/views/main.py:280 contrib/admin/views/main.py:365 #, python-format msgid "You may add another %s below." msgstr "Pot afegir un altre %s a baix." -#: contrib/admin/views/main.py:296 +#: contrib/admin/views/main.py:298 #, python-format msgid "Add %s" msgstr "Afegir %s" -#: contrib/admin/views/main.py:342 +#: contrib/admin/views/main.py:344 #, python-format msgid "Added %s." msgstr "Agregat %s." -#: contrib/admin/views/main.py:342 contrib/admin/views/main.py:344 -#: contrib/admin/views/main.py:346 core/validators.py:283 +#: contrib/admin/views/main.py:344 contrib/admin/views/main.py:346 +#: contrib/admin/views/main.py:348 core/validators.py:283 #: db/models/manipulators.py:309 msgid "and" msgstr "i" -#: contrib/admin/views/main.py:344 +#: contrib/admin/views/main.py:346 #, python-format msgid "Changed %s." msgstr "Modificat %s." -#: contrib/admin/views/main.py:346 +#: contrib/admin/views/main.py:348 #, python-format msgid "Deleted %s." msgstr "Eliminat %s." -#: contrib/admin/views/main.py:349 +#: contrib/admin/views/main.py:351 msgid "No fields changed." msgstr "Cap camp canviat." -#: contrib/admin/views/main.py:352 +#: contrib/admin/views/main.py:354 #, python-format msgid "The %(name)s \"%(obj)s\" was changed successfully." msgstr "S'ha modificat amb èxist el/la %(name)s \"%(obj)s." -#: contrib/admin/views/main.py:360 +#: contrib/admin/views/main.py:362 #, python-format msgid "" "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." msgstr "" "S'ha agregat amb èxit el/la %(name)s \"%(obj)s\". Pot editar-lo de nou abaix." -#: contrib/admin/views/main.py:398 +#: contrib/admin/views/main.py:400 #, python-format msgid "Change %s" msgstr "Modificar %s" -#: contrib/admin/views/main.py:483 +#: contrib/admin/views/main.py:487 #, python-format msgid "One or more %(fieldname)s in %(name)s: %(obj)s" msgstr "Un o més %(fieldname)s en %(name)s: %(obj)s" -#: contrib/admin/views/main.py:488 +#: contrib/admin/views/main.py:492 #, python-format msgid "One or more %(fieldname)s in %(name)s:" msgstr "Un o més %(fieldname)s en %(name)s:" -#: contrib/admin/views/main.py:520 +#: contrib/admin/views/main.py:524 #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "El/la %(name)s \"%(obj)s\".ha estat eliminat amb èxit." -#: contrib/admin/views/main.py:523 +#: contrib/admin/views/main.py:527 msgid "Are you sure?" msgstr "Està segur?" -#: contrib/admin/views/main.py:545 +#: contrib/admin/views/main.py:549 #, python-format msgid "Change history: %s" msgstr "Modificar històric: %s" -#: contrib/admin/views/main.py:579 +#: contrib/admin/views/main.py:583 #, python-format msgid "Select %s" msgstr "Seleccioni %s" -#: contrib/admin/views/main.py:579 +#: contrib/admin/views/main.py:583 #, python-format msgid "Select %s to change" msgstr "Seleccioni %s per modificar" -#: contrib/admin/views/main.py:780 +#: contrib/admin/views/main.py:784 msgid "Database error" msgstr "Error de/en la base de dades" @@ -1622,72 +1622,72 @@ msgstr "n" msgid "rd" msgstr "r" -#: contrib/humanize/templatetags/humanize.py:50 +#: contrib/humanize/templatetags/humanize.py:52 #, python-format msgid "%(value).1f million" msgid_plural "%(value).1f million" msgstr[0] "%(value).1f milió" msgstr[1] "%(value).1f milions" -#: contrib/humanize/templatetags/humanize.py:53 +#: contrib/humanize/templatetags/humanize.py:55 #, python-format msgid "%(value).1f billion" msgid_plural "%(value).1f billion" msgstr[0] "%(value).1f bilió" msgstr[1] "%(value).1f bilions" -#: contrib/humanize/templatetags/humanize.py:56 +#: contrib/humanize/templatetags/humanize.py:58 #, python-format msgid "%(value).1f trillion" msgid_plural "%(value).1f trillion" msgstr[0] "%(value).1f trilió" msgstr[1] "%(value).1f trilions" -#: contrib/humanize/templatetags/humanize.py:71 +#: contrib/humanize/templatetags/humanize.py:74 msgid "one" msgstr "un" -#: contrib/humanize/templatetags/humanize.py:71 +#: contrib/humanize/templatetags/humanize.py:74 msgid "two" msgstr "dos" -#: contrib/humanize/templatetags/humanize.py:71 +#: contrib/humanize/templatetags/humanize.py:74 msgid "three" msgstr "tres" -#: contrib/humanize/templatetags/humanize.py:71 +#: contrib/humanize/templatetags/humanize.py:74 msgid "four" msgstr "cuatre" -#: contrib/humanize/templatetags/humanize.py:71 +#: contrib/humanize/templatetags/humanize.py:74 msgid "five" msgstr "cinc" -#: contrib/humanize/templatetags/humanize.py:71 +#: contrib/humanize/templatetags/humanize.py:74 msgid "six" msgstr "sis" -#: contrib/humanize/templatetags/humanize.py:71 +#: contrib/humanize/templatetags/humanize.py:74 msgid "seven" msgstr "set" -#: contrib/humanize/templatetags/humanize.py:71 +#: contrib/humanize/templatetags/humanize.py:74 msgid "eight" msgstr "vuit" -#: contrib/humanize/templatetags/humanize.py:71 +#: contrib/humanize/templatetags/humanize.py:74 msgid "nine" msgstr "nou" -#: contrib/humanize/templatetags/humanize.py:90 +#: contrib/humanize/templatetags/humanize.py:94 msgid "today" msgstr "avui" -#: contrib/humanize/templatetags/humanize.py:92 +#: contrib/humanize/templatetags/humanize.py:96 msgid "tomorrow" msgstr "demà" -#: contrib/humanize/templatetags/humanize.py:94 +#: contrib/humanize/templatetags/humanize.py:98 msgid "yesterday" msgstr "ahir" @@ -3024,6 +3024,50 @@ msgstr "" "Introdueixi un número vàlid de la Seguretat Social dels E.U.A. en el format " "XXX-XX-XXXX." +#: contrib/localflavor/za/forms.py:22 +msgid "Enter a valid South African ID number" +msgstr "Introdueixi un número d'Identitat Sud Africà valid" + +#: contrib/localflavor/za/forms.py:57 +msgid "Enter a valid South African postal code" +msgstr "Introdueixi un codi postal Sud Africà vàlid." + +#: contrib/localflavor/za/za_provinces.py:4 +msgid "Eastern Cape" +msgstr "Eastern Cape" + +#: contrib/localflavor/za/za_provinces.py:5 +msgid "Free State" +msgstr "Free State" + +#: contrib/localflavor/za/za_provinces.py:6 +msgid "Gauteng" +msgstr "Gauteng" + +#: contrib/localflavor/za/za_provinces.py:7 +msgid "KwaZulu-Natal" +msgstr "KwaZulu-Natal" + +#: contrib/localflavor/za/za_provinces.py:8 +msgid "Limpopo" +msgstr "Limpopo" + +#: contrib/localflavor/za/za_provinces.py:9 +msgid "Mpumalanga" +msgstr "Mpumalanga" + +#: contrib/localflavor/za/za_provinces.py:10 +msgid "Northern Cape" +msgstr "Northern Cape" + +#: contrib/localflavor/za/za_provinces.py:11 +msgid "North West" +msgstr "North West" + +#: contrib/localflavor/za/za_provinces.py:12 +msgid "Western Cape" +msgstr "Western Cape" + #: contrib/redirects/models.py:7 msgid "redirect from" msgstr "redreçar des de" @@ -3056,23 +3100,23 @@ msgstr "redreçament" msgid "redirects" msgstr "redreçaments" -#: contrib/sessions/models.py:80 +#: contrib/sessions/models.py:46 msgid "session key" msgstr "clau de la sessió" -#: contrib/sessions/models.py:81 +#: contrib/sessions/models.py:47 msgid "session data" msgstr "dades de la sessió" -#: contrib/sessions/models.py:82 +#: contrib/sessions/models.py:48 msgid "expire date" msgstr "data de caducitat" -#: contrib/sessions/models.py:87 +#: contrib/sessions/models.py:53 msgid "session" msgstr "sessió" -#: contrib/sessions/models.py:88 +#: contrib/sessions/models.py:54 msgid "sessions" msgstr "sessions" @@ -3141,7 +3185,7 @@ msgstr "No s'admeten caracters no numèrics." msgid "This value can't be comprised solely of digits." msgstr "Aquest valor no pot contenir només dígits." -#: core/validators.py:128 newforms/fields.py:157 +#: core/validators.py:128 newforms/fields.py:151 msgid "Enter a whole number." msgstr "Introdueixi un número sencer." @@ -3170,17 +3214,17 @@ msgstr "Introdueixi una hora vàlida en el format HH:MM." msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." msgstr "Introdueixi un data/hora vàlida en format YYYY-MM-DD HH:MM." -#: core/validators.py:170 newforms/fields.py:408 +#: core/validators.py:170 newforms/fields.py:402 msgid "Enter a valid e-mail address." msgstr "Introdueixi una adreça de correu vàlida." -#: core/validators.py:182 core/validators.py:474 newforms/fields.py:438 -#: oldforms/__init__.py:686 +#: core/validators.py:182 core/validators.py:474 newforms/fields.py:432 +#: oldforms/__init__.py:687 msgid "No file was submitted. Check the encoding type on the form." msgstr "" "No s'ha enviat cap fitxer. Comprovi el tipus de codificació del formulari." -#: core/validators.py:193 newforms/fields.py:462 +#: core/validators.py:193 newforms/fields.py:456 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -3431,7 +3475,7 @@ msgstr "Ja existeix %(optname)s amb auqest %(fieldname)s." #: db/models/fields/__init__.py:161 db/models/fields/__init__.py:318 #: db/models/fields/__init__.py:735 db/models/fields/__init__.py:746 -#: newforms/fields.py:45 newforms/models.py:211 oldforms/__init__.py:373 +#: newforms/fields.py:45 oldforms/__init__.py:374 msgid "This field is required." msgstr "Aquest camp és obligatori." @@ -3484,150 +3528,150 @@ msgstr[1] "" msgid "Enter a valid value." msgstr "Introdueixi un valor vàlid." -#: newforms/fields.py:129 +#: newforms/fields.py:123 #, python-format msgid "Ensure this value has at most %(max)d characters (it has %(length)d)." msgstr "" "Asseguris de que el valor té com a màxim %(max)d caràcters (en té %(length)" "d)." -#: newforms/fields.py:130 +#: newforms/fields.py:124 #, python-format msgid "Ensure this value has at least %(min)d characters (it has %(length)d)." msgstr "" "Asseguris de que el valor té com a mínim %(min)d caràcters (en té %(length)" "d)." -#: newforms/fields.py:158 newforms/fields.py:187 newforms/fields.py:216 +#: newforms/fields.py:152 newforms/fields.py:181 newforms/fields.py:210 #, python-format msgid "Ensure this value is less than or equal to %s." msgstr "Aquest valor ha de ser menor o igual a %s." -#: newforms/fields.py:159 newforms/fields.py:188 newforms/fields.py:217 +#: newforms/fields.py:153 newforms/fields.py:182 newforms/fields.py:211 #, python-format msgid "Ensure this value is greater than or equal to %s." msgstr "Asseguris de que aquest valor sigui superior o igual a %s." -#: newforms/fields.py:186 newforms/fields.py:215 +#: newforms/fields.py:180 newforms/fields.py:209 msgid "Enter a number." msgstr "Introdueixi un número." -#: newforms/fields.py:218 +#: newforms/fields.py:212 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Asseguris de que no hi ha més de %s dígits en total." -#: newforms/fields.py:219 +#: newforms/fields.py:213 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Asseguris de que no hi ha més de %s decimals." -#: newforms/fields.py:220 +#: newforms/fields.py:214 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Asseguris de que no hia ha més de %s dígits decimals." -#: newforms/fields.py:268 newforms/fields.py:724 +#: newforms/fields.py:262 newforms/fields.py:719 msgid "Enter a valid date." msgstr "Introdueixi una data vàlida." -#: newforms/fields.py:301 newforms/fields.py:725 +#: newforms/fields.py:295 newforms/fields.py:720 msgid "Enter a valid time." msgstr "Introdueixi una hora vàlida." -#: newforms/fields.py:340 +#: newforms/fields.py:334 msgid "Enter a valid date/time." msgstr "Introdueixi una data/hora vàlides." -#: newforms/fields.py:439 +#: newforms/fields.py:433 msgid "No file was submitted." msgstr "No s'ha enviat cap fitxer." -#: newforms/fields.py:440 oldforms/__init__.py:688 +#: newforms/fields.py:434 oldforms/__init__.py:689 msgid "The submitted file is empty." msgstr "El fitxer enviat està buit." -#: newforms/fields.py:498 +#: newforms/fields.py:492 msgid "Enter a valid URL." msgstr "Introdueixi una URL vàlida." -#: newforms/fields.py:499 +#: newforms/fields.py:493 msgid "This URL appears to be a broken link." msgstr "Aquesta URL sembla ser un enllaç trencat." -#: newforms/fields.py:560 newforms/models.py:194 +#: newforms/fields.py:555 newforms/models.py:155 msgid "Select a valid choice. That choice is not one of the available choices." msgstr "" "Esculli una opció vàlida; Aquesta opció no és una de les opcions disponibles." -#: newforms/fields.py:599 +#: newforms/fields.py:594 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Esculli una opció vàlida. %(value)s no és una de les opcions vàlides." -#: newforms/fields.py:600 newforms/fields.py:662 newforms/models.py:215 +#: newforms/fields.py:595 newforms/fields.py:657 newforms/models.py:215 msgid "Enter a list of values." msgstr "Introdueixi una llista de valors." -#: newforms/fields.py:753 +#: newforms/fields.py:748 msgid "Enter a valid IPv4 address." msgstr "Introdueixi una adreça IPv4 vàlida." -#: newforms/models.py:221 +#: newforms/models.py:216 #, python-format msgid "Select a valid choice. %s is not one of the available choices." msgstr "Esculli una opció vàlida; %s' no és una de les opcions vàlides." -#: oldforms/__init__.py:408 +#: oldforms/__init__.py:409 #, python-format msgid "Ensure your text is less than %s character." msgid_plural "Ensure your text is less than %s characters." msgstr[0] "Asseguris de que el seu texte té menys de %s caracter." msgstr[1] "Asseguris de que el seu texte té menys de %s caracters." -#: oldforms/__init__.py:413 +#: oldforms/__init__.py:414 msgid "Line breaks are not allowed here." msgstr "No es permeten salts de línia." -#: oldforms/__init__.py:511 oldforms/__init__.py:585 oldforms/__init__.py:624 +#: oldforms/__init__.py:512 oldforms/__init__.py:586 oldforms/__init__.py:625 #, python-format msgid "Select a valid choice; '%(data)s' is not in %(choices)s." msgstr "Esculli una opció vàlida; %(data)s' no està dintre de %(choices)s." -#: oldforms/__init__.py:744 +#: oldforms/__init__.py:745 msgid "Enter a whole number between -32,768 and 32,767." msgstr "Introdueixi un número enter entre -32,768 i 32,767." -#: oldforms/__init__.py:754 +#: oldforms/__init__.py:755 msgid "Enter a positive number." msgstr "Introdueixi un número positiu." -#: oldforms/__init__.py:764 +#: oldforms/__init__.py:765 msgid "Enter a whole number between 0 and 32,767." msgstr "Introdueixi un número entre 0 i 32,767." -#: template/defaultfilters.py:555 +#: template/defaultfilters.py:658 msgid "yes,no,maybe" msgstr "si,no,potser" -#: template/defaultfilters.py:585 +#: template/defaultfilters.py:689 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d byte" msgstr[1] "%(size)d bytes" -#: template/defaultfilters.py:587 +#: template/defaultfilters.py:691 #, python-format msgid "%.1f KB" msgstr "%.1f KB" -#: template/defaultfilters.py:589 +#: template/defaultfilters.py:693 #, python-format msgid "%.1f MB" msgstr "%.1f MB" -#: template/defaultfilters.py:590 +#: template/defaultfilters.py:694 #, python-format msgid "%.1f GB" msgstr "%.1f GB" @@ -3890,23 +3934,23 @@ msgstr "%(number)d %(type)s" msgid ", %(number)d %(type)s" msgstr ", %(number)d %(type)s" -#: utils/translation/trans_real.py:395 +#: utils/translation/trans_real.py:399 msgid "DATE_FORMAT" msgstr "F j, Y" -#: utils/translation/trans_real.py:396 +#: utils/translation/trans_real.py:400 msgid "DATETIME_FORMAT" msgstr "F j, Y, H:i" -#: utils/translation/trans_real.py:397 +#: utils/translation/trans_real.py:401 msgid "TIME_FORMAT" msgstr "H:i" -#: utils/translation/trans_real.py:413 +#: utils/translation/trans_real.py:417 msgid "YEAR_MONTH_FORMAT" msgstr "j de/d' F del Y" -#: utils/translation/trans_real.py:414 +#: utils/translation/trans_real.py:418 msgid "MONTH_DAY_FORMAT" msgstr "j de/d' F del Y" @@ -3924,43 +3968,3 @@ msgstr "El/La %(verbose_name)s s'ha actualtzat amb èxit." #, python-format msgid "The %(verbose_name)s was deleted." msgstr "El %(verbose_name)s s'ha eliminat." - -#~ msgid "" -#~ "This comment was posted by a user who has posted fewer than %(count)s " -#~ "comment:\n" -#~ "\n" -#~ "%(text)sThis comment was posted by a user who has posted fewer than %" -#~ "(count)s comments:\n" -#~ "\n" -#~ "%(text)s" -#~ msgstr "" -#~ "Aquest comentari el va enviar un usuari que ha enviat menys de %(count)s " -#~ "comentari:\n" -#~ "\n" -#~ "%(text)sAquest comentari el va enviar un usuari que ha enviat menys de %" -#~ "(count)s comentaris:\n" -#~ "\n" -#~ "%(text)s" - -#~ msgid "AnonymousUser" -#~ msgstr "AnonymousUser" - -#~ msgid "" -#~ "Please enter a valid decimal number with a whole part of at most %s digit." -#~ "Please enter a valid decimal number with a whole part of at most %s " -#~ "digits." -#~ msgstr "" -#~ "Si us plau, introdueixi un número decimal vàlid amb la part entera amb " -#~ "com a màxim %s dígit.Si us plau, introdueixi un número decimal vàlid amb " -#~ "la part entera amb com a màxim %s dígits." - -#~ msgid "" -#~ "Please enter a valid decimal number with at most %s decimal place.Please " -#~ "enter a valid decimal number with at most %s decimal places." -#~ msgstr "" -#~ "Si us plau, introdueixi un número decimal vàlid amb no més de %s dígit en " -#~ "la part decimal.Si us plau, introdueixi un número decimal vàlid amb no " -#~ "més de %s dígits en la part decimal." - -#~ msgid "%d milliseconds" -#~ msgstr "%d milisegons" diff --git a/django/conf/locale/ca/LC_MESSAGES/djangojs.mo b/django/conf/locale/ca/LC_MESSAGES/djangojs.mo index 581b176be4f56b34885373c6b066a03aec2a0f62..ddd78596752a58314218a376f9665c6f9194e890 100644 GIT binary patch delta 363 zcmXZVJxjwt90u^KCe_BK6c^WH;GmGn zk0JUET>S`6&Tc|oeNsF9c>b@``h)%SIl8M5GFc>~N(iZ$|2AO*c@5U!I$VJaHlaT^ zfOX^xxC*b}GQ5RL@E)GRM_AtZg5{rYh?nHYB<vKgcjCjjoobr~(wl^7P%sLTS?(|ZjvRLFg^=Mb+T1B)Q>uc(ZS#SkHCr@QI z5J~3tb<)P33Wp+;PEU%&qh;&;MaR_di%uN5It|-aa11 Xmoi4?+}$cZjJN8cR5GQ-*qr_Wy<0}~ delta 314 zcmeC?{l+ulPJJ;W14A1#0|Ofa1H&&C2+hdKz`zIOa{y^hAT180xq-AIkQM;ax=`8< zNb>;s{yohf?HxydSbDHO-X7_YF=h)cD@6grI(zaYX{Vp qmtLBfo~j#?nwV>)pkR|)JUNb~fIZC9SHWD*VDfF2Et99ReggoRCpZ@X diff --git a/django/conf/locale/ca/LC_MESSAGES/djangojs.po b/django/conf/locale/ca/LC_MESSAGES/djangojs.po index 3ae0e9bad8..47f41acdda 100644 --- a/django/conf/locale/ca/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/ca/LC_MESSAGES/djangojs.po @@ -1,19 +1,21 @@ +# translation of djangojs.po to español # translation of djangojs.po to # Catalan translation for the django-admin JS files. # This file is distributed under the same license as the Django package. # +# Antoni Aloy , 2007. msgid "" msgstr "" "Project-Id-Version: djangojs\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-20 18:25+0200\n" -"PO-Revision-Date: 2007-06-25 17:47+0200\n" -"Last-Translator: Marc Fargas \n" -"Language-Team: \n" +"PO-Revision-Date: 2007-12-01 12:06+0100\n" +"Last-Translator: Antoni Aloy \n" +"Language-Team: español \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: VIM 7.0\n" +"X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: contrib/admin/media/js/SelectFilter2.js:33 @@ -51,8 +53,7 @@ msgstr "Deseleccionar tots" msgid "" "January February March April May June July August September October November " "December" -msgstr "" -"Febrer Març Abril Maig Juny Juliol Agost Setembre Octubre Novembre Desembre" +msgstr "Gener Febrer Març Abril Maig Juny Juliol Agost Setembre Octubre Novembre Desembre" #: contrib/admin/media/js/dateparse.js:33 msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" @@ -117,3 +118,4 @@ msgstr "Mostrar" #: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 msgid "Hide" msgstr "Ocultar" + diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index a4e6269b6f..e45b6ed0bb 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -114,7 +114,7 @@ def result_headers(cl): yield {"text": header, "sortable": True, "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}), - "class_attrib": (th_classes and ' class="%s"' % ' '.join(th_classes) or '')} + "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')} def _boolean_icon(field_val): BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 33f92dc854..4b1590264b 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -322,7 +322,7 @@ class AnonymousUser(object): id = None username = '' is_staff = False - is_active = True + is_active = False is_superuser = False _groups = EmptyManager() _user_permissions = EmptyManager() diff --git a/django/contrib/auth/tests.py b/django/contrib/auth/tests.py index 329049c546..d369ac524c 100644 --- a/django/contrib/auth/tests.py +++ b/django/contrib/auth/tests.py @@ -16,9 +16,21 @@ False >>> u2 = User.objects.create_user('testuser2', 'test2@example.com') >>> u2.has_usable_password() False + +>>> u.is_authenticated() +True +>>> u.is_staff +False +>>> u.is_active +True + >>> a = AnonymousUser() +>>> a.is_authenticated() +False >>> a.is_staff False +>>> a.is_active +False >>> a.groups.all() [] >>> a.user_permissions.all() diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index b738a269ec..e496ed1af8 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -16,18 +16,18 @@ class GenericForeignKey(object): Provides a generic relation to any object through content-type/object-id fields. """ - + def __init__(self, ct_field="content_type", fk_field="object_id"): self.ct_field = ct_field self.fk_field = fk_field - + def contribute_to_class(self, cls, name): - # Make sure the fields exist (these raise FieldDoesNotExist, + # Make sure the fields exist (these raise FieldDoesNotExist, # which is a fine error to raise here) self.name = name self.model = cls self.cache_attr = "_%s_cache" % name - + # For some reason I don't totally understand, using weakrefs here doesn't work. dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False) @@ -35,18 +35,18 @@ class GenericForeignKey(object): setattr(cls, name, self) def instance_pre_init(self, signal, sender, args, kwargs): - # Handle initalizing an object with the generic FK instaed of - # content-type/object-id fields. + # Handle initalizing an object with the generic FK instaed of + # content-type/object-id fields. if self.name in kwargs: value = kwargs.pop(self.name) kwargs[self.ct_field] = self.get_content_type(value) kwargs[self.fk_field] = value._get_pk_val() - + def get_content_type(self, obj): # Convenience function using get_model avoids a circular import when using this model ContentType = get_model("contenttypes", "contenttype") return ContentType.objects.get_for_model(obj) - + def __get__(self, instance, instance_type=None): if instance is None: raise AttributeError, u"%s must be accessed via instance" % self.name @@ -77,21 +77,21 @@ class GenericForeignKey(object): setattr(instance, self.ct_field, ct) setattr(instance, self.fk_field, fk) setattr(instance, self.cache_attr, value) - + class GenericRelation(RelatedField, Field): """Provides an accessor to generic related objects (i.e. comments)""" def __init__(self, to, **kwargs): kwargs['verbose_name'] = kwargs.get('verbose_name', None) - kwargs['rel'] = GenericRel(to, + kwargs['rel'] = GenericRel(to, related_name=kwargs.pop('related_name', None), limit_choices_to=kwargs.pop('limit_choices_to', None), symmetrical=kwargs.pop('symmetrical', True)) - + # Override content-type/object-id field names on the related class self.object_id_field_name = kwargs.pop("object_id_field", "object_id") - self.content_type_field_name = kwargs.pop("content_type_field", "content_type") - + self.content_type_field_name = kwargs.pop("content_type_field", "content_type") + kwargs['blank'] = True kwargs['editable'] = False kwargs['serialize'] = False @@ -116,9 +116,9 @@ class GenericRelation(RelatedField, Field): def m2m_column_name(self): return self.object_id_field_name - + def m2m_reverse_name(self): - return self.object_id_field_name + return self.model._meta.pk.column def contribute_to_class(self, cls, name): super(GenericRelation, self).contribute_to_class(cls, name) @@ -131,13 +131,13 @@ class GenericRelation(RelatedField, Field): def contribute_to_related_class(self, cls, related): pass - + def set_attributes_from_rel(self): pass def get_internal_type(self): return "ManyToManyField" - + class ReverseGenericRelatedObjectsDescriptor(object): """ This class provides the functionality that makes the related-object @@ -193,12 +193,12 @@ def create_generic_related_manager(superclass): Factory function for a manager that subclasses 'superclass' (which is a Manager) and adds behavior for generic related objects. """ - + class GenericRelatedObjectManager(superclass): def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, join_table=None, source_col_name=None, target_col_name=None, content_type=None, content_type_field_name=None, object_id_field_name=None): - + super(GenericRelatedObjectManager, self).__init__() self.core_filters = core_filters or {} self.model = model @@ -212,10 +212,10 @@ def create_generic_related_manager(superclass): self.content_type_field_name = content_type_field_name self.object_id_field_name = object_id_field_name self.pk_val = self.instance._get_pk_val() - + def get_query_set(self): query = { - '%s__pk' % self.content_type_field_name : self.content_type.id, + '%s__pk' % self.content_type_field_name : self.content_type.id, '%s__exact' % self.object_id_field_name : self.pk_val, } return superclass.get_query_set(self).filter(**query) diff --git a/django/contrib/localflavor/mx/__init__.py b/django/contrib/localflavor/mx/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/mx/forms.py b/django/contrib/localflavor/mx/forms.py new file mode 100644 index 0000000000..5e42cca7d7 --- /dev/null +++ b/django/contrib/localflavor/mx/forms.py @@ -0,0 +1,14 @@ +""" +Mexican-specific form helpers. +""" + +from django.newforms.fields import Select + +class MXStateSelect(Select): + """ + A Select widget that uses a list of Mexican states as its choices. + """ + def __init__(self, attrs=None): + from mx_states import STATE_CHOICES + super(MXStateSelect, self).__init__(attrs, choices=STATE_CHOICES) + diff --git a/django/contrib/localflavor/mx/mx_states.py b/django/contrib/localflavor/mx/mx_states.py new file mode 100644 index 0000000000..eed1700efb --- /dev/null +++ b/django/contrib/localflavor/mx/mx_states.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +""" +A list of Mexican states for use as `choices` in a formfield. + +This exists in this standalone file so that it's only imported into memory +when explicitly needed. +""" + +from django.utils.translation import ugettext_lazy as _ + +STATE_CHOICES = ( + ('AGU', _(u'Aguascalientes')), + ('BCN', _(u'Baja California')), + ('BCS', _(u'Baja California Sur')), + ('CAM', _(u'Campeche')), + ('CHH', _(u'Chihuahua')), + ('CHP', _(u'Chiapas')), + ('COA', _(u'Coahuila')), + ('COL', _(u'Colima')), + ('DIF', _(u'Distrito Federal')), + ('DUR', _(u'Durango')), + ('GRO', _(u'Guerrero')), + ('GUA', _(u'Guanajuato')), + ('HID', _(u'Hidalgo')), + ('JAL', _(u'Jalisco')), + ('MEX', _(u'Estado de México')), + ('MIC', _(u'Michoacán')), + ('MOR', _(u'Morelos')), + ('NAY', _(u'Nayarit')), + ('NLE', _(u'Nuevo León')), + ('OAX', _(u'Oaxaca')), + ('PUE', _(u'Puebla')), + ('QUE', _(u'Querétaro')), + ('ROO', _(u'Quintana Roo')), + ('SIN', _(u'Sinaloa')), + ('SLP', _(u'San Luis Potosí')), + ('SON', _(u'Sonora')), + ('TAB', _(u'Tabasco')), + ('TAM', _(u'Tamaulipas')), + ('TLA', _(u'Tlaxcala')), + ('VER', _(u'Veracruz')), + ('YUC', _(u'Yucatán')), + ('ZAC', _(u'Zacatecas')), +) + diff --git a/django/contrib/localflavor/uk/forms.py b/django/contrib/localflavor/uk/forms.py index 84d6c0e157..2b162230de 100644 --- a/django/contrib/localflavor/uk/forms.py +++ b/django/contrib/localflavor/uk/forms.py @@ -2,7 +2,7 @@ UK-specific Form helpers """ -from django.newforms.fields import RegexField +from django.newforms.fields import RegexField, Select from django.utils.translation import ugettext class UKPostcodeField(RegexField): @@ -17,3 +17,19 @@ class UKPostcodeField(RegexField): max_length=None, min_length=None, error_message=ugettext(u'Enter a postcode. A space is required between the two postcode parts.'), *args, **kwargs) + +class UKCountySelect(Select): + """ + A Select widget that uses a list of UK Counties/Regions as its choices. + """ + def __init__(self, attrs=None): + from uk_regions import UK_REGION_CHOICES + super(UKCountySelect, self).__init__(attrs, choices=UK_REGION_CHOICES) + +class UKNationSelect(Select): + """ + A Select widget that uses a list of UK Nations as its choices. + """ + def __init__(self, attrs=None): + from uk_regions import UK_NATIONS_CHOICES + super(UKNationSelect, self).__init__(attrs, choices=UK_NATIONS_CHOICES) diff --git a/django/contrib/localflavor/uk/uk_regions.py b/django/contrib/localflavor/uk/uk_regions.py new file mode 100644 index 0000000000..3e2c16ef5d --- /dev/null +++ b/django/contrib/localflavor/uk/uk_regions.py @@ -0,0 +1,97 @@ +""" +Sources: + English regions: http://www.statistics.gov.uk/geography/downloads/31_10_01_REGION_names_and_codes_12_00.xls + Northern Ireland regions: http://en.wikipedia.org/wiki/List_of_Irish_counties_by_area + Welsh regions: http://en.wikipedia.org/wiki/Preserved_counties_of_Wales + Scottish regions: http://en.wikipedia.org/wiki/Regions_and_districts_of_Scotland +""" +from django.utils.translation import ugettext as _ + +ENGLAND_REGION_CHOICES = ( + ("Bedfordshire", _("Bedfordshire")), + ("Buckinghamshire", _("Buckinghamshire")), + ("Cambridgeshire", ("Cambridgeshire")), + ("Cheshire", _("Cheshire")), + ("Cornwall and Isles of Scilly", _("Cornwall and Isles of Scilly")), + ("Cumbria", _("Cumbria")), + ("Derbyshire", _("Derbyshire")), + ("Devon", _("Devon")), + ("Dorset", _("Dorset")), + ("Durham", _("Durham")), + ("East Sussex", _("East Sussex")), + ("Essex", _("Essex")), + ("Gloucestershire", _("Gloucestershire")), + ("Greater London", _("Greater London")), + ("Greater Manchester", _("Greater Manchester")), + ("Hampshire", _("Hampshire")), + ("Hertfordshire", _("Hertfordshire")), + ("Kent", _("Kent")), + ("Lancashire", _("Lancashire")), + ("Leicestershire", _("Leicestershire")), + ("Lincolnshire", _("Lincolnshire")), + ("Merseyside", _("Merseyside")), + ("Norfolk", _("Norfolk")), + ("North Yorkshire", _("North Yorkshire")), + ("Northamptonshire", _("Northamptonshire")), + ("Northumberland", _("Northumberland")), + ("Nottinghamshire", _("Nottinghamshire")), + ("Oxfordshire", _("Oxfordshire")), + ("Shropshire", _("Shropshire")), + ("Somerset", _("Somerset")), + ("South Yorkshire", _("South Yorkshire")), + ("Staffordshire", _("Staffordshire")), + ("Suffolk", _("Suffolk")), + ("Surrey", _("Surrey")), + ("Tyne and Wear", _("Tyne and Wear")), + ("Warwickshire", _("Warwickshire")), + ("West Midlands", _("West Midlands")), + ("West Sussex", _("West Sussex")), + ("West Yorkshire", _("West Yorkshire")), + ("Wiltshire", _("Wiltshire")), + ("Worcestershire", _("Worcestershire")), +) + +NORTHERN_IRELAND_REGION_CHOICES = ( + ("County Antrim", _("County Antrim")), + ("County Armagh", _("County Armagh")), + ("County Down", _("County Down")), + ("County Fermanagh", _("County Down")), + ("County Londonderry", _("County Londonderry")), + ("County Tyrone", _("County Tyrone")), +) + +WALES_REGION_CHOICES = ( + ("Clwyd", _("Clwyd")), + ("Dyfed", _("Dyfed")), + ("Gwent", _("Gwent")), + ("Gwynedd", _("Gwynedd")), + ("Mid Glamorgan", _("Mid Glamorgan")), + ("Powys", _("Powys")), + ("South Glamorgan", _("South Glamorgan")), + ("West Glamorgan", _("West Glamorgan")), +) + +SCOTTISH_REGION_CHOICES = ( + ("Borders", _("Borders")), + ("Central Scotland", _("Central Scotland")), + ("Dumfries and Galloway", _("Dumfries and Galloway")), + ("Fife", _("Fife")), + ("Grampian", _("Grampian")), + ("Highland", _("Highland")), + ("Lothian", _("Lothian")), + ("Orkney Islands", _("Orkney Islands")), + ("Shetland Islands", _("Shetland Islands")), + ("Strathclyde", _("Strathclyde")), + ("Tayside", _("Tayside")), + ("Western Isles", _("Western Isles")), +) + +UK_NATIONS_CHOICES = ( + ("England", _("England")), + ("Northern Ireland", _("Northern Ireland")), + ("Scotland", _("Scotland")), + ("Wales", _("Wales")), +) + +UK_REGION_CHOICES = ENGLAND_REGION_CHOICES + NORTHERN_IRELAND_REGION_CHOICES + WALES_REGION_CHOICES + SCOTTISH_REGION_CHOICES + diff --git a/django/contrib/localflavor/za/__init__.py b/django/contrib/localflavor/za/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/za/forms.py b/django/contrib/localflavor/za/forms.py new file mode 100644 index 0000000000..b04b7cf6ab --- /dev/null +++ b/django/contrib/localflavor/za/forms.py @@ -0,0 +1,57 @@ +""" +South Africa-specific Form helpers +""" + +from django.newforms import ValidationError +from django.newforms.fields import Field, RegexField, EMPTY_VALUES +from django.utils.checksums import luhn +from django.utils.translation import gettext as _ +import re +from datetime import date + +id_re = re.compile(r'^(?P\d\d)(?P\d\d)(?P
    \d\d)(?P\d{4})(?P\d{3})') + +class ZAIDField(Field): + """A form field for South African ID numbers -- the checksum is validated + using the Luhn checksum, and uses a simlistic (read: not entirely accurate) + check for the birthdate + """ + + def __init__(self, *args, **kwargs): + super(ZAIDField, self).__init__() + self.error_message = _(u'Enter a valid South African ID number') + + def clean(self, value): + # strip spaces and dashes + value = value.strip().replace(' ', '').replace('-', '') + + super(ZAIDField, self).clean(value) + + if value in EMPTY_VALUES: + return u'' + + match = re.match(id_re, value) + + if not match: + raise ValidationError(self.error_message) + + g = match.groupdict() + + try: + # The year 2000 is conveniently a leapyear. + # This algorithm will break in xx00 years which aren't leap years + # There is no way to guess the century of a ZA ID number + d = date(int(g['yy']) + 2000, int(g['mm']), int(g['dd'])) + except ValueError: + raise ValidationError(self.error_message) + + if not luhn(value): + raise ValidationError(self.error_message) + + return value + +class ZAPostCodeField(RegexField): + def __init__(self, *args, **kwargs): + super(ZAPostCodeField, self).__init__(r'^\d{4}$', + max_length=None, min_length=None, + error_message=_(u'Enter a valid South African postal code')) diff --git a/django/contrib/localflavor/za/za_provinces.py b/django/contrib/localflavor/za/za_provinces.py new file mode 100644 index 0000000000..0bc6fe14b3 --- /dev/null +++ b/django/contrib/localflavor/za/za_provinces.py @@ -0,0 +1,13 @@ +from django.utils.translation import gettext_lazy as _ + +PROVINCE_CHOICES = ( + ('EC', _('Eastern Cape')), + ('FS', _('Free State')), + ('GP', _('Gauteng')), + ('KN', _('KwaZulu-Natal')), + ('LP', _('Limpopo')), + ('MP', _('Mpumalanga')), + ('NC', _('Northern Cape')), + ('NW', _('North West')), + ('WC', _('Western Cape')), +) diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index 13708fd26d..5d4f4786e1 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -32,7 +32,23 @@ def textile(value): return mark_safe(force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8'))) textile.is_safe = True -def markdown(value): +def markdown(value, arg=''): + """ + Runs Markdown over a given value, optionally using various + extensions python-markdown supports. + + Syntax:: + + {{ value|markdown:"extension1_name,extension2_name..." }} + + To enable safe mode, which strips raw HTML and only returns HTML + generated by actual Markdown syntax, pass "safe" as the first + extension in the list. + + If the version of Markdown in use does not support extensions, + they will be silently ignored. + + """ try: import markdown except ImportError: @@ -40,7 +56,18 @@ def markdown(value): raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed." return force_unicode(value) else: - return mark_safe(force_unicode(markdown.markdown(smart_str(value)))) + # markdown.version was first added in 1.6b. The only version of markdown + # to fully support extensions before 1.6b was the shortlived 1.6a. + if hasattr(markdown, 'version'): + extensions = [e for e in arg.split(",") if e] + if len(extensions) > 0 and extensions[0] == "safe": + extensions = extensions[1:] + safe_mode = True + else: + safe_mode = False + return mark_safe(force_unicode(markdown.markdown(smart_str(value), extensions, safe_mode=safe_mode))) + else: + return mark_safe(force_unicode(markdown.markdown(smart_str(value)))) markdown.is_safe = True def restructuredtext(value): diff --git a/django/contrib/markup/tests.py b/django/contrib/markup/tests.py index 6e7beaeb2f..9a96f8cda4 100644 --- a/django/contrib/markup/tests.py +++ b/django/contrib/markup/tests.py @@ -61,8 +61,15 @@ Paragraph 2 with a link_ t = Template("{{ rest_content|restructuredtext }}") rendered = t.render(Context(locals())).strip() if docutils: - self.assertEqual(rendered, """

    Paragraph 1

    + # Different versions of docutils return slightly different HTML + try: + # Docutils v0.4 and earlier + self.assertEqual(rendered, """

    Paragraph 1

    Paragraph 2 with a link

    """) + except AssertionError, e: + # Docutils from SVN (which will become 0.5) + self.assertEqual(rendered, """

    Paragraph 1

    +

    Paragraph 2 with a link

    """) else: self.assertEqual(rendered, rest_content) diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 192065a5f3..b8726fd2bd 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -51,6 +51,14 @@ class SessionBase(object): self.modified = self.modified or key in self._session return self._session.pop(key, *args) + def setdefault(self, key, value): + if key in self._session: + return self._session[key] + else: + self.modified = True + self._session[key] = value + return value + def set_test_cookie(self): self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py index 221db5cc60..a8c3c69b10 100644 --- a/django/contrib/sessions/backends/file.py +++ b/django/contrib/sessions/backends/file.py @@ -1,14 +1,23 @@ import os +import tempfile from django.conf import settings from django.contrib.sessions.backends.base import SessionBase -from django.core.exceptions import SuspiciousOperation +from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured class SessionStore(SessionBase): """ Implements a file based session store. """ def __init__(self, session_key=None): - self.storage_path = settings.SESSION_FILE_PATH + self.storage_path = getattr(settings, "SESSION_FILE_PATH", tempfile.gettempdir()) + + # Make sure the storage path is valid. + if not os.path.isdir(self.storage_path): + raise ImproperlyConfigured("The session storage path %r doesn't exist. "\ + "Please set your SESSION_FILE_PATH setting "\ + "to an existing directory in which Django "\ + "can store session data." % self.storage_path) + self.file_prefix = settings.SESSION_COOKIE_NAME super(SessionStore, self).__init__(session_key) diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py index 8fc5e17d69..dfa7bed226 100644 --- a/django/contrib/sessions/models.py +++ b/django/contrib/sessions/models.py @@ -18,40 +18,6 @@ class SessionManager(models.Manager): pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest() return base64.encodestring(pickled + pickled_md5) - def get_new_session_key(self): - "Returns session key that isn't being used." - # The random module is seeded when this Apache child is created. - # Use SECRET_KEY as added salt. - try: - pid = os.getpid() - except AttributeError: - # No getpid() in Jython, for example - pid = 1 - while 1: - session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), pid, time.time(), settings.SECRET_KEY)).hexdigest() - try: - self.get(session_key=session_key) - except self.model.DoesNotExist: - break - return session_key - - def get_new_session_object(self): - """ - Returns a new session object. - """ - # FIXME: There is a *small* chance of collision here, meaning we will - # return an existing object. That can be fixed when we add a way to - # validate (and guarantee) that non-auto primary keys are unique. For - # now, we save immediately in order to reduce the "window of - # misfortune" as much as possible. - created = False - while not created: - obj, created = self.get_or_create(session_key=self.get_new_session_key(), - expire_date = datetime.datetime.now()) - # Collision in key generation, so re-seed the generator - random.seed() - return obj - def save(self, session_key, session_dict, expire_date): s = self.model(session_key, self.encode(session_dict), expire_date) if session_dict: diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index ab3034f858..b2c664ce7b 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -1,5 +1,6 @@ r""" +>>> from django.conf import settings >>> from django.contrib.sessions.backends.db import SessionStore as DatabaseSession >>> from django.contrib.sessions.backends.cache import SessionStore as CacheSession >>> from django.contrib.sessions.backends.file import SessionStore as FileSession @@ -39,6 +40,13 @@ True >>> file_session.exists(file_session.session_key) False +# Make sure the file backend checks for a good storage dir +>>> settings.SESSION_FILE_PATH = "/if/this/directory/exists/you/have/a/weird/computer" +>>> FileSession() +Traceback (innermost last): + ... +ImproperlyConfigured: The session storage path '/if/this/directory/exists/you/have/a/weird/computer' doesn't exist. Please set your SESSION_FILE_PATH setting to an existing directory in which Django can store session data. + >>> cache_session = CacheSession() >>> cache_session.modified False @@ -66,6 +74,11 @@ False >>> s.accessed, s.modified (True, False) +>>> s.setdefault('foo', 'bar') +'bar' +>>> s.setdefault('foo', 'baz') +'bar' + >>> s.accessed = False # Reset the accessed flag >>> s.pop('some key') diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py index 6da8e883b9..495cc92822 100644 --- a/django/core/cache/__init__.py +++ b/django/core/cache/__init__.py @@ -22,19 +22,28 @@ from django.core.cache.backends.base import InvalidCacheBackendError BACKENDS = { # name for use in settings file --> name of module in "backends" directory 'memcached': 'memcached', - 'simple': 'simple', 'locmem': 'locmem', 'file': 'filebased', 'db': 'db', 'dummy': 'dummy', } +DEPRECATED_BACKENDS = { + # deprecated backend --> replacement module + 'simple': 'locmem', +} + def get_cache(backend_uri): if backend_uri.find(':') == -1: raise InvalidCacheBackendError, "Backend URI must start with scheme://" scheme, rest = backend_uri.split(':', 1) if not rest.startswith('//'): raise InvalidCacheBackendError, "Backend URI must start with scheme://" + if scheme in DEPRECATED_BACKENDS: + import warnings + warnings.warn("'%s' backend is deprecated. Use '%s' instead." % + (scheme, DEPRECATED_BACKENDS[scheme]), DeprecationWarning) + scheme = DEPRECATED_BACKENDS[scheme] if scheme not in BACKENDS: raise InvalidCacheBackendError, "%r is not a valid cache backend" % scheme diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py index 690193ac81..c1277bf20c 100644 --- a/django/core/cache/backends/filebased.py +++ b/django/core/cache/backends/filebased.py @@ -1,41 +1,38 @@ "File-based cache backend" -from django.core.cache.backends.simple import CacheClass as SimpleCacheClass -from django.utils.http import urlquote_plus +import md5 import os, time try: import cPickle as pickle except ImportError: import pickle +from django.core.cache.backends.base import BaseCache -class CacheClass(SimpleCacheClass): +class CacheClass(BaseCache): def __init__(self, dir, params): + BaseCache.__init__(self, params) + + max_entries = params.get('max_entries', 300) + try: + self._max_entries = int(max_entries) + except (ValueError, TypeError): + self._max_entries = 300 + + cull_frequency = params.get('cull_frequency', 3) + try: + self._cull_frequency = int(cull_frequency) + except (ValueError, TypeError): + self._cull_frequency = 3 + self._dir = dir if not os.path.exists(self._dir): self._createdir() - SimpleCacheClass.__init__(self, dir, params) - del self._cache - del self._expire_info def add(self, key, value, timeout=None): - fname = self._key_to_file(key) - if timeout is None: - timeout = self.default_timeout - try: - filelist = os.listdir(self._dir) - except (IOError, OSError): - self._createdir() - filelist = [] - if len(filelist) > self._max_entries: - self._cull(filelist) - if os.path.basename(fname) not in filelist: - try: - f = open(fname, 'wb') - now = time.time() - pickle.dump(now + timeout, f, 2) - pickle.dump(value, f, 2) - except (IOError, OSError): - pass + if self.has_key(key): + return None + + self.set(key, value, timeout) def get(self, key, default=None): fname = self._key_to_file(key) @@ -45,7 +42,7 @@ class CacheClass(SimpleCacheClass): now = time.time() if exp < now: f.close() - os.remove(fname) + self._delete(fname) else: return pickle.load(f) except (IOError, OSError, EOFError, pickle.PickleError): @@ -54,40 +51,74 @@ class CacheClass(SimpleCacheClass): def set(self, key, value, timeout=None): fname = self._key_to_file(key) + dirname = os.path.dirname(fname) + if timeout is None: timeout = self.default_timeout + + self._cull() + try: - filelist = os.listdir(self._dir) - except (IOError, OSError): - self._createdir() - filelist = [] - if len(filelist) > self._max_entries: - self._cull(filelist) - try: + if not os.path.exists(dirname): + os.makedirs(dirname) + f = open(fname, 'wb') now = time.time() - pickle.dump(now + timeout, f, 2) - pickle.dump(value, f, 2) + pickle.dump(now + timeout, f, pickle.HIGHEST_PROTOCOL) + pickle.dump(value, f, pickle.HIGHEST_PROTOCOL) except (IOError, OSError): pass def delete(self, key): try: - os.remove(self._key_to_file(key)) + self._delete(self._key_to_file(key)) + except (IOError, OSError): + pass + + def _delete(self, fname): + os.remove(fname) + try: + # Remove the 2 subdirs if they're empty + dirname = os.path.dirname(fname) + os.rmdir(dirname) + os.rmdir(os.path.dirname(dirname)) except (IOError, OSError): pass def has_key(self, key): - return os.path.exists(self._key_to_file(key)) + fname = self._key_to_file(key) + try: + f = open(fname, 'rb') + exp = pickle.load(f) + now = time.time() + if exp < now: + f.close() + self._delete(fname) + return False + else: + return True + except (IOError, OSError, EOFError, pickle.PickleError): + return False - def _cull(self, filelist): + def _cull(self): + if int(self._num_entries) < self._max_entries: + return + + try: + filelist = os.listdir(self._dir) + except (IOError, OSError): + return + if self._cull_frequency == 0: doomed = filelist else: - doomed = [k for (i, k) in enumerate(filelist) if i % self._cull_frequency == 0] - for fname in doomed: + doomed = [os.path.join(self._dir, k) for (i, k) in enumerate(filelist) if i % self._cull_frequency == 0] + + for topdir in doomed: try: - os.remove(os.path.join(self._dir, fname)) + for root, _, files in os.walk(topdir): + for f in files: + self._delete(os.path.join(root, f)) except (IOError, OSError): pass @@ -98,4 +129,22 @@ class CacheClass(SimpleCacheClass): raise EnvironmentError, "Cache directory '%s' does not exist and could not be created'" % self._dir def _key_to_file(self, key): - return os.path.join(self._dir, urlquote_plus(key)) + """ + Convert the filename into an md5 string. We'll turn the first couple + bits of the path into directory prefixes to be nice to filesystems + that have problems with large numbers of files in a directory. + + Thus, a cache key of "foo" gets turnned into a file named + ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``. + """ + path = md5.new(key.encode('utf-8')).hexdigest() + path = os.path.join(path[:2], path[2:4], path[4:]) + return os.path.join(self._dir, path) + + def _get_num_entries(self): + count = 0 + for _,_,files in os.walk(self._dir): + count += len(files) + return count + _num_entries = property(_get_num_entries) + diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py index 2d74e2b132..e8e1e0d450 100644 --- a/django/core/cache/backends/locmem.py +++ b/django/core/cache/backends/locmem.py @@ -6,65 +6,122 @@ try: except ImportError: import pickle -from django.core.cache.backends.simple import CacheClass as SimpleCacheClass +from django.core.cache.backends.base import BaseCache from django.utils.synch import RWLock -class CacheClass(SimpleCacheClass): - def __init__(self, host, params): - SimpleCacheClass.__init__(self, host, params) +class CacheClass(BaseCache): + def __init__(self, _, params): + BaseCache.__init__(self, params) + self._cache = {} + self._expire_info = {} + + max_entries = params.get('max_entries', 300) + try: + self._max_entries = int(max_entries) + except (ValueError, TypeError): + self._max_entries = 300 + + cull_frequency = params.get('cull_frequency', 3) + try: + self._cull_frequency = int(cull_frequency) + except (ValueError, TypeError): + self._cull_frequency = 3 + self._lock = RWLock() def add(self, key, value, timeout=None): self._lock.writer_enters() - # Python 2.3 and 2.4 don't allow combined try-except-finally blocks. try: - try: - super(CacheClass, self).add(key, pickle.dumps(value), timeout) - except pickle.PickleError: - pass + exp = self._expire_info.get(key) + if exp is None or exp <= time.time(): + try: + self._set(key, pickle.dumps(value), timeout) + except pickle.PickleError: + pass finally: self._lock.writer_leaves() def get(self, key, default=None): - should_delete = False self._lock.reader_enters() try: - now = time.time() exp = self._expire_info.get(key) if exp is None: return default - elif exp < now: - should_delete = True - else: + elif exp > time.time(): try: return pickle.loads(self._cache[key]) except pickle.PickleError: return default finally: self._lock.reader_leaves() - if should_delete: - self._lock.writer_enters() - try: - del self._cache[key] - del self._expire_info[key] - return default - finally: - self._lock.writer_leaves() + self._lock.writer_enters() + try: + del self._cache[key] + del self._expire_info[key] + return default + finally: + self._lock.writer_leaves() + + def _set(self, key, value, timeout=None): + if len(self._cache) >= self._max_entries: + self._cull() + if timeout is None: + timeout = self.default_timeout + self._cache[key] = value + self._expire_info[key] = time.time() + timeout def set(self, key, value, timeout=None): self._lock.writer_enters() # Python 2.3 and 2.4 don't allow combined try-except-finally blocks. try: try: - super(CacheClass, self).set(key, pickle.dumps(value), timeout) + self._set(key, pickle.dumps(value), timeout) except pickle.PickleError: pass finally: self._lock.writer_leaves() + def has_key(self, key): + self._lock.reader_enters() + try: + exp = self._expire_info.get(key) + if exp is None: + return False + elif exp > time.time(): + return True + finally: + self._lock.reader_leaves() + + self._lock.writer_enters() + try: + del self._cache[key] + del self._expire_info[key] + return False + finally: + self._lock.writer_leaves() + + def _cull(self): + if self._cull_frequency == 0: + self._cache.clear() + self._expire_info.clear() + else: + doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0] + for k in doomed: + self.delete(k) + + def _delete(self, key): + try: + del self._cache[key] + except KeyError: + pass + try: + del self._expire_info[key] + except KeyError: + pass + def delete(self, key): self._lock.writer_enters() try: - SimpleCacheClass.delete(self, key) + self._delete(key) finally: self._lock.writer_leaves() diff --git a/django/core/cache/backends/simple.py b/django/core/cache/backends/simple.py deleted file mode 100644 index ff60d49066..0000000000 --- a/django/core/cache/backends/simple.py +++ /dev/null @@ -1,73 +0,0 @@ -"Single-process in-memory cache backend." - -from django.core.cache.backends.base import BaseCache -import time - -class CacheClass(BaseCache): - def __init__(self, host, params): - BaseCache.__init__(self, params) - self._cache = {} - self._expire_info = {} - - max_entries = params.get('max_entries', 300) - try: - self._max_entries = int(max_entries) - except (ValueError, TypeError): - self._max_entries = 300 - - cull_frequency = params.get('cull_frequency', 3) - try: - self._cull_frequency = int(cull_frequency) - except (ValueError, TypeError): - self._cull_frequency = 3 - - def add(self, key, value, timeout=None): - if len(self._cache) >= self._max_entries: - self._cull() - if timeout is None: - timeout = self.default_timeout - if key not in self._cache.keys(): - self._cache[key] = value - self._expire_info[key] = time.time() + timeout - - def get(self, key, default=None): - now = time.time() - exp = self._expire_info.get(key) - if exp is None: - return default - elif exp < now: - del self._cache[key] - del self._expire_info[key] - return default - else: - return self._cache[key] - - def set(self, key, value, timeout=None): - if len(self._cache) >= self._max_entries: - self._cull() - if timeout is None: - timeout = self.default_timeout - self._cache[key] = value - self._expire_info[key] = time.time() + timeout - - def delete(self, key): - try: - del self._cache[key] - except KeyError: - pass - try: - del self._expire_info[key] - except KeyError: - pass - - def has_key(self, key): - return key in self._cache - - def _cull(self): - if self._cull_frequency == 0: - self._cache.clear() - self._expire_info.clear() - else: - doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0] - for k in doomed: - self.delete(k) diff --git a/django/core/exceptions.py b/django/core/exceptions.py index f22f67c261..d9fc326cf2 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -4,6 +4,10 @@ class ObjectDoesNotExist(Exception): "The requested object does not exist" silent_variable_failure = True +class MultipleObjectsReturned(Exception): + "The query returned multiple objects when only one was expected." + pass + class SuspiciousOperation(Exception): "The user did something suspicious" pass diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 1796cae8ea..17a24d6f60 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -1,7 +1,8 @@ +import sys + +from django import http from django.core import signals from django.dispatch import dispatcher -from django import http -import sys class BaseHandler(object): # Changes that are always applied to a response (in this order). diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py index e81a65be4d..ebf79295e0 100644 --- a/django/core/handlers/modpython.py +++ b/django/core/handlers/modpython.py @@ -1,11 +1,12 @@ -from django.core.handlers.base import BaseHandler +import os +from pprint import pformat + +from django import http from django.core import signals +from django.core.handlers.base import BaseHandler from django.dispatch import dispatcher from django.utils import datastructures from django.utils.encoding import force_unicode -from django import http -from pprint import pformat -import os # NOTE: do *not* import settings (or any module which eventually imports # settings) until after ModPythonHandler has been called; otherwise os.environ diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 94575ca369..df2ba19b65 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -5,12 +5,12 @@ try: except ImportError: from StringIO import StringIO -from django.core.handlers.base import BaseHandler +from django import http from django.core import signals +from django.core.handlers.base import BaseHandler from django.dispatch import dispatcher from django.utils import datastructures from django.utils.encoding import force_unicode -from django import http # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html STATUS_CODE_TEXT = { diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index fcbc9d1110..d78e2eda0b 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -10,7 +10,7 @@ from django.core.management.base import BaseCommand, CommandError, handle_defaul get_version = django.get_version # A cache of loaded commands, so that call_command -# doesn't have to reload every time it is called +# doesn't have to reload every time it's called. _commands = None def find_commands(management_dir): @@ -29,8 +29,8 @@ def find_commands(management_dir): def find_management_module(app_name): """ - Determines the path to the management module for the application named, - without acutally importing the application or the management module. + Determines the path to the management module for the given app_name, + without actually importing the application or the management module. Raises ImportError if the management module cannot be found for any reason. """ @@ -46,19 +46,19 @@ def find_management_module(app_name): def load_command_class(app_name, name): """ Given a command name and an application name, returns the Command - class instance. All errors raised by the importation process + class instance. All errors raised by the import process (ImportError, AttributeError) are allowed to propagate. """ return getattr(__import__('%s.management.commands.%s' % (app_name, name), {}, {}, ['Command']), 'Command')() -def get_commands(): +def get_commands(load_user_commands=True, project_directory=None): """ - Returns a dictionary of commands against the application in which - those commands can be found. This works by looking for a - management.commands package in django.core, and in each installed - application -- if a commands package exists, all commands in that - package are registered. + Returns a dictionary mapping command names to their callback applications. + + This works by looking for a management.commands package in django.core, and + in each installed application -- if a commands package exists, all commands + in that package are registered. Core commands are always included. If a settings module has been specified, user-defined commands will also be included, the @@ -73,34 +73,22 @@ def get_commands(): startapp command), the instantiated module can be placed in the dictionary in place of the application name. - The dictionary is cached on the first call, and reused on subsequent + The dictionary is cached on the first call and reused on subsequent calls. """ global _commands if _commands is None: - _commands = dict([(name, 'django.core') - for name in find_commands(__path__[0])]) - # Get commands from all installed apps. - try: - from django.conf import settings - apps = settings.INSTALLED_APPS - except (AttributeError, EnvironmentError): - apps = [] + _commands = dict([(name, 'django.core') for name in find_commands(__path__[0])]) - for app_name in apps: - try: - path = find_management_module(app_name) - _commands.update(dict([(name, app_name) - for name in find_commands(path)])) - except ImportError: - pass # No management module - ignore this app - - # Try to determine the project directory - try: + if load_user_commands: + # Get commands from all installed apps. from django.conf import settings - project_directory = setup_environ(__import__(settings.SETTINGS_MODULE)) - except (AttributeError, EnvironmentError, ImportError): - project_directory = None + for app_name in settings.INSTALLED_APPS: + try: + path = find_management_module(app_name) + _commands.update(dict([(name, app_name) for name in find_commands(path)])) + except ImportError: + pass # No management module -- ignore this app. if project_directory: # Remove the "startproject" command from self.commands, because @@ -157,18 +145,18 @@ class ManagementUtility(object): def __init__(self, argv=None): self.argv = argv or sys.argv[:] self.prog_name = os.path.basename(self.argv[0]) + self.project_directory = None + self.user_commands = False def main_help_text(self): """ Returns the script's main help text, as a string. """ usage = ['%s [options] [args]' % self.prog_name] - usage.append('Django command line tool,' - ' version %s' % django.get_version()) - usage.append("Type '%s help ' for help on a specific" - " subcommand." % self.prog_name) + usage.append('Django command line tool, version %s' % django.get_version()) + usage.append("Type '%s help ' for help on a specific subcommand." % self.prog_name) usage.append('Available subcommands:') - commands = get_commands().keys() + commands = get_commands(self.user_commands, self.project_directory).keys() commands.sort() for cmd in commands: usage.append(' %s' % cmd) @@ -178,18 +166,18 @@ class ManagementUtility(object): """ Tries to fetch the given subcommand, printing a message with the appropriate command called from the command line (usually - django-admin.py or manage.py) if it can't be found. + "django-admin.py" or "manage.py") if it can't be found. """ try: - app_name = get_commands()[subcommand] + app_name = get_commands(self.user_commands, self.project_directory)[subcommand] if isinstance(app_name, BaseCommand): # If the command is already loaded, use it directly. klass = app_name else: klass = load_command_class(app_name, subcommand) except KeyError: - sys.stderr.write("Unknown command: %r\nType '%s help' for" - " usage.\n" % (subcommand, self.prog_name)) + sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \ + (subcommand, self.prog_name)) sys.exit(1) return klass @@ -201,8 +189,7 @@ class ManagementUtility(object): # Preprocess options to extract --settings and --pythonpath. # These options could affect the commands that are available, so they # must be processed early. - parser = LaxOptionParser(version=get_version(), - option_list=BaseCommand.option_list) + parser = LaxOptionParser(version=get_version(), option_list=BaseCommand.option_list) try: options, args = parser.parse_args(self.argv) handle_default_options(options) @@ -242,6 +229,8 @@ class ProjectManagementUtility(ManagementUtility): """ def __init__(self, argv, project_directory): super(ProjectManagementUtility, self).__init__(argv) + self.project_directory = project_directory + self.user_commands = True def setup_environ(settings_mod): """ @@ -254,6 +243,8 @@ def setup_environ(settings_mod): # way. For example, if this file (manage.py) lives in a directory # "myproject", this code would add "/path/to/myproject" to sys.path. project_directory, settings_filename = os.path.split(settings_mod.__file__) + if not project_directory: + project_directory = os.getcwd() project_name = os.path.basename(project_directory) settings_name = os.path.splitext(settings_filename)[0] sys.path.append(os.path.join(project_directory, os.pardir)) @@ -261,8 +252,7 @@ def setup_environ(settings_mod): sys.path.pop() # Set DJANGO_SETTINGS_MODULE appropriately. - os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, - settings_name) + os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name) return project_directory def execute_from_command_line(argv=None): diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 83fc9b8ac9..216db09141 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -58,7 +58,7 @@ class Command(BaseCommand): else: formats = [] - if verbosity > 0: + if verbosity > 2: if formats: print "Loading '%s' fixtures..." % fixture_name else: @@ -106,7 +106,7 @@ class Command(BaseCommand): return fixture.close() except: - if verbosity > 1: + if verbosity > 2: print "No %s fixture '%s' in %s." % \ (format, fixture_name, humanize(fixture_dir)) @@ -122,7 +122,7 @@ class Command(BaseCommand): transaction.leave_transaction_management() if count[0] == 0: - if verbosity > 0: + if verbosity > 2: 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 8b4bb68005..e6e84ec78c 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -33,8 +33,9 @@ class Command(NoArgsCommand): for app_name in settings.INSTALLED_APPS: try: __import__(app_name + '.management', {}, {}, ['']) - except ImportError: - pass + except ImportError, exc: + if not exc.args[0].startswith('No module named management'): + raise cursor = connection.cursor() diff --git a/django/core/management/validation.py b/django/core/management/validation.py index c433b7fa96..bc9faae056 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -72,10 +72,14 @@ def get_validation_errors(outfile, app=None): # Check to see if the related field will clash with any # existing fields, m2m fields, m2m related objects or related objects if f.rel: - rel_opts = f.rel.to._meta if f.rel.to not in models.get_models(): - e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, rel_opts.object_name)) + e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, f.rel.to)) + # it is a string and we could not find the model it refers to + # so skip the next section + if isinstance(f.rel.to, (str, unicode)): + continue + rel_opts = f.rel.to._meta rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() rel_query_name = f.related_query_name() for r in rel_opts.fields: @@ -103,10 +107,14 @@ def get_validation_errors(outfile, app=None): for i, f in enumerate(opts.many_to_many): # Check to see if the related m2m field will clash with any # existing fields, m2m fields, m2m related objects or related objects - rel_opts = f.rel.to._meta if f.rel.to not in models.get_models(): - e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, rel_opts.object_name)) + e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, f.rel.to)) + # it is a string and we could not find the model it refers to + # so skip the next section + if isinstance(f.rel.to, (str, unicode)): + continue + rel_opts = f.rel.to._meta rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() rel_query_name = f.related_query_name() # If rel_name is none, there is no reverse accessor. diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py index 92159dbbe3..772f34a2e3 100644 --- a/django/core/serializers/pyyaml.py +++ b/django/core/serializers/pyyaml.py @@ -5,6 +5,7 @@ Requires PyYaml (http://pyyaml.org/), but that's checked for in __init__. """ import datetime +from django.db import models from django.core.serializers.python import Serializer as PythonSerializer from django.core.serializers.python import Deserializer as PythonDeserializer try: @@ -17,10 +18,23 @@ class Serializer(PythonSerializer): """ Convert a queryset to YAML. """ + + def handle_field(self, obj, field): + # A nasty special case: base YAML doesn't support serialization of time + # types (as opposed to dates or datetimes, which it does support). Since + # we want to use the "safe" serializer for better interoperability, we + # need to do something with those pesky times. Converting 'em to strings + # isn't perfect, but it's better than a "!!python/time" type which would + # halt deserialization under any other language. + if isinstance(field, models.TimeField) and getattr(obj, field.name) is not None: + self._current[field.name] = str(getattr(obj, field.name)) + else: + super(Serializer, self).handle_field(obj, field) + def end_serialization(self): self.options.pop('stream', None) self.options.pop('fields', None) - yaml.dump(self.objects, self.stream, **self.options) + yaml.safe_dump(self.objects, self.stream, **self.options) def getvalue(self): return self.stream.getvalue() diff --git a/django/db/__init__.py b/django/db/__init__.py index d4ea1403bd..8f75e0d7b8 100644 --- a/django/db/__init__.py +++ b/django/db/__init__.py @@ -34,8 +34,8 @@ except ImportError, e: raise # If there's some other error, this must be an error in Django itself. def _import_database_module(import_path='', module_name=''): - """Lazyily import a database module when requested.""" - return __import__('%s%s.%s' % (_import_path, settings.DATABASE_ENGINE, module_name), {}, {}, ['']) + """Lazily import a database module when requested.""" + return __import__('%s%s.%s' % (import_path, settings.DATABASE_ENGINE, module_name), {}, {}, ['']) # We don't want to import the introspect/creation modules unless # someone asks for 'em, so lazily load them on demmand. diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index fb842950b1..cd830413fc 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -47,7 +47,8 @@ class DatabaseOperations(BaseDatabaseOperations): BEGIN SELECT %(sq_name)s.nextval INTO :new.%(col_name)s FROM dual; - END;/""" % locals() + END; + /""" % locals() return sequence_sql, trigger_sql def date_extract_sql(self, lookup_type, field_name): @@ -445,24 +446,31 @@ class FormatStylePlaceholderCursor(Database.Cursor): charset = 'utf-8' def _format_params(self, params): - sz_kwargs = {} if isinstance(params, dict): result = {} charset = self.charset for key, value in params.items(): result[smart_str(key, charset)] = smart_str(value, charset) - if hasattr(value, 'oracle_type'): sz_kwargs[key] = value.oracle_type() + return result else: - result = {} - for i in xrange(len(params)): - key = 'arg%d' % i - result[key] = smart_str(params[i], self.charset, True) - if hasattr(params[i], 'oracle_type'): sz_kwargs[key] = params[i].oracle_type() + return tuple([smart_str(p, self.charset, True) for p in params]) - # If any of the parameters had an `oracle_type` method, then we set - # the inputsizes for those parameters using the returned type - if sz_kwargs: self.setinputsizes(**sz_kwargs) - return result + def _guess_input_sizes(self, params_list): + # Mark any string parameter greater than 4000 characters as an NCLOB. + if isinstance(params_list[0], dict): + sizes = {} + iterators = [params.iteritems() for params in params_list] + else: + sizes = [None] * len(params_list[0]) + iterators = [enumerate(params) for params in params_list] + for iterator in iterators: + for key, value in iterator: + if isinstance(value, basestring) and len(value) > 4000: + sizes[key] = Database.NCLOB + if isinstance(sizes, dict): + self.setinputsizes(**sizes) + else: + self.setinputsizes(*sizes) def execute(self, query, params=None): if params is None: @@ -477,11 +485,12 @@ class FormatStylePlaceholderCursor(Database.Cursor): if query.endswith(';') or query.endswith('/'): query = query[:-1] query = smart_str(query, self.charset) % tuple(args) + self._guess_input_sizes([params]) return Database.Cursor.execute(self, query, params) def executemany(self, query, params=None): try: - args = [(':arg%d' % i) for i in range(len(params[0]))] + args = [(':arg%d' % i) for i in range(len(params[0]))] except (IndexError, TypeError): # No params given, nothing to do return None @@ -493,6 +502,7 @@ class FormatStylePlaceholderCursor(Database.Cursor): query = query[:-1] query = smart_str(query, self.charset) % tuple(args) new_param_list = [self._format_params(i) for i in params] + self._guess_input_sizes(new_param_list) return Database.Cursor.executemany(self, query, new_param_list) def fetchone(self): diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index d7b3558344..6b5233e8de 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -6,6 +6,7 @@ Requires psycopg 2: http://initd.org/projects/psycopg2 from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures from django.db.backends.postgresql.operations import DatabaseOperations as PostgresqlDatabaseOperations +from django.utils.safestring import SafeUnicode try: import psycopg2 as Database import psycopg2.extensions @@ -17,6 +18,7 @@ DatabaseError = Database.DatabaseError IntegrityError = Database.IntegrityError psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) +psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString) class DatabaseFeatures(BaseDatabaseFeatures): needs_datetime_string_cast = False diff --git a/django/db/models/base.py b/django/db/models/base.py index bc90e8d543..e19223051d 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1,7 +1,7 @@ import django.db.models.manipulators import django.db.models.manager from django.core import validators -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist from django.db.models.fields.related import OneToOneRel, ManyToOneRel from django.db.models.query import delete_objects @@ -35,6 +35,8 @@ class ModelBase(type): new_class = type.__new__(cls, name, bases, {'__module__': attrs.pop('__module__')}) new_class.add_to_class('_meta', Options(attrs.pop('Meta', None))) new_class.add_to_class('DoesNotExist', types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {})) + new_class.add_to_class('MultipleObjectsReturned', + types.ClassType('MultipleObjectsReturned', (MultipleObjectsReturned, ), {})) # Build complete list of parents for base in bases: diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 20aa56d408..61ccc2d85d 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -104,7 +104,7 @@ class Field(object): self.radio_admin = radio_admin self.help_text = help_text self.db_column = db_column - self.db_tablespace = db_tablespace + self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE # Set db_index to True if the field has a relationship and doesn't explicitly set db_index. self.db_index = db_index diff --git a/django/db/models/options.py b/django/db/models/options.py index 788d1c80de..37ace0a7c1 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -29,7 +29,7 @@ class Options(object): self.object_name, self.app_label = None, None self.get_latest_by = None self.order_with_respect_to = None - self.db_tablespace = None + self.db_tablespace = settings.DEFAULT_TABLESPACE self.admin = None self.meta = meta self.pk = None @@ -152,7 +152,7 @@ class Options(object): rel_objs = [] for klass in get_models(): for f in klass._meta.fields: - if f.rel and self == f.rel.to._meta: + if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: rel_objs.append(RelatedObject(f.rel.to, klass, f)) self._all_related_objects = rel_objs return rel_objs @@ -186,7 +186,7 @@ class Options(object): rel_objs = [] for klass in get_models(): for f in klass._meta.many_to_many: - if f.rel and self == f.rel.to._meta: + if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: rel_objs.append(RelatedObject(f.rel.to, klass, f)) if app_cache_ready(): self._all_related_many_to_many_objects = rel_objs diff --git a/django/db/models/query.py b/django/db/models/query.py index 4d0d295e97..0bc36f425a 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -261,7 +261,8 @@ class _QuerySet(object): obj_list = list(clone) if len(obj_list) < 1: raise self.model.DoesNotExist, "%s matching query does not exist." % self.model._meta.object_name - assert len(obj_list) == 1, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs) + elif len(obj_list) > 1: + raise self.model.MultipleObjectsReturned, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs) return obj_list[0] def create(self, **kwargs): diff --git a/django/http/__init__.py b/django/http/__init__.py index 47f9736ce2..13cc8cea0d 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -331,7 +331,7 @@ class HttpResponse(object): chunk = self._iterator.next() if isinstance(chunk, unicode): chunk = chunk.encode(self._charset) - return chunk + return str(chunk) def close(self): if hasattr(self._container, 'close'): diff --git a/django/http/utils.py b/django/http/utils.py index d08a9e0237..f98ca93a37 100644 --- a/django/http/utils.py +++ b/django/http/utils.py @@ -5,7 +5,7 @@ Functions that modify an HTTP request or response in some way. # This group of functions are run as part of the response handling, after # everything else, including all response middleware. Think of them as # "compulsory response middleware". Be careful about what goes here, because -# it's a little fiddly to override this behaviour, so they should be truly +# it's a little fiddly to override this behavior, so they should be truly # universally applicable. def fix_location_header(request, response): @@ -13,7 +13,7 @@ def fix_location_header(request, response): Ensures that we always use an absolute URI in any location header in the response. This is required by RFC 2616, section 14.30. - Code constructing response objects is free to insert relative paths and + Code constructing response objects is free to insert relative paths, as this function converts them to absolute paths. """ if 'Location' in response and request.get_host(): @@ -31,4 +31,3 @@ def conditional_content_removal(request, response): if request.method == 'HEAD': response.content = '' return response - diff --git a/django/middleware/common.py b/django/middleware/common.py index 10a3a71b8d..3d57fa4367 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -5,6 +5,7 @@ from django.conf import settings from django import http from django.core.mail import mail_managers from django.utils.http import urlquote +from django.core import urlresolvers class CommonMiddleware(object): """ @@ -16,6 +17,12 @@ class CommonMiddleware(object): this middleware appends missing slashes and/or prepends missing "www."s. + - If APPEND_SLASH is set and the initial URL doesn't end with a + slash, and it is not found in urlpatterns, a new URL is formed by + appending a slash at the end. If this new URL is found in + urlpatterns, then an HTTP-redirect is returned to this new URL; + otherwise the initial URL is processed as usual. + - ETags: If the USE_ETAGS setting is set, ETags will be calculated from the entire page content and Not Modified responses will be returned appropriately. @@ -33,27 +40,48 @@ class CommonMiddleware(object): if user_agent_regex.search(request.META['HTTP_USER_AGENT']): return http.HttpResponseForbidden('

    Forbidden

    ') - # Check for a redirect based on settings.APPEND_SLASH and settings.PREPEND_WWW + # Check for a redirect based on settings.APPEND_SLASH + # and settings.PREPEND_WWW host = request.get_host() old_url = [host, request.path] new_url = old_url[:] - if settings.PREPEND_WWW and old_url[0] and not old_url[0].startswith('www.'): + + if (settings.PREPEND_WWW and old_url[0] and + not old_url[0].startswith('www.')): new_url[0] = 'www.' + old_url[0] - # Append a slash if append_slash is set and the URL doesn't have a - # trailing slash or a file extension. - if settings.APPEND_SLASH and (not old_url[1].endswith('/')) and ('.' not in old_url[1].split('/')[-1]): - new_url[1] = new_url[1] + '/' - if settings.DEBUG and request.method == 'POST': - raise RuntimeError, "You called this URL via POST, but the URL doesn't end in a slash and you have APPEND_SLASH set. Django can't redirect to the slash URL while maintaining POST data. Change your form to point to %s%s (note the trailing slash), or set APPEND_SLASH=False in your Django settings." % (new_url[0], new_url[1]) + + # Append a slash if APPEND_SLASH is set and the URL doesn't have a + # trailing slash and there is no pattern for the current path + if settings.APPEND_SLASH and (not old_url[1].endswith('/')): + try: + urlresolvers.resolve(request.path) + except urlresolvers.Resolver404: + new_url[1] = new_url[1] + '/' + if settings.DEBUG and request.method == 'POST': + raise RuntimeError, ("" + "You called this URL via POST, but the URL doesn't end " + "in a slash and you have APPEND_SLASH set. Django can't " + "redirect to the slash URL while maintaining POST data. " + "Change your form to point to %s%s (note the trailing " + "slash), or set APPEND_SLASH=False in your Django " + "settings.") % (new_url[0], new_url[1]) + if new_url != old_url: - # Redirect - if new_url[0]: - newurl = "%s://%s%s" % (request.is_secure() and 'https' or 'http', new_url[0], urlquote(new_url[1])) + # Redirect if the target url exists + try: + urlresolvers.resolve(new_url[1]) + except urlresolvers.Resolver404: + pass else: - newurl = urlquote(new_url[1]) - if request.GET: - newurl += '?' + request.GET.urlencode() - return http.HttpResponsePermanentRedirect(newurl) + if new_url[0]: + newurl = "%s://%s%s" % ( + request.is_secure() and 'https' or 'http', + new_url[0], urlquote(new_url[1])) + else: + newurl = urlquote(new_url[1]) + if request.GET: + newurl += '?' + request.GET.urlencode() + return http.HttpResponsePermanentRedirect(newurl) return None diff --git a/django/newforms/fields.py b/django/newforms/fields.py index 4a54a98d61..3b8f4195b0 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -83,21 +83,15 @@ class Field(object): self.creation_counter = Field.creation_counter Field.creation_counter += 1 - self.error_messages = self._build_error_messages(error_messages) - - def _build_error_messages(self, extra_error_messages): - error_messages = {} - - def get_default_error_messages(klass): + def set_class_error_messages(messages, klass): for base_class in klass.__bases__: - get_default_error_messages(base_class) - if hasattr(klass, 'default_error_messages'): - error_messages.update(klass.default_error_messages) + set_class_error_messages(messages, base_class) + messages.update(getattr(klass, 'default_error_messages', {})) - get_default_error_messages(self.__class__) - if extra_error_messages: - error_messages.update(extra_error_messages) - return error_messages + messages = {} + set_class_error_messages(messages, self.__class__) + messages.update(error_messages or {}) + self.error_messages = messages def clean(self, value): """ @@ -415,7 +409,7 @@ class EmailField(RegexField): try: from django.conf import settings URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT -except (ImportError, EnvironmentError): +except ImportError: # It's OK if Django settings aren't configured. URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' @@ -539,8 +533,8 @@ class BooleanField(Field): """Returns a Python boolean object.""" super(BooleanField, self).clean(value) # Explicitly check for the string 'False', which is what a hidden field - # will submit for False (since bool("True") == True we don't need to - # handle that explicitly). + # will submit for False. Because bool("True") == True, we don't need to + # handle that explicitly. if value == 'False': return False return bool(value) diff --git a/django/newforms/models.py b/django/newforms/models.py index 51ed16ff7f..17fd1cf2e2 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -3,16 +3,20 @@ Helper functions for creating Form classes from Django models and database field objects. """ +from warnings import warn + from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_unicode from django.utils.datastructures import SortedDict +from django.core.exceptions import ImproperlyConfigured -from util import ValidationError +from util import ValidationError, ErrorList from forms import BaseForm from fields import Field, ChoiceField, EMPTY_VALUES from widgets import Select, SelectMultiple, MultipleHiddenInput __all__ = ( + 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', 'ModelChoiceField', 'ModelMultipleChoiceField' ) @@ -80,6 +84,9 @@ def form_for_model(model, form=BaseForm, fields=None, determining the formfield for a given database field. It's a callable that takes a database Field instance and returns a form Field instance. """ + warn("form_for_model is deprecated, use ModelForm instead.", + PendingDeprecationWarning, + stacklevel=3) opts = model._meta field_list = [] for f in opts.fields + opts.many_to_many: @@ -107,6 +114,9 @@ def form_for_instance(instance, form=BaseForm, fields=None, takes a database Field instance, plus **kwargs, and returns a form Field instance with the given kwargs (i.e. 'initial'). """ + warn("form_for_instance is deprecated, use ModelForm instead.", + PendingDeprecationWarning, + stacklevel=3) model = instance.__class__ opts = model._meta field_list = [] @@ -132,6 +142,160 @@ def form_for_fields(field_list): for f in field_list if f.editable]) return type('FormForFields', (BaseForm,), {'base_fields': fields}) + +# ModelForms ################################################################# + +def model_to_dict(instance, fields=None, exclude=None): + """ + Returns a dict containing the data in ``instance`` suitable for passing as + a Form's ``initial`` keyword argument. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned dict. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned dict, even if they are listed in + the ``fields`` argument. + """ + # avoid a circular import + from django.db.models.fields.related import ManyToManyField + opts = instance._meta + data = {} + for f in opts.fields + opts.many_to_many: + if not f.editable: + continue + if fields and not f.name in fields: + continue + if exclude and f.name in exclude: + continue + if isinstance(f, ManyToManyField): + # If the object doesn't have a primry key yet, just use an empty + # list for its m2m fields. Calling f.value_from_object will raise + # an exception. + if instance.pk is None: + data[f.name] = [] + else: + # MultipleChoiceWidget needs a list of pks, not object instances. + data[f.name] = [obj.pk for obj in f.value_from_object(instance)] + else: + data[f.name] = f.value_from_object(instance) + return data + +def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()): + """ + Returns a ``SortedDict`` containing form fields for the given model. + + ``fields`` is an optional list of field names. If provided, only the named + fields will be included in the returned fields. + + ``exclude`` is an optional list of field names. If provided, the named + fields will be excluded from the returned fields, even if they are listed + in the ``fields`` argument. + """ + # TODO: if fields is provided, it would be nice to return fields in that order + field_list = [] + opts = model._meta + for f in opts.fields + opts.many_to_many: + if not f.editable: + continue + if fields and not f.name in fields: + continue + if exclude and f.name in exclude: + continue + formfield = formfield_callback(f) + if formfield: + field_list.append((f.name, formfield)) + return SortedDict(field_list) + +class ModelFormOptions(object): + def __init__(self, options=None): + self.model = getattr(options, 'model', None) + self.fields = getattr(options, 'fields', None) + self.exclude = getattr(options, 'exclude', None) + +class ModelFormMetaclass(type): + def __new__(cls, name, bases, attrs): + # TODO: no way to specify formfield_callback yet, do we need one, or + # should it be a special case for the admin? + fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] + fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) + + # If this class is subclassing another Form, add that Form's fields. + # Note that we loop over the bases in *reverse*. This is necessary in + # order to preserve the correct order of fields. + for base in bases[::-1]: + if hasattr(base, 'base_fields'): + fields = base.base_fields.items() + fields + declared_fields = SortedDict(fields) + + opts = ModelFormOptions(attrs.get('Meta', None)) + attrs['_meta'] = opts + + # Don't allow more than one Meta model defenition in bases. The fields + # would be generated correctly, but the save method won't deal with + # more than one object. + base_models = [] + for base in bases: + base_opts = getattr(base, '_meta', None) + base_model = getattr(base_opts, 'model', None) + if base_model is not None: + base_models.append(base_model) + if len(base_models) > 1: + raise ImproperlyConfigured("%s's base classes define more than one model." % name) + + # If a model is defined, extract form fields from it and add them to base_fields + if attrs['_meta'].model is not None: + # Don't allow a subclass to define a different Meta model than a + # parent class has. Technically the right fields would be generated, + # but the save method will not deal with more than one model. + for base in bases: + base_opts = getattr(base, '_meta', None) + base_model = getattr(base_opts, 'model', None) + if base_model and base_model is not opts.model: + raise ImproperlyConfigured('%s defines a different model than its parent.' % name) + model_fields = fields_for_model(opts.model, opts.fields, opts.exclude) + # fields declared in base classes override fields from the model + model_fields.update(declared_fields) + attrs['base_fields'] = model_fields + else: + attrs['base_fields'] = declared_fields + return type.__new__(cls, name, bases, attrs) + +class BaseModelForm(BaseForm): + def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, + initial=None, error_class=ErrorList, label_suffix=':', instance=None): + opts = self._meta + if instance is None: + # if we didn't get an instance, instantiate a new one + self.instance = opts.model() + object_data = {} + else: + self.instance = instance + object_data = model_to_dict(instance, opts.fields, opts.exclude) + # if initial was provided, it should override the values from instance + if initial is not None: + object_data.update(initial) + BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix) + + def save(self, commit=True): + """ + Saves this ``form``'s cleaned_data into model instance ``self.instance``. + + If commit=True, then the changes to ``instance`` will be saved to the + database. Returns ``instance``. + """ + if self.instance.pk is None: + fail_message = 'created' + else: + fail_message = 'changed' + return save_instance(self, self.instance, self._meta.fields, fail_message, commit) + +class ModelForm(BaseModelForm): + __metaclass__ = ModelFormMetaclass + + +# Fields ##################################################################### + class QuerySetIterator(object): def __init__(self, queryset, empty_label, cache_choices): self.queryset = queryset @@ -142,7 +306,7 @@ class QuerySetIterator(object): if self.empty_label is not None: yield (u"", self.empty_label) for obj in self.queryset: - yield (obj._get_pk_val(), smart_unicode(obj)) + yield (obj.pk, smart_unicode(obj)) # Clear the QuerySet cache if required. if not self.cache_choices: self.queryset._result_cache = None diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py index 611b997736..1cece7c7c0 100644 --- a/django/shortcuts/__init__.py +++ b/django/shortcuts/__init__.py @@ -38,7 +38,7 @@ def get_object_or_404(klass, *args, **kwargs): klass may be a Model, Manager, or QuerySet object. All other passed arguments and keyword arguments are used in the get() query. - Note: Like with get(), an AssertionError will be raised if more than one + Note: Like with get(), an MultipleObjectsReturned will be raised if more than one object is found. """ queryset = _get_queryset(klass) diff --git a/django/template/context.py b/django/template/context.py index 017d2d84b1..0e41a26618 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -23,12 +23,14 @@ class Context(object): yield d def push(self): - self.dicts = [{}] + self.dicts + d = {} + self.dicts = [d] + self.dicts + return d def pop(self): if len(self.dicts) == 1: raise ContextPopException - del self.dicts[0] + return self.dicts.pop(0) def __setitem__(self, key, value): "Set a variable in the current context" @@ -62,6 +64,7 @@ class Context(object): def update(self, other_dict): "Like dict.update(). Pushes an entire dictionary's keys and values onto the context." self.dicts = [other_dict] + self.dicts + return other_dict # This is a function rather than module-level procedural code because we only # want it to execute if somebody uses RequestContext. diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index f2be64ef1d..2f211dadb5 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -43,7 +43,11 @@ def stringfilter(func): def addslashes(value): - """Adds slashes - useful for passing strings to JavaScript, for example.""" + """ + Adds slashes before quotes. Useful for escaping strings in CSV, for + example. Less useful for escaping JavaScript; use the ``escapejs`` + filter instead. + """ return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") addslashes.is_safe = True addslashes = stringfilter(addslashes) @@ -54,6 +58,25 @@ def capfirst(value): capfirst.is_safe=True capfirst = stringfilter(capfirst) +_js_escapes = ( + ('\\', '\\\\'), + ('"', '\\"'), + ("'", "\\'"), + ('\n', '\\n'), + ('\r', '\\r'), + ('\b', '\\b'), + ('\f', '\\f'), + ('\t', '\\t'), + ('\v', '\\v'), + ('', '\n', '>'] -# list of possible strings used for bullets in bulleted lists +# List of possible strings used for bullets in bulleted lists. DOTS = ['·', '*', '\xe2\x80\xa2', '•', '•', '•'] unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)') @@ -28,7 +28,7 @@ trailing_empty_content_re = re.compile(r'(?:

    (?: |\s|
    )*?

    \s*)+\ del x # Temporary variable def escape(html): - "Return the given HTML with ampersands, quotes and carets encoded." + """Returns the given HTML with ampersands, quotes and carets encoded.""" return mark_safe(force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) escape = allow_lazy(escape, unicode) @@ -42,7 +42,7 @@ def conditional_escape(html): return escape(html) def linebreaks(value, autoescape=False): - "Converts newlines into

    and
    s" + """Converts newlines into

    and
    s.""" value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines paras = re.split('\n{2,}', value) if autoescape: @@ -50,31 +50,31 @@ def linebreaks(value, autoescape=False): else: paras = [u'

    %s

    ' % p.strip().replace('\n', '
    ') for p in paras] return u'\n\n'.join(paras) -linebreaks = allow_lazy(linebreaks, unicode) +linebreaks = allow_lazy(linebreaks, unicode) def strip_tags(value): - "Return the given HTML with all tags stripped." + """Returns the given HTML with all tags stripped.""" return re.sub(r'<[^>]*?>', '', force_unicode(value)) strip_tags = allow_lazy(strip_tags) def strip_spaces_between_tags(value): - "Return the given HTML with spaces between tags removed." + """Returns the given HTML with spaces between tags removed.""" return re.sub(r'>\s+<', '><', force_unicode(value)) strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, unicode) def strip_entities(value): - "Return the given HTML with all entities (&something;) stripped." + """Returns the given HTML with all entities (&something;) stripped.""" return re.sub(r'&(?:\w+|#\d+);', '', force_unicode(value)) strip_entities = allow_lazy(strip_entities, unicode) def fix_ampersands(value): - "Return the given HTML with all unencoded ampersands encoded correctly." + """Returns the given HTML with all unencoded ampersands encoded correctly.""" return unencoded_ampersands_re.sub('&', force_unicode(value)) fix_ampersands = allow_lazy(fix_ampersands, unicode) def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): """ - Convert any URLs in text into clickable links. + Converts any URLs in text into clickable links. Works on http://, https://, and www. links. Links can have trailing punctuation (periods, commas, close-parens) and leading punctuation @@ -100,7 +100,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): if safe_input: middle = mark_safe(middle) if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \ - len(middle) > 0 and middle[0] in string.letters + string.digits and \ + len(middle) > 0 and middle[0] in string.ascii_letters + string.digits and \ (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))): middle = '%s' % ( urlquote(middle, safe='/&=:;#?+'), nofollow_attr, diff --git a/django/utils/maxlength.py b/django/utils/maxlength.py index 9216fe1c3a..a616541f85 100644 --- a/django/utils/maxlength.py +++ b/django/utils/maxlength.py @@ -1,6 +1,6 @@ """ Utilities for providing backwards compatibility for the maxlength argument, -which has been replaced by max_length, see ticket #2101. +which has been replaced by max_length. See ticket #2101. """ from warnings import warn @@ -15,17 +15,15 @@ def legacy_maxlength(max_length, maxlength): """ Consolidates max_length and maxlength, providing backwards compatibilty for the legacy "maxlength" argument. + If one of max_length or maxlength is given, then that value is returned. - If both are given, a TypeError is raised. - If maxlength is used at all, a deprecation warning is issued. + If both are given, a TypeError is raised. If maxlength is used at all, a + deprecation warning is issued. """ if maxlength is not None: - warn("maxlength is deprecated, use max_length instead.", - PendingDeprecationWarning, - stacklevel=3) + warn("maxlength is deprecated. Use max_length instead.", DeprecationWarning, stacklevel=3) if max_length is not None: - raise TypeError("field can not take both the max_length" - " argument and the legacy maxlength argument.") + raise TypeError("Field cannot take both the max_length argument and the legacy maxlength argument.") max_length = maxlength return max_length @@ -33,7 +31,8 @@ def remove_maxlength(func): """ A decorator to be used on a class's __init__ that provides backwards compatibilty for the legacy "maxlength" keyword argument, i.e. - name = models.CharField(maxlength=20) + name = models.CharField(maxlength=20) + It does this by changing the passed "maxlength" keyword argument (if it exists) into a "max_length" keyword argument. """ @@ -58,7 +57,6 @@ class LegacyMaxlength(type): Metaclass for providing backwards compatibility support for the "maxlength" keyword argument. """ - def __init__(cls, name, bases, attrs): super(LegacyMaxlength, cls).__init__(name, bases, attrs) # Decorate the class's __init__ to remove any maxlength keyword. diff --git a/django/utils/safestring.py b/django/utils/safestring.py index f6f4c1eb2f..2e31c23676 100644 --- a/django/utils/safestring.py +++ b/django/utils/safestring.py @@ -34,16 +34,13 @@ class SafeString(str, SafeData): Concatenating a safe string with another safe string or safe unicode object is safe. Otherwise, the result is no longer safe. """ + t = super(SafeString, self).__add__(rhs) if isinstance(rhs, SafeUnicode): - return SafeUnicode(self + rhs) + return SafeUnicode(t) elif isinstance(rhs, SafeString): - return SafeString(self + rhs) - else: - return super(SafeString, self).__add__(rhs) - - def __str__(self): - return self - + return SafeString(t) + return t + def _proxy_method(self, *args, **kwargs): """ Wrap a call to a normal unicode method up so that we return safe @@ -69,11 +66,11 @@ class SafeUnicode(unicode, SafeData): Concatenating a safe unicode object with another safe string or safe unicode object is safe. Otherwise, the result is no longer safe. """ + t = super(SafeUnicode, self).__add__(rhs) if isinstance(rhs, SafeData): - return SafeUnicode(self + rhs) - else: - return super(SafeUnicode, self).__add__(rhs) - + return SafeUnicode(t) + return t + def _proxy_method(self, *args, **kwargs): """ Wrap a call to a normal unicode method up so that we return safe diff --git a/django/views/debug.py b/django/views/debug.py index 3358d2f08e..18a396d3a6 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -1,9 +1,12 @@ +import os +import re +import sys + from django.conf import settings from django.template import Template, Context, TemplateDoesNotExist from django.utils.html import escape from django.http import HttpResponseServerError, HttpResponseNotFound from django.utils.encoding import smart_unicode -import os, re, sys HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST') @@ -131,7 +134,7 @@ def technical_500_response(request, exc_type, exc_value, tb): if start is not None and end is not None: unicode_str = exc_value.args[1] unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace') - + from django import get_version t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') c = Context({ 'exception_type': exc_type.__name__, @@ -142,8 +145,10 @@ def technical_500_response(request, exc_type, exc_value, tb): 'request': request, 'request_protocol': request.is_secure() and "https" or "http", 'settings': get_safe_settings(), - 'sys_executable' : sys.executable, - 'sys_version_info' : '%d.%d.%d' % sys.version_info[0:3], + 'sys_executable': sys.executable, + 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3], + 'django_version_info': get_version(), + 'sys_path' : sys.path, 'template_info': template_info, 'template_does_not_exist': template_does_not_exist, 'loader_debug_info': loader_debug_info, @@ -229,8 +234,8 @@ TECHNICAL_500_TEMPLATE = """ - - + + {{ exception_type }} at {{ request.path|escape }}