diff --git a/AUTHORS b/AUTHORS index ffb9e87690..5ec60de738 100644 --- a/AUTHORS +++ b/AUTHORS @@ -130,6 +130,7 @@ answer newbie questions, and generally made Django that much better: Matthew Flanagan Eric Floehr Vincent Foley + Rudolph Froger Jorge Gajon gandalf@owca.info Marc Garcia @@ -300,6 +301,7 @@ answer newbie questions, and generally made Django that much better: Georgi Stanojevski Vasiliy Stavenko Thomas Steinacher + Johan C. Stöver nowell strite Thomas Stromberg Sundance @@ -327,6 +329,7 @@ answer newbie questions, and generally made Django that much better: tt@gurgle.no Amit Upadhyay Geert Vanderkelen + I.S. van Oostveen viestards.lists@gmail.com George Vilches Vlado diff --git a/django/conf/__init__.py b/django/conf/__init__.py index c24e87e6ed..61d9ff7536 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -52,7 +52,9 @@ class LazySettings(object): if not settings_module: # If it's set but is an empty string. raise KeyError except KeyError: - 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 + # NOTE: This is arguably an EnvironmentError, but that causes + # problems with Python's interactive help. + raise ImportError("Settings cannot be imported, because environment variable %s is undefined." % ENVIRONMENT_VARIABLE) self._target = Settings(settings_module) diff --git a/django/conf/locale/nl/LC_MESSAGES/django.mo b/django/conf/locale/nl/LC_MESSAGES/django.mo index 4c70c28ff4..b49be6a3e3 100644 Binary files a/django/conf/locale/nl/LC_MESSAGES/django.mo and b/django/conf/locale/nl/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/nl/LC_MESSAGES/django.po b/django/conf/locale/nl/LC_MESSAGES/django.po index 9aaeef8af9..b8c516b72d 100644 --- a/django/conf/locale/nl/LC_MESSAGES/django.po +++ b/django/conf/locale/nl/LC_MESSAGES/django.po @@ -1,17 +1,12 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the django package. -# Johan C. Stöver , 2005. -# Rudolph Froger , 2006. -# + #, fuzzy msgid "" msgstr "" "Project-Id-Version: Django 1.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2006-12-09 15:51+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Johan C. Stöver \n" +"PO-Revision-Date: 2007-12-17 21:14+0100\n" +"Last-Translator: I.S. van Oostveen\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -69,7 +64,7 @@ msgstr "De waarde moet een geheel getal zijn." #: db/models/fields/__init__.py:381 msgid "This value must be either True or False." -msgstr "De waarde moet of True (waar) of False (onwaar) zijn." +msgstr "De waarde moet of True (Waar) of False (Onwaar) zijn." #: db/models/fields/__init__.py:397 msgid "This field cannot be null." @@ -279,7 +274,7 @@ msgstr "Alleen alfabetische karakters zijn toegestaan" #: core/validators.py:139 msgid "Year must be 1900 or later." -msgstr "Het jaartal moet 1900 of nieuwer zijn." +msgstr "Het jaartal moet 1900 of later zijn." #: core/validators.py:143 #, python-format @@ -296,20 +291,20 @@ msgstr "Geef een geldig e-mailadres op." #: core/validators.py:173 core/validators.py:442 forms/__init__.py:667 msgid "No file was submitted. Check the encoding type on the form." -msgstr "Er was geen bestand verstuurd. Controleer de encoding van het formulier." +msgstr "Er was geen bestand verstuurd. Controleer het coderings type van het formulier." #: core/validators.py:177 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -"Bestand ongeldig. Het bestand dat is gegeven is geen afbeelding of was " +"Bestand ongeldig. Het bestand dat is gegeven is geen afbeelding of is " "beschadigd." #: core/validators.py:184 #, python-format msgid "The URL %s does not point to a valid image." -msgstr "De URL %s wijst niet naar een afbeelding." +msgstr "De URL %s wijst niet naar een geldige afbeelding." #: core/validators.py:188 #, python-format @@ -321,7 +316,7 @@ msgstr "" #: core/validators.py:196 #, python-format msgid "The URL %s does not point to a valid QuickTime video." -msgstr "De URL %s wijst niet naar een QuickTime video." +msgstr "De URL %s wijst niet naar een geldige QuickTime video." #: core/validators.py:200 msgid "A valid URL is required." @@ -349,18 +344,18 @@ msgstr "Ongeldige URL: %s" #: core/validators.py:243 core/validators.py:245 #, python-format msgid "The URL %s is a broken link." -msgstr "De URL %s is een niet werkende link." +msgstr "De URL %s is niet een werkende link." #: core/validators.py:251 msgid "Enter a valid U.S. state abbreviation." -msgstr "Geef een geldige afkorting van een VS staat." +msgstr "Geef een geldige afkorting van een staat in de VS." #: core/validators.py:265 #, python-format msgid "Watch your mouth! The word %s is not allowed here." msgid_plural "Watch your mouth! The words %s are not allowed here." msgstr[0] "Pas op uw taalgebruik! Gebruik van %s niet toegestaan." -msgstr[1] "Pas op uw taalgebruik! Gebruik van de woorden %s niet toegestaan." +msgstr[1] "Pas op uw taalgebruik! Gebruik van de woorden %s is niet toegestaan." #: core/validators.py:272 #, python-format @@ -373,7 +368,7 @@ msgstr "Voer tenminste één veld in." #: core/validators.py:300 core/validators.py:311 msgid "Please enter both fields or leave them both empty." -msgstr "Voer waarden in in beide velden of laat beide leeg." +msgstr "Voer waarden in beide velden in of laat beide leeg." #: core/validators.py:318 #, python-format @@ -450,7 +445,7 @@ msgstr "Zorg ervoor dat het bestand hoogstens %s bytes groot is." #: core/validators.py:453 msgid "The format for this field is wrong." -msgstr "Het formaat van dit veld is fout." +msgstr "Het formaat van dit veld is foutief." #: core/validators.py:468 msgid "This field is invalid." @@ -536,7 +531,7 @@ msgid "" "Your Web browser doesn't appear to have cookies enabled. Cookies are " "required for logging in." msgstr "" -"Het lijkt erop dat uw browser geen cookies accepteerd. Om aan te melden " +"Het lijkt erop dat uw browser geen cookies accepteert. Om aan te melden " "moeten cookies worden geaccepteerd." #: contrib/auth/forms.py:59 contrib/admin/views/decorators.py:10 @@ -645,7 +640,7 @@ msgstr "supergebruiker status" msgid "" "Designates that this user has all permissions without explicitly assigning " "them." -msgstr "Bepaald dat deze gebruiker alle rechten heeft, zonder deze expliciet toe te wijzen." +msgstr "Bepaalt dat deze gebruiker alle rechten heeft, zonder deze expliciet toe te wijzen." #: contrib/auth/models.py:98 msgid "last login" @@ -685,7 +680,7 @@ msgstr "Rechten" #: contrib/auth/models.py:113 msgid "Important dates" -msgstr "Belangrijke data" +msgstr "Belangrijke datums" #: contrib/auth/models.py:114 msgid "Groups" @@ -783,7 +778,7 @@ msgid "" "Please log in again, because your session has expired. Don't worry: Your " "submission has been saved." msgstr "" -"Uw sessie is verlopen, meldt u opnieuw aan. Maakt u geen zorgen: Uw bijdrage " +"Uw sessie is verlopen, meld u opnieuw aan. Maakt u zich geen zorgen: Uw bijdrage " "is opgeslagen." #: contrib/admin/views/decorators.py:69 @@ -791,7 +786,7 @@ msgid "" "Looks like your browser isn't configured to accept cookies. Please enable " "cookies, reload this page, and try again." msgstr "" -"Het lijkt erop dat uw browser geen cookies accepteerd. Zet het gebruik van " +"Het lijkt erop dat uw browser geen cookies accepteert. Zet het gebruik van " "cookies aan in uw browser, laad deze pagina nogmaals en probeer het opnieuw." #: contrib/admin/views/decorators.py:83 @@ -928,7 +923,7 @@ msgstr "Model %r niet gevonden in app %r" #: contrib/admin/views/doc.py:183 #, python-format msgid "the related `%s.%s` object" -msgstr "the related `%s.%s` object" +msgstr "het gerelateerde `%s.%s` object" #: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 #: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 @@ -938,7 +933,7 @@ msgstr "model:" #: contrib/admin/views/doc.py:214 #, python-format msgid "related `%s.%s` objects" -msgstr "related `%s.%s` objects" +msgstr "de gerelateerde `%s.%s` objecten" #: contrib/admin/views/doc.py:219 #, python-format @@ -948,12 +943,12 @@ msgstr "alle %s" #: contrib/admin/views/doc.py:224 #, python-format msgid "number of %s" -msgstr "nummer van %s" +msgstr "aantal %s" #: contrib/admin/views/doc.py:229 #, python-format msgid "Fields on %s objects" -msgstr "Velden van %s objects" +msgstr "Velden van %s objecten" #: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 #: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 @@ -1139,11 +1134,11 @@ msgid "" "All of the following related items will be deleted:" msgstr "" "Weet u zeker dat u %(object_name)s \"%(escaped_object)s\" wilt verwijderen? Alle " -"volgende opjecten worden verwijderd:" +"volgende objecten worden verwijderd:" #: contrib/admin/templates/admin/delete_confirmation.html:26 msgid "Yes, I'm sure" -msgstr "Ja, Ik weet het zeker" +msgstr "Ja, ik weet het zeker" #: contrib/admin/templates/admin/404.html:4 #: contrib/admin/templates/admin/404.html:8 @@ -1197,7 +1192,7 @@ msgstr "Opslaan en nieuw item" #: contrib/admin/templates/admin/submit_line.html:6 msgid "Save and continue editing" -msgstr "Opslaan en bewerk opnieuw" +msgstr "Opslaan en opnieuw bewerken" #: contrib/admin/templates/admin/submit_line.html:7 msgid "Save" @@ -1224,7 +1219,7 @@ msgstr "Wijzigen" #: contrib/admin/templates/admin/index.html:44 msgid "You don't have permission to edit anything." -msgstr "U heeft geen rechten om iets te wijzigen" +msgstr "U heeft geen rechten om iets te wijzigen." #: contrib/admin/templates/admin/index.html:52 msgid "Recent Actions" @@ -1380,7 +1375,7 @@ msgstr "" "de bookmarklet vanuit elke pagina op de site worden gekozen. Let erop dat " "het soms\n" "noodzakelijk is dat de computer van waaruit de pagina wordt bekeken intern " -"is\n" +"is.\n" "(Raadpleeg uw systeembeheerder of uw computer zich op het interne netwerk " "bevind).

\n" @@ -1393,7 +1388,7 @@ msgid "" "Jumps you from any page to the documentation for the view that generates " "that page." msgstr "" -"Spring vanuit elke pagina naar de documentatie voor de view die gegenereerd " +"Springt vanuit elke pagina naar de documentatie voor de view die gegenereerd " "wordt door die pagina" #: contrib/admin/templates/admin_doc/bookmarklets.html:22 @@ -1406,7 +1401,7 @@ msgid "" "object." msgstr "" "Toont de content-type en unieke ID voor pagina's die een enkel object " -"voorsteld." +"voorstellen." #: contrib/admin/templates/admin_doc/bookmarklets.html:25 msgid "Edit this object (current window)" @@ -1414,7 +1409,7 @@ msgstr "Bewerk dit object (huidig venster)" #: contrib/admin/templates/admin_doc/bookmarklets.html:26 msgid "Jumps to the admin page for pages that represent a single object." -msgstr "Ga naar de beheerpagina voor pagina's die een enkel object weergeven." +msgstr "Gaat naar de beheerpagina voor pagina's die een enkel object weergeven." #: contrib/admin/templates/admin_doc/bookmarklets.html:28 msgid "Edit this object (new window)" @@ -1527,7 +1522,7 @@ msgid "" "password twice so we can verify you typed it in correctly." msgstr "" "Vanwege de beveiliging moet u uw oude en twee keer een nieuw " -"wachtwoordinvoeren, zodat we kunnen controleren of er geen typefouten zijn " +"wachtwoord invoeren, zodat we kunnen controleren of er geen typefouten zijn " "gemaakt." #: contrib/admin/templates/registration/password_change_form.html:17 @@ -1597,7 +1592,7 @@ msgid "" "Example: 'flatpages/contact_page.html'. If this isn't provided, the system " "will use 'flatpages/default.html'." msgstr "" -"Voorbeeld: 'flatpages/contact_page'. Als deze niet is opgegeven, dan wordt " +"Voorbeeld: 'flatpages/contact_page.html'. Als deze niet is opgegeven, dan wordt " "'flatpages/default.html' gebruikt." #: contrib/flatpages/models.py:14 @@ -1894,7 +1889,7 @@ msgstr "Een of meerdere verplichte velden zijn niet ingevuld" #: contrib/comments/views/comments.py:196 #: contrib/comments/views/comments.py:286 msgid "Somebody tampered with the comment form (security violation)" -msgstr "Iemand heeft het opmerkingenformulier gewijzigd (Beveilingsinbreuk)" +msgstr "Iemand heeft het opmerkingenformulier gewijzigd (beveilingsinbreuk)" #: contrib/comments/views/comments.py:206 #: contrib/comments/views/comments.py:292 diff --git a/django/contrib/admin/media/js/urlify.js b/django/contrib/admin/media/js/urlify.js index 74e6e57e6c..63e626d884 100644 --- a/django/contrib/admin/media/js/urlify.js +++ b/django/contrib/admin/media/js/urlify.js @@ -55,6 +55,12 @@ var POLISH_MAP = { 'Ź':'Z', 'Ż':'Z' } +var LATVIAN_MAP = { + 'ā':'a', 'č':'c', 'ē':'e', 'ģ':'g', 'ī':'i', 'ķ':'k', 'ļ':'l', 'ņ':'n', + 'š':'s', 'ū':'u', 'ž':'z', 'Ā':'A', 'Č':'C', 'Ē':'E', 'Ģ':'G', 'Ī':'i', + 'Ķ':'k', 'Ļ':'L', 'Ņ':'N', 'Š':'S', 'Ū':'u', 'Ž':'Z' +} + var ALL_DOWNCODE_MAPS=new Array() ALL_DOWNCODE_MAPS[0]=LATIN_MAP ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_MAP @@ -64,6 +70,7 @@ ALL_DOWNCODE_MAPS[4]=RUSSIAN_MAP ALL_DOWNCODE_MAPS[5]=UKRAINIAN_MAP ALL_DOWNCODE_MAPS[6]=CZECH_MAP ALL_DOWNCODE_MAPS[7]=POLISH_MAP +ALL_DOWNCODE_MAPS[8]=LATVIAN_MAP var Downcoder = new Object(); Downcoder.Initialize = function() diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index 4b0947eb34..1b32ee2e2e 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -12,6 +12,7 @@ from django.utils.safestring import mark_safe from django.db.models.query import QuerySet EMPTY_VALUE = '(None)' +DISPLAY_SIZE = 100 class EasyModel(object): def __init__(self, site, model): @@ -93,8 +94,8 @@ class EasyInstance(object): def __unicode__(self): val = smart_unicode(self.instance) - if len(val) > 30: - return val[:30] + u'...' + if len(val) > DISPLAY_SIZE: + return val[:DISPLAY_SIZE] + u'...' return val def __str__(self): diff --git a/django/contrib/localflavor/ar/forms.py b/django/contrib/localflavor/ar/forms.py index 6d86e4e676..6374a48c92 100644 --- a/django/contrib/localflavor/ar/forms.py +++ b/django/contrib/localflavor/ar/forms.py @@ -24,18 +24,20 @@ class ARPostalCodeField(RegexField): See http://www.correoargentino.com.ar/consulta_cpa/home.php """ + default_error_messages = { + 'invalid': ugettext("Enter a postal code in the format NNNN or ANNNNAAA."), + } + def __init__(self, *args, **kwargs): super(ARPostalCodeField, self).__init__(r'^\d{4}$|^[A-HJ-NP-Za-hj-np-z]\d{4}\D{3}$', - min_length=4, max_length=8, - error_message=ugettext("Enter a postal code in the format NNNN or ANNNNAAA."), - *args, **kwargs) + min_length=4, max_length=8, *args, **kwargs) def clean(self, value): value = super(ARPostalCodeField, self).clean(value) if value in EMPTY_VALUES: return u'' if len(value) not in (4, 8): - raise ValidationError(ugettext("Enter a postal code in the format NNNN or ANNNNAAA.")) + raise ValidationError(self.error_messages['invalid']) if len(value) == 8: return u'%s%s%s' % (value[0].upper(), value[1:5], value[5:].upper()) return value @@ -44,6 +46,11 @@ class ARDNIField(CharField): """ A field that validates `Documento Nacional de Identidad (DNI) numbers. """ + default_error_messages = { + 'invalid': ugettext("This field requires only numbers."), + 'max_digits': ugettext("This field requires 7 or 8 digits."), + } + def __init__(self, *args, **kwargs): super(ARDNIField, self).__init__(max_length=10, min_length=7, *args, **kwargs) @@ -58,10 +65,9 @@ class ARDNIField(CharField): if not value.isdigit(): value = value.replace('.', '') if not value.isdigit(): - raise ValidationError(ugettext("This field requires only numbers.")) + raise ValidationError(self.error_messages['invalid']) if len(value) not in (7, 8): - raise ValidationError( - ugettext("This field requires 7 or 8 digits.")) + raise ValidationError(self.error_messages['max_digits']) return value @@ -70,9 +76,13 @@ class ARCUITField(RegexField): This field validates a CUIT (Cdigo nico de Identificacin Tributaria). A CUIT is of the form XX-XXXXXXXX-V. The last digit is a check digit. """ + default_error_messages = { + 'invalid': ugettext('Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'), + 'checksum': ugettext("Invalid CUIT."), + } + def __init__(self, *args, **kwargs): super(ARCUITField, self).__init__(r'^\d{2}-?\d{8}-?\d$', - error_message=ugettext('Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'), *args, **kwargs) def clean(self, value): @@ -85,7 +95,7 @@ class ARCUITField(RegexField): return u'' value, cd = self._canon(value) if self._calc_cd(value) != cd: - raise ValidationError(ugettext("Invalid CUIT.")) + raise ValidationError(self.error_messages['checksum']) return self._format(value, cd) def _canon(self, cuit): diff --git a/django/contrib/localflavor/au/forms.py b/django/contrib/localflavor/au/forms.py index 33e883511b..ada27b69b9 100644 --- a/django/contrib/localflavor/au/forms.py +++ b/django/contrib/localflavor/au/forms.py @@ -12,14 +12,20 @@ PHONE_DIGITS_RE = re.compile(r'^(\d{10})$') class AUPostCodeField(RegexField): """Australian post code field.""" + default_error_messages = { + 'invalid': ugettext('Enter a 4 digit post code.'), + } + def __init__(self, *args, **kwargs): super(AUPostCodeField, self).__init__(r'^\d{4}$', - max_length=None, min_length=None, - error_message=ugettext('Enter a 4 digit post code.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class AUPhoneNumberField(Field): """Australian phone number field.""" + default_error_messages = { + 'invalid': u'Phone numbers must contain 10 digits.', + } + def clean(self, value): """ Validate a phone number. Strips parentheses, whitespace and hyphens. @@ -31,7 +37,7 @@ class AUPhoneNumberField(Field): phone_match = PHONE_DIGITS_RE.search(value) if phone_match: return u'%s' % phone_match.group(1) - raise ValidationError(u'Phone numbers must contain 10 digits.') + raise ValidationError(self.error_messages['invalid']) class AUStateSelect(Select): """ diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py index 58bf795292..aa7e3b23fd 100644 --- a/django/contrib/localflavor/br/forms.py +++ b/django/contrib/localflavor/br/forms.py @@ -17,13 +17,19 @@ except NameError: phone_digits_re = re.compile(r'^(\d{2})[-\.]?(\d{4})[-\.]?(\d{4})$') class BRZipCodeField(RegexField): + default_error_messages = { + 'invalid': _('Enter a zip code in the format XXXXX-XXX.'), + } + def __init__(self, *args, **kwargs): super(BRZipCodeField, self).__init__(r'^\d{5}-\d{3}$', - max_length=None, min_length=None, - error_message=_('Enter a zip code in the format XXXXX-XXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class BRPhoneNumberField(Field): + default_error_messages = { + 'invalid': _('Phone numbers must be in XX-XXXX-XXXX format.'), + } + def clean(self, value): super(BRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: @@ -32,7 +38,7 @@ class BRPhoneNumberField(Field): m = phone_digits_re.search(value) if m: return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) - raise ValidationError(_('Phone numbers must be in XX-XXXX-XXXX format.')) + raise ValidationError(self.error_messages['invalid']) class BRStateSelect(Select): """ @@ -48,6 +54,9 @@ class BRStateChoiceField(Field): A choice field that uses a list of Brazilian states as its choices. """ widget = Select + default_error_messages = { + 'invalid': _(u'Select a valid brazilian state. That state is not one of the available states.'), + } def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None): @@ -65,9 +74,7 @@ class BRStateChoiceField(Field): return value valid_values = set([smart_unicode(k) for k, v in self.widget.choices]) if value not in valid_values: - raise ValidationError(_(u'Select a valid brazilian state.' - u' That state is not one' - u' of the available states.')) + raise ValidationError(self.error_messages['invalid']) return value def DV_maker(v): @@ -83,6 +90,12 @@ class BRCPFField(CharField): More information: http://en.wikipedia.org/wiki/Cadastro_de_Pessoas_F%C3%ADsicas """ + default_error_messages = { + 'invalid': _("Invalid CPF number."), + 'max_digits': _("This field requires at most 11 digits or 14 characters."), + 'digits_only': _("This field requires only numbers."), + } + def __init__(self, *args, **kwargs): super(BRCPFField, self).__init__(max_length=14, min_length=11, *args, **kwargs) @@ -100,9 +113,9 @@ class BRCPFField(CharField): try: int(value) except ValueError: - raise ValidationError(_("This field requires only numbers.")) + raise ValidationError(self.error_messages['digits_only']) if len(value) != 11: - raise ValidationError(_("This field requires at most 11 digits or 14 characters.")) + raise ValidationError(self.error_messages['max_digits']) orig_dv = value[-2:] new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(10, 1, -1))]) @@ -112,11 +125,17 @@ class BRCPFField(CharField): new_2dv = DV_maker(new_2dv % 11) value = value[:-1] + str(new_2dv) if value[-2:] != orig_dv: - raise ValidationError(_("Invalid CPF number.")) + raise ValidationError(self.error_messages['invalid']) return orig_value class BRCNPJField(Field): + default_error_messages = { + 'invalid': _("Invalid CNPJ number."), + 'digits_only': _("This field requires only numbers."), + 'max_digits': _("This field requires at least 14 digits"), + } + def clean(self, value): """ Value can be either a string in the format XX.XXX.XXX/XXXX-XX or a @@ -131,10 +150,9 @@ class BRCNPJField(Field): try: int(value) except ValueError: - raise ValidationError("This field requires only numbers.") + raise ValidationError(self.error_messages['digits_only']) if len(value) != 14: - raise ValidationError( - _("This field requires at least 14 digits")) + raise ValidationError(self.error_messages['max_digits']) orig_dv = value[-2:] new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(5, 1, -1) + range(9, 1, -1))]) @@ -144,7 +162,6 @@ class BRCNPJField(Field): new_2dv = DV_maker(new_2dv % 11) value = value[:-1] + str(new_2dv) if value[-2:] != orig_dv: - raise ValidationError(_("Invalid CNPJ number.")) + raise ValidationError(self.error_messages['invalid']) return orig_value - diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py index 98f65a5c6c..b40dba8335 100644 --- a/django/contrib/localflavor/ca/forms.py +++ b/django/contrib/localflavor/ca/forms.py @@ -1,37 +1,43 @@ -""" -Canada-specific Form helpers -""" - -from django.newforms import ValidationError -from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES -from django.newforms.util import smart_unicode +""" +Canada-specific Form helpers +""" + +from django.newforms import ValidationError +from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES +from django.newforms.util import smart_unicode from django.utils.translation import gettext, ugettext -import re - +import re + phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$') sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$") - -class CAPostalCodeField(RegexField): - """Canadian postal code field.""" - def __init__(self, *args, **kwargs): - super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXYZ]\d[A-Z] \d[A-Z]\d$', - max_length=None, min_length=None, - error_message=gettext(u'Enter a postal code in the format XXX XXX.'), - *args, **kwargs) - -class CAPhoneNumberField(Field): - """Canadian phone number field.""" - def clean(self, value): - """Validate a phone number. - """ - super(CAPhoneNumberField, self).clean(value) + +class CAPostalCodeField(RegexField): + """Canadian postal code field.""" + default_error_messages = { + 'invalid': gettext(u'Enter a postal code in the format XXX XXX.'), + } + + def __init__(self, *args, **kwargs): + super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXYZ]\d[A-Z] \d[A-Z]\d$', + max_length=None, min_length=None, *args, **kwargs) + +class CAPhoneNumberField(Field): + """Canadian phone number field.""" + default_error_messages = { + 'invalid': u'Phone numbers must be in XXX-XXX-XXXX format.', + } + + def clean(self, value): + """Validate a phone number. + """ + super(CAPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return u'' value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) m = phone_digits_re.search(value) if m: return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) - raise ValidationError(u'Phone numbers must be in XXX-XXX-XXXX format.') + raise ValidationError(self.error_messages['invalid']) class CAProvinceField(Field): """ @@ -39,6 +45,10 @@ class CAProvinceField(Field): It normalizes the input to the standard two-leter postal service abbreviation for the given province. """ + default_error_messages = { + 'invalid': u'Enter a Canadian province or territory.', + } + def clean(self, value): from ca_provinces import PROVINCES_NORMALIZED super(CAProvinceField, self).clean(value) @@ -53,45 +63,49 @@ class CAProvinceField(Field): return PROVINCES_NORMALIZED[value.strip().lower()].decode('ascii') except KeyError: pass - raise ValidationError(u'Enter a Canadian province or territory.') - -class CAProvinceSelect(Select): - """ - A Select widget that uses a list of Canadian provinces and - territories as its choices. - """ - def __init__(self, attrs=None): - from ca_provinces import PROVINCE_CHOICES # relative import + raise ValidationError(self.error_messages['invalid']) + +class CAProvinceSelect(Select): + """ + A Select widget that uses a list of Canadian provinces and + territories as its choices. + """ + def __init__(self, attrs=None): + from ca_provinces import PROVINCE_CHOICES # relative import super(CAProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES) - + class CASocialInsuranceNumberField(Field): """ A Canadian Social Insurance Number (SIN). Checks the following rules to determine whether the number is valid: - * Conforms to the XXX-XXX-XXXX format. + * Conforms to the XXX-XXX-XXX format. * Passes the check digit process "Luhn Algorithm" See: http://en.wikipedia.org/wiki/Social_Insurance_Number """ + default_error_messages = { + 'invalid': ugettext('Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format.'), + } + def clean(self, value): super(CASocialInsuranceNumberField, self).clean(value) if value in EMPTY_VALUES: return u'' - msg = ugettext('Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.') + match = re.match(sin_re, value) if not match: - raise ValidationError(msg) - - number = u'%s-%s-%s' % (match.group(1), match.group(2), match.group(3)) + raise ValidationError(self.error_messages['invalid']) + + number = u'%s-%s-%s' % (match.group(1), match.group(2), match.group(3)) check_number = u'%s%s%s' % (match.group(1), match.group(2), match.group(3)) if not self.luhn_checksum_is_valid(check_number): - raise ValidationError(msg) + raise ValidationError(self.error_messages['invalid']) return number - + def luhn_checksum_is_valid(self, number): """ - Checks to make sure that the SIN passes a luhn mod-10 checksum + Checks to make sure that the SIN passes a luhn mod-10 checksum See: http://en.wikipedia.org/wiki/Luhn_algorithm """ @@ -109,4 +123,4 @@ class CASocialInsuranceNumberField(Field): sum = sum + digit - return ( (sum % 10) == 0 ) \ No newline at end of file + return ( (sum % 10) == 0 ) diff --git a/django/contrib/localflavor/ch/forms.py b/django/contrib/localflavor/ch/forms.py index 5e601f5d74..2158fba697 100644 --- a/django/contrib/localflavor/ch/forms.py +++ b/django/contrib/localflavor/ch/forms.py @@ -12,11 +12,13 @@ id_re = re.compile(r"^(?P\w{8})(?P(\d{1}|<))(?P\d{1})$ phone_digits_re = re.compile(r'^0([1-9]{1})\d{8}$') class CHZipCodeField(RegexField): + default_error_messages = { + 'invalid': ugettext('Enter a zip code in the format XXXX.'), + } + def __init__(self, *args, **kwargs): super(CHZipCodeField, self).__init__(r'^\d{4}$', - max_length=None, min_length=None, - error_message=ugettext('Enter a zip code in the format XXXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class CHPhoneNumberField(Field): """ @@ -25,6 +27,10 @@ class CHPhoneNumberField(Field): '0XX.XXX.XX.XX' and '0XXXXXXXXX' validate but are corrected to '0XX XXX XX XX'. """ + default_error_messages = { + 'invalid': 'Phone numbers must be in 0XX XXX XX XX format.', + } + def clean(self, value): super(CHPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: @@ -33,7 +39,7 @@ class CHPhoneNumberField(Field): m = phone_digits_re.search(value) if m: return u'%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10]) - raise ValidationError('Phone numbers must be in 0XX XXX XX XX format.') + raise ValidationError(self.error_messages['invalid']) class CHStateSelect(Select): """ @@ -54,6 +60,10 @@ class CHIdentityCardNumberField(Field): Algorithm is documented at http://adi.kousz.ch/artikel/IDCHE.htm """ + default_error_messages = { + 'invalid': ugettext('Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.'), + } + def has_valid_checksum(self, number): given_number, given_checksum = number[:-1], number[-1] new_number = given_number @@ -87,23 +97,22 @@ class CHIdentityCardNumberField(Field): def clean(self, value): super(CHIdentityCardNumberField, self).clean(value) - error_msg = ugettext('Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.') if value in EMPTY_VALUES: return u'' match = re.match(id_re, value) if not match: - raise ValidationError(error_msg) + raise ValidationError(self.error_messages['invalid']) idnumber, pos9, checksum = match.groupdict()['idnumber'], match.groupdict()['pos9'], match.groupdict()['checksum'] if idnumber == '00000000' or \ idnumber == 'A0000000': - raise ValidationError(error_msg) + raise ValidationError(self.error_messages['invalid']) all_digits = "%s%s%s" % (idnumber, pos9, checksum) if not self.has_valid_checksum(all_digits): - raise ValidationError(error_msg) + raise ValidationError(self.error_messages['invalid']) return u'%s%s%s' % (idnumber, pos9, checksum) diff --git a/django/contrib/localflavor/cl/forms.py b/django/contrib/localflavor/cl/forms.py index d2d37a3712..2f5ac29b39 100644 --- a/django/contrib/localflavor/cl/forms.py +++ b/django/contrib/localflavor/cl/forms.py @@ -25,16 +25,21 @@ class CLRutField(RegexField): Samples for testing are available from https://palena.sii.cl/cvc/dte/ee_empresas_emisoras.html """ + default_error_messages = { + 'invalid': ugettext('Enter a valid Chilean RUT.'), + 'strict': ugettext('Enter a valid Chilean RUT. The format is XX.XXX.XXX-X.'), + 'checksum': ugettext('The Chilean RUT is not valid.'), + } + def __init__(self, *args, **kwargs): if 'strict' in kwargs: del kwargs['strict'] super(CLRutField, self).__init__(r'^(\d{1,2}\.)?\d{3}\.\d{3}-[\dkK]$', - error_message=ugettext('Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'), - *args, **kwargs) + error_message=self.default_error_messages['strict'], *args, **kwargs) else: # In non-strict mode, accept RUTs that validate but do not exist in # the real world. - super(CLRutField, self).__init__(r'^[\d\.]{1,11}-?[\dkK]$', error_message=ugettext('Enter valid a Chilean RUT'), *args, **kwargs) + super(CLRutField, self).__init__(r'^[\d\.]{1,11}-?[\dkK]$', *args, **kwargs) def clean(self, value): """ @@ -47,7 +52,7 @@ class CLRutField(RegexField): if self._algorithm(rut) == verificador: return self._format(rut, verificador) else: - raise ValidationError(u'The Chilean RUT is not valid.') + raise ValidationError(self.error_messages['checksum']) def _algorithm(self, rut): """ diff --git a/django/contrib/localflavor/de/forms.py b/django/contrib/localflavor/de/forms.py index 1a0b8587a6..76ce215dca 100644 --- a/django/contrib/localflavor/de/forms.py +++ b/django/contrib/localflavor/de/forms.py @@ -10,11 +10,12 @@ import re id_re = re.compile(r"^(?P\d{10})(?P\w{1,3})[-\ ]?(?P\d{7})[-\ ]?(?P\d{7})[-\ ]?(?P\d{1})$") class DEZipCodeField(RegexField): + default_error_messages = { + 'invalid': ugettext('Enter a zip code in the format XXXXX.'), + } def __init__(self, *args, **kwargs): super(DEZipCodeField, self).__init__(r'^\d{5}$', - max_length=None, min_length=None, - error_message=ugettext('Enter a zip code in the format XXXXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class DEStateSelect(Select): """ @@ -36,6 +37,10 @@ class DEIdentityCardNumberField(Field): Algorithm is documented at http://de.wikipedia.org/wiki/Personalausweis """ + default_error_messages = { + 'invalid': ugettext('Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.'), + } + def has_valid_checksum(self, number): given_number, given_checksum = number[:-1], number[-1] calculated_checksum = 0 @@ -57,23 +62,22 @@ class DEIdentityCardNumberField(Field): def clean(self, value): super(DEIdentityCardNumberField, self).clean(value) - error_msg = ugettext('Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.') if value in EMPTY_VALUES: return u'' match = re.match(id_re, value) if not match: - raise ValidationError(error_msg) + raise ValidationError(self.error_messages['invalid']) gd = match.groupdict() residence, origin = gd['residence'], gd['origin'] birthday, validity, checksum = gd['birthday'], gd['validity'], gd['checksum'] if residence == '0000000000' or birthday == '0000000' or validity == '0000000': - raise ValidationError(error_msg) + raise ValidationError(self.error_messages['invalid']) all_digits = u"%s%s%s%s" % (residence, birthday, validity, checksum) if not self.has_valid_checksum(residence) or not self.has_valid_checksum(birthday) or \ not self.has_valid_checksum(validity) or not self.has_valid_checksum(all_digits): - raise ValidationError(error_msg) + raise ValidationError(self.error_messages['invalid']) return u'%s%s-%s-%s-%s' % (residence, origin, birthday, validity, checksum) diff --git a/django/contrib/localflavor/es/forms.py b/django/contrib/localflavor/es/forms.py index 29b41828f6..81186f8b3d 100644 --- a/django/contrib/localflavor/es/forms.py +++ b/django/contrib/localflavor/es/forms.py @@ -15,12 +15,14 @@ class ESPostalCodeField(RegexField): Spanish postal code is a five digits string, with two first digits between 01 and 52, assigned to provinces code. """ + default_error_messages = { + 'invalid': _('Enter a valid postal code in the range and format 01XXX - 52XXX.'), + } + def __init__(self, *args, **kwargs): super(ESPostalCodeField, self).__init__( r'^(0[1-9]|[1-4][0-9]|5[0-2])\d{3}$', - max_length=None, min_length=None, - error_message=_('Enter a valid postal code in the range and format 01XXX - 52XXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class ESPhoneNumberField(RegexField): """ @@ -33,11 +35,13 @@ class ESPhoneNumberField(RegexField): TODO: accept and strip characters like dot, hyphen... in phone number """ + default_error_messages = { + 'invalid': _('Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or 9XXXXXXXX.'), + } + def __init__(self, *args, **kwargs): super(ESPhoneNumberField, self).__init__(r'^(6|8|9)\d{8}$', - max_length=None, min_length=None, - error_message=_('Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or 9XXXXXXXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class ESIdentityCardNumberField(RegexField): """ @@ -58,19 +62,23 @@ class ESIdentityCardNumberField(RegexField): public, and different authors have different opinions on which ones allows letters, so both validations are assumed true for all types. """ + default_error_messages = { + 'invalid': _('Please enter a valid NIF, NIE, or CIF.'), + 'invalid_only_nif': _('Please enter a valid NIF or NIE.'), + 'invalid_nif': _('Invalid checksum for NIF.'), + 'invalid_nie': _('Invalid checksum for NIE.'), + 'invalid_cif': _('Invalid checksum for CIF.'), + } + def __init__(self, only_nif=False, *args, **kwargs): self.only_nif = only_nif self.nif_control = 'TRWAGMYFPDXBNJZSQVHLCKE' self.cif_control = 'JABCDEFGHI' self.cif_types = 'ABCDEFGHKLMNPQS' self.nie_types = 'XT' - if self.only_nif: - self.id_types = 'NIF or NIE' - else: - self.id_types = 'NIF, NIE, or CIF' super(ESIdentityCardNumberField, self).__init__(r'^([%s]?)[ -]?(\d+)[ -]?([%s]?)$' % (self.cif_types + self.nie_types + self.cif_types.lower() + self.nie_types.lower(), self.nif_control + self.nif_control.lower()), max_length=None, min_length=None, - error_message=_('Please enter a valid %s.' % self.id_types), + error_message=self.default_error_messages['invalid%s' % (self.only_nif and '_only_nif' or '')], *args, **kwargs) def clean(self, value): @@ -88,13 +96,13 @@ class ESIdentityCardNumberField(RegexField): if letter2 == nif_get_checksum(number): return value else: - raise ValidationError, _('Invalid checksum for NIF.') + raise ValidationError, self.error_messages['invalid_nif'] elif letter1 in self.nie_types and letter2: # NIE if letter2 == nif_get_checksum(number): return value else: - raise ValidationError, _('Invalid checksum for NIE.') + raise ValidationError, self.error_messages['invalid_nie'] elif not self.only_nif and letter1 in self.cif_types and len(number) in [7, 8]: # CIF if not letter2: @@ -103,9 +111,9 @@ class ESIdentityCardNumberField(RegexField): if letter2 in [checksum, self.cif_control[checksum]]: return value else: - raise ValidationError, _('Invalid checksum for CIF.') + raise ValidationError, self.error_messages['invalid_cif'] else: - raise ValidationError, _('Please enter a valid %s.' % self.id_types) + raise ValidationError, self.error_messages['invalid'] class ESCCCField(RegexField): """ @@ -130,11 +138,14 @@ class ESCCCField(RegexField): TODO: allow IBAN validation too """ + default_error_messages = { + 'invalid': _('Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX.'), + 'checksum': _('Invalid checksum for bank account number.'), + } + def __init__(self, *args, **kwargs): super(ESCCCField, self).__init__(r'^\d{4}[ -]?\d{4}[ -]?\d{2}[ -]?\d{10}$', - max_length=None, min_length=None, - error_message=_('Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) def clean(self, value): super(ESCCCField, self).clean(value) @@ -147,7 +158,7 @@ class ESCCCField(RegexField): if get_checksum('00' + entity + office) + get_checksum(account) == checksum: return value else: - raise ValidationError, _('Invalid checksum for bank account number.') + raise ValidationError, self.error_messages['checksum'] class ESRegionSelect(Select): """ diff --git a/django/contrib/localflavor/fi/forms.py b/django/contrib/localflavor/fi/forms.py index 82e024f7b1..a0274da44c 100644 --- a/django/contrib/localflavor/fi/forms.py +++ b/django/contrib/localflavor/fi/forms.py @@ -8,11 +8,12 @@ from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES from django.utils.translation import ugettext class FIZipCodeField(RegexField): + default_error_messages = { + 'invalid': ugettext('Enter a zip code in the format XXXXX.'), + } def __init__(self, *args, **kwargs): super(FIZipCodeField, self).__init__(r'^\d{5}$', - max_length=None, min_length=None, - error_message=ugettext('Enter a zip code in the format XXXXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class FIMunicipalitySelect(Select): """ @@ -23,6 +24,10 @@ class FIMunicipalitySelect(Select): super(FIMunicipalitySelect, self).__init__(attrs, choices=MUNICIPALITY_CHOICES) class FISocialSecurityNumber(Field): + default_error_messages = { + 'invalid': ugettext('Enter a valid Finnish social security number.'), + } + def clean(self, value): super(FISocialSecurityNumber, self).clean(value) if value in EMPTY_VALUES: @@ -37,9 +42,9 @@ class FISocialSecurityNumber(Field): (?P(\d{3})) (?P[%s])$""" % checkmarks, value, re.VERBOSE | re.IGNORECASE) if not result: - raise ValidationError(ugettext('Enter a valid Finnish social security number.')) + raise ValidationError(self.error_messages['invalid']) gd = result.groupdict() checksum = int(gd['date'] + gd['serial']) if checkmarks[checksum % len(checkmarks)] == gd['checksum'].upper(): return u'%s' % value.upper() - raise ValidationError(ugettext('Enter a valid Finnish social security number.')) + raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/fr/forms.py b/django/contrib/localflavor/fr/forms.py index cc0a5db259..635b62edd5 100644 --- a/django/contrib/localflavor/fr/forms.py +++ b/django/contrib/localflavor/fr/forms.py @@ -11,11 +11,13 @@ import re phone_digits_re = re.compile(r'^0\d(\s|\.)?(\d{2}(\s|\.)?){3}\d{2}$') class FRZipCodeField(RegexField): + default_error_messages = { + 'invalid': ugettext('Enter a zip code in the format XXXXX.'), + } + def __init__(self, *args, **kwargs): super(FRZipCodeField, self).__init__(r'^\d{5}$', - max_length=None, min_length=None, - error_message=ugettext('Enter a zip code in the format XXXXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class FRPhoneNumberField(Field): """ @@ -24,6 +26,10 @@ class FRPhoneNumberField(Field): '0X.XX.XX.XX.XX' and '0XXXXXXXXX' validate but are corrected to '0X XX XX XX XX'. """ + default_error_messages = { + 'invalid': u'Phone numbers must be in 0X XX XX XX XX format.', + } + def clean(self, value): super(FRPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: @@ -32,7 +38,7 @@ class FRPhoneNumberField(Field): m = phone_digits_re.search(value) if m: return u'%s %s %s %s %s' % (value[0:2], value[2:4], value[4:6], value[6:8], value[8:10]) - raise ValidationError(u'Phone numbers must be in 0X XX XX XX XX format.') + raise ValidationError(self.error_messages['invalid']) class FRDepartmentSelect(Select): """ diff --git a/django/contrib/localflavor/in_/forms.py b/django/contrib/localflavor/in_/forms.py index 5febc40e67..1cb303d772 100644 --- a/django/contrib/localflavor/in_/forms.py +++ b/django/contrib/localflavor/in_/forms.py @@ -10,11 +10,13 @@ import re class INZipCodeField(RegexField): + default_error_messages = { + 'invalid': gettext(u'Enter a zip code in the format XXXXXXX.'), + } + def __init__(self, *args, **kwargs): super(INZipCodeField, self).__init__(r'^\d{6}$', - max_length=None, min_length=None, - error_message=gettext(u'Enter a zip code in the format XXXXXXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class INStateField(Field): """ @@ -22,6 +24,10 @@ class INStateField(Field): abbreviation. It normalizes the input to the standard two-letter vehicle registration abbreviation for the given state or union territory """ + default_error_messages = { + 'invalid': u'Enter a Indian state or territory.', + } + def clean(self, value): from in_states import STATES_NORMALIZED super(INStateField, self).clean(value) @@ -36,7 +42,7 @@ class INStateField(Field): return smart_unicode(STATES_NORMALIZED[value.strip().lower()]) except KeyError: pass - raise ValidationError(u'Enter a Indian state or territory.') + raise ValidationError(self.error_messages['invalid']) class INStateSelect(Select): """ diff --git a/django/contrib/localflavor/is_/forms.py b/django/contrib/localflavor/is_/forms.py index ef8799e29c..ea7a1e00c3 100644 --- a/django/contrib/localflavor/is_/forms.py +++ b/django/contrib/localflavor/is_/forms.py @@ -13,10 +13,14 @@ class ISIdNumberField(RegexField): Icelandic identification number (kennitala). This is a number every citizen of Iceland has. """ + default_error_messages = { + 'invalid': ugettext('Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.'), + 'checksum': ugettext(u'The Icelandic identification number is not valid.'), + } + def __init__(self, *args, **kwargs): - error_msg = ugettext('Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.') kwargs['min_length'],kwargs['max_length'] = 10,11 - super(ISIdNumberField, self).__init__(r'^\d{6}(-| )?\d{4}$', error_message=error_msg, *args, **kwargs) + super(ISIdNumberField, self).__init__(r'^\d{6}(-| )?\d{4}$', *args, **kwargs) def clean(self, value): value = super(ISIdNumberField, self).clean(value) @@ -28,7 +32,7 @@ class ISIdNumberField(RegexField): if self._validate(value): return self._format(value) else: - raise ValidationError(ugettext(u'The Icelandic identification number is not valid.')) + raise ValidationError(self.error_messages['checksum']) def _canonify(self, value): """ diff --git a/django/contrib/localflavor/it/forms.py b/django/contrib/localflavor/it/forms.py index 0b6eecec45..88e65734e4 100644 --- a/django/contrib/localflavor/it/forms.py +++ b/django/contrib/localflavor/it/forms.py @@ -10,11 +10,12 @@ from django.contrib.localflavor.it.util import ssn_check_digit, vat_number_check import re class ITZipCodeField(RegexField): + default_error_messages = { + 'invalid': ugettext('Enter a valid zip code.'), + } def __init__(self, *args, **kwargs): super(ITZipCodeField, self).__init__(r'^\d{5}$', - max_length=None, min_length=None, - error_message=ugettext('Enter a valid zip code.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class ITRegionSelect(Select): """ @@ -38,11 +39,13 @@ class ITSocialSecurityNumberField(RegexField): For reference see http://www.agenziaentrate.it/ and search for 'Informazioni sulla codificazione delle persone fisiche'. """ - err_msg = ugettext(u'Enter a valid Social Security number.') + default_error_messages = { + 'invalid': ugettext(u'Enter a valid Social Security number.'), + } + def __init__(self, *args, **kwargs): super(ITSocialSecurityNumberField, self).__init__(r'^\w{3}\s*\w{3}\s*\w{5}\s*\w{5}$', - max_length=None, min_length=None, error_message=self.err_msg, - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) def clean(self, value): value = super(ITSocialSecurityNumberField, self).clean(value) @@ -52,26 +55,29 @@ class ITSocialSecurityNumberField(RegexField): try: check_digit = ssn_check_digit(value) except ValueError: - raise ValidationError(self.err_msg) + raise ValidationError(self.error_messages['invalid']) if not value[15] == check_digit: - raise ValidationError(self.err_msg) + raise ValidationError(self.error_messages['invalid']) return value class ITVatNumberField(Field): """ A form field that validates Italian VAT numbers (partita IVA). """ + default_error_messages = { + 'invalid': ugettext(u'Enter a valid VAT number.'), + } + def clean(self, value): value = super(ITVatNumberField, self).clean(value) if value == u'': return value - err_msg = ugettext(u'Enter a valid VAT number.') try: vat_number = int(value) except ValueError: - raise ValidationError(err_msg) + raise ValidationError(self.error_messages['invalid']) vat_number = str(vat_number).zfill(11) check_digit = vat_number_check_digit(vat_number[0:10]) if not vat_number[10] == check_digit: - raise ValidationError(err_msg) + raise ValidationError(self.error_messages['invalid']) return smart_unicode(vat_number) diff --git a/django/contrib/localflavor/jp/forms.py b/django/contrib/localflavor/jp/forms.py index 682ffb8c31..13083aab6e 100644 --- a/django/contrib/localflavor/jp/forms.py +++ b/django/contrib/localflavor/jp/forms.py @@ -15,11 +15,13 @@ class JPPostalCodeField(RegexField): Accepts 7 digits, with or without a hyphen. """ + default_error_messages = { + 'invalid': ugettext('Enter a postal code in the format XXXXXXX or XXX-XXXX.'), + } + def __init__(self, *args, **kwargs): super(JPPostalCodeField, self).__init__(r'^\d{3}-\d{4}$|^\d{7}$', - max_length=None, min_length=None, - error_message=ugettext('Enter a postal code in the format XXXXXXX or XXX-XXXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) def clean(self, value): """ diff --git a/django/contrib/localflavor/nl/forms.py b/django/contrib/localflavor/nl/forms.py index e202ab0595..748484926e 100644 --- a/django/contrib/localflavor/nl/forms.py +++ b/django/contrib/localflavor/nl/forms.py @@ -17,24 +17,27 @@ class NLZipCodeField(Field): """ A Dutch postal code field. """ + default_error_messages = { + 'invalid': _('Enter a valid postal code'), + } + def clean(self, value): super(NLZipCodeField, self).clean(value) if value in EMPTY_VALUES: return u'' - - msg = _('Enter a valid postal code') + value = value.strip().upper().replace(' ', '') if not pc_re.search(value): - raise ValidationError(msg) - + raise ValidationError(self.error_messages['invalid']) + if int(value[:4]) < 1000: - raise ValidationError(msg) - + raise ValidationError(self.error_messages['invalid']) + return u'%s %s' % (value[:4], value[4:]) class NLProvinceSelect(Select): """ - A Select widget that uses a list of provinces of the Netherlands as its + A Select widget that uses a list of provinces of the Netherlands as its choices. """ def __init__(self, attrs=None): @@ -45,48 +48,53 @@ class NLPhoneNumberField(Field): """ A Dutch telephone number field. """ + default_error_messages = { + 'invalid': _('Enter a valid phone number'), + } + def clean(self, value): super(NLPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: return u'' - - msg = _('Enter a valid phone number') + phone_nr = re.sub('[\-\s\(\)]', '', smart_unicode(value)) - + if len(phone_nr) == 10 and numeric_re.search(phone_nr): return value - + if phone_nr[:3] == '+31' and len(phone_nr) == 12 and \ numeric_re.search(phone_nr[3:]): return value - - raise ValidationError(msg) + + raise ValidationError(self.error_messages['invalid']) class NLSoFiNumberField(Field): """ A Dutch social security number (SoFi/BSN) field. - + http://nl.wikipedia.org/wiki/Sofinummer """ + default_error_messages = { + 'invalid': _('Enter a valid SoFi number'), + } + def clean(self, value): super(NLSoFiNumberField, self).clean(value) if value in EMPTY_VALUES: return u'' - - msg = _('Enter a valid SoFi number') - + if not sofi_re.search(value): - raise ValidationError(msg) - + raise ValidationError(self.error_messages['invalid']) + if int(value) == 0: - raise ValidationError(msg) - + raise ValidationError(self.error_messages['invalid']) + checksum = 0 for i in range(9, 1, -1): checksum += int(value[9-i]) * i checksum -= int(value[-1]) - + if checksum % 11 != 0: - raise ValidationError(msg) - + raise ValidationError(self.error_messages['invalid']) + return value diff --git a/django/contrib/localflavor/no/forms.py b/django/contrib/localflavor/no/forms.py index 2de3df14d8..8dc001955d 100644 --- a/django/contrib/localflavor/no/forms.py +++ b/django/contrib/localflavor/no/forms.py @@ -8,11 +8,13 @@ from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES from django.utils.translation import ugettext class NOZipCodeField(RegexField): + default_error_messages = { + 'invalid': ugettext('Enter a zip code in the format XXXX.'), + } + def __init__(self, *args, **kwargs): super(NOZipCodeField, self).__init__(r'^\d{4}$', - max_length=None, min_length=None, - error_message=ugettext('Enter a zip code in the format XXXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class NOMunicipalitySelect(Select): """ @@ -27,14 +29,17 @@ class NOSocialSecurityNumber(Field): """ Algorithm is documented at http://no.wikipedia.org/wiki/Personnummer """ + default_error_messages = { + 'invalid': ugettext(u'Enter a valid Norwegian social security number.'), + } + def clean(self, value): super(NOSocialSecurityNumber, self).clean(value) if value in EMPTY_VALUES: return u'' - msg = ugettext(u'Enter a valid Norwegian social security number.') if not re.match(r'^\d{11}$', value): - raise ValidationError(msg) + raise ValidationError(self.error_messages['invalid']) day = int(value[:2]) month = int(value[2:4]) @@ -52,7 +57,7 @@ class NOSocialSecurityNumber(Field): if 900 <= inum < 1000 and year2 > 39: self.birthday = datetime.date(1900+year2, month, day) except ValueError: - raise ValidationError(msg) + raise ValidationError(self.error_messages['invalid']) sexnum = int(value[8]) if sexnum % 2 == 0: @@ -68,9 +73,9 @@ class NOSocialSecurityNumber(Field): return sum([(a * b) for (a, b) in zip(aval, bval)]) if multiply_reduce(digits, weight_1) % 11 != 0: - raise ValidationError(msg) + raise ValidationError(self.error_messages['invalid']) if multiply_reduce(digits, weight_2) % 11 != 0: - raise ValidationError(msg) + raise ValidationError(self.error_messages['invalid']) return value diff --git a/django/contrib/localflavor/pe/forms.py b/django/contrib/localflavor/pe/forms.py index b1ae215417..57c9d441f7 100644 --- a/django/contrib/localflavor/pe/forms.py +++ b/django/contrib/localflavor/pe/forms.py @@ -19,6 +19,11 @@ class PEDNIField(CharField): """ A field that validates `Documento Nacional de IdentidadŽ (DNI) numbers. """ + default_error_messages = { + 'invalid': ugettext("This field requires only numbers."), + 'max_digits': ugettext("This field requires 8 digits."), + } + def __init__(self, *args, **kwargs): super(PEDNIField, self).__init__(max_length=8, min_length=8, *args, **kwargs) @@ -31,9 +36,9 @@ class PEDNIField(CharField): if value in EMPTY_VALUES: return u'' if not value.isdigit(): - raise ValidationError(ugettext("This field requires only numbers.")) + raise ValidationError(self.error_messages['invalid']) if len(value) != 8: - raise ValidationError(ugettext("This field requires 8 digits.")) + raise ValidationError(self.error_messages['max_digits']) return value @@ -42,6 +47,11 @@ class PERUCField(RegexField): This field validates a RUC (Registro Unico de Contribuyentes). A RUC is of the form XXXXXXXXXXX. """ + default_error_messages = { + 'invalid': ugettext("This field requires only numbers."), + 'max_digits': ugettext("This field requires 11 digits."), + } + def __init__(self, *args, **kwargs): super(PERUCField, self).__init__(max_length=11, min_length=11, *args, **kwargs) @@ -54,8 +64,8 @@ class PERUCField(RegexField): if value in EMPTY_VALUES: return u'' if not value.isdigit(): - raise ValidationError(ugettext("This field requires only numbers.")) + raise ValidationError(self.error_messages['invalid']) if len(value) != 11: - raise ValidationError(ugettext("This field requires 11 digits.")) + raise ValidationError(self.error_messages['max_digits']) return value diff --git a/django/contrib/localflavor/pl/forms.py b/django/contrib/localflavor/pl/forms.py index 3d1c4234d3..b02dad7025 100644 --- a/django/contrib/localflavor/pl/forms.py +++ b/django/contrib/localflavor/pl/forms.py @@ -35,16 +35,19 @@ class PLNationalIdentificationNumberField(RegexField): The algorithm is documented at http://en.wikipedia.org/wiki/PESEL. """ + default_error_messages = { + 'invalid': _(u'National Identification Number consists of 11 digits.'), + 'checksum': _(u'Wrong checksum for the National Identification Number.'), + } def __init__(self, *args, **kwargs): super(PLNationalIdentificationNumberField, self).__init__(r'^\d{11}$', - max_length=None, min_length=None, error_message=_(u'National Identification Number consists of 11 digits.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) def clean(self,value): super(PLNationalIdentificationNumberField, self).clean(value) if not self.has_valid_checksum(value): - raise ValidationError(_(u'Wrong checksum for the National Identification Number.')) + raise ValidationError(self.error_messages['checksum']) return u'%s' % value def has_valid_checksum(self, number): @@ -65,17 +68,20 @@ class PLTaxNumberField(RegexField): Checksum algorithm based on documentation at http://wipos.p.lodz.pl/zylla/ut/nip-rego.html """ + default_error_messages = { + 'invalid': _(u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX.'), + 'checksum': _(u'Wrong checksum for the Tax Number (NIP).'), + } def __init__(self, *args, **kwargs): super(PLTaxNumberField, self).__init__(r'^\d{3}-\d{3}-\d{2}-\d{2}$|^\d{2}-\d{2}-\d{3}-\d{3}$', - max_length=None, min_length=None, - error_message=_(u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX.'), *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) def clean(self,value): super(PLTaxNumberField, self).clean(value) value = re.sub("[-]", "", value) if not self.has_valid_checksum(value): - raise ValidationError(_(u'Wrong checksum for the Tax Number (NIP).')) + raise ValidationError(self.error_messages['checksum']) return u'%s' % value def has_valid_checksum(self, number): @@ -102,15 +108,19 @@ class PLNationalBusinessRegisterField(RegexField): The checksum algorithm is documented at http://wipos.p.lodz.pl/zylla/ut/nip-rego.html """ + default_error_messages = { + 'invalid': _(u'National Business Register Number (REGON) consists of 7 or 9 digits.'), + 'checksum': _(u'Wrong checksum for the National Business Register Number (REGON).'), + } + def __init__(self, *args, **kwargs): super(PLNationalBusinessRegisterField, self).__init__(r'^\d{7,9}$', - max_length=None, min_length=None, error_message=_(u'National Business Register Number (REGON) consists of 7 or 9 digits.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) def clean(self,value): super(PLNationalBusinessRegisterField, self).clean(value) if not self.has_valid_checksum(value): - raise ValidationError(_(u'Wrong checksum for the National Business Register Number (REGON).')) + raise ValidationError(self.error_messages['checksum']) return u'%s' % value def has_valid_checksum(self, number): @@ -142,9 +152,10 @@ class PLPostalCodeField(RegexField): A form field that validates as Polish postal code. Valid code is XX-XXX where X is digit. """ + default_error_messages = { + 'invalid': _(u'Enter a postal code in the format XX-XXX.'), + } + def __init__(self, *args, **kwargs): super(PLPostalCodeField, self).__init__(r'^\d{2}-\d{3}$', - max_length=None, min_length=None, - error_message=_(u'Enter a postal code in the format XX-XXX.'), - *args, **kwargs) - + max_length=None, min_length=None, *args, **kwargs) diff --git a/django/contrib/localflavor/sk/forms.py b/django/contrib/localflavor/sk/forms.py index 0aa000a0a5..711d70fc2b 100644 --- a/django/contrib/localflavor/sk/forms.py +++ b/django/contrib/localflavor/sk/forms.py @@ -26,11 +26,13 @@ class SKPostalCodeField(RegexField): A form field that validates its input as Slovak postal code. Valid form is XXXXX or XXX XX, where X represents integer. """ + default_error_messages = { + 'invalid': ugettext(u'Enter a postal code in the format XXXXX or XXX XX.'), + } + def __init__(self, *args, **kwargs): super(SKPostalCodeField, self).__init__(r'^\d{5}$|^\d{3} \d{2}$', - max_length=None, min_length=None, - error_message=ugettext(u'Enter a postal code in the format XXXXX or XXX XX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) def clean(self, value): """ diff --git a/django/contrib/localflavor/uk/forms.py b/django/contrib/localflavor/uk/forms.py index 2b162230de..52cd7ad232 100644 --- a/django/contrib/localflavor/uk/forms.py +++ b/django/contrib/localflavor/uk/forms.py @@ -2,21 +2,39 @@ UK-specific Form helpers """ -from django.newforms.fields import RegexField, Select +import re + +from django.newforms.fields import CharField, Select +from django.newforms import ValidationError from django.utils.translation import ugettext -class UKPostcodeField(RegexField): +class UKPostcodeField(CharField): """ A form field that validates its input is a UK postcode. The regular expression used is sourced from the schema for British Standard BS7666 address types: http://www.govtalk.gov.uk/gdsc/schemas/bs7666-v2-0.xsd + + The value is uppercased and a space added in the correct place, if required. """ - def __init__(self, *args, **kwargs): - super(UKPostcodeField, self).__init__(r'^(GIR 0AA|[A-PR-UWYZ]([0-9]{1,2}|([A-HIK-Y][0-9](|[0-9]|[ABEHMNPRVWXY]))|[0-9][A-HJKSTUW]) [0-9][ABD-HJLNP-UW-Z]{2})$', - max_length=None, min_length=None, - error_message=ugettext(u'Enter a postcode. A space is required between the two postcode parts.'), - *args, **kwargs) + default_error_messages = { + 'invalid': ugettext(u'Enter a valid postcode.'), + } + outcode_pattern = '[A-PR-UWYZ]([0-9]{1,2}|([A-HIK-Y][0-9](|[0-9]|[ABEHMNPRVWXY]))|[0-9][A-HJKSTUW])' + incode_pattern = '[0-9][ABD-HJLNP-UW-Z]{2}' + postcode_regex = re.compile(r'^(GIR 0AA|%s %s)$' % (outcode_pattern, incode_pattern)) + space_regex = re.compile(r' *(%s)$' % incode_pattern) + + def clean(self, value): + value = super(UKPostcodeField, self).clean(value) + if value == u'': + return value + postcode = value.upper().strip() + # Put a single space before the incode (second part). + postcode = self.space_regex.sub(r' \1', postcode) + if not self.postcode_regex.search(postcode): + raise ValidationError(self.default_error_messages['invalid']) + return postcode class UKCountySelect(Select): """ diff --git a/django/contrib/localflavor/us/forms.py b/django/contrib/localflavor/us/forms.py index 259a7f7058..d6aa684cba 100644 --- a/django/contrib/localflavor/us/forms.py +++ b/django/contrib/localflavor/us/forms.py @@ -12,13 +12,19 @@ phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$') ssn_re = re.compile(r"^(?P\d{3})[-\ ]?(?P\d{2})[-\ ]?(?P\d{4})$") class USZipCodeField(RegexField): + default_error_messages = { + 'invalid': ugettext('Enter a zip code in the format XXXXX or XXXXX-XXXX.'), + } + def __init__(self, *args, **kwargs): super(USZipCodeField, self).__init__(r'^\d{5}(?:-\d{4})?$', - max_length=None, min_length=None, - error_message=ugettext('Enter a zip code in the format XXXXX or XXXXX-XXXX.'), - *args, **kwargs) + max_length=None, min_length=None, *args, **kwargs) class USPhoneNumberField(Field): + default_error_messages = { + 'invalid': u'Phone numbers must be in XXX-XXX-XXXX format.', + } + def clean(self, value): super(USPhoneNumberField, self).clean(value) if value in EMPTY_VALUES: @@ -27,7 +33,7 @@ class USPhoneNumberField(Field): m = phone_digits_re.search(value) if m: return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) - raise ValidationError(u'Phone numbers must be in XXX-XXX-XXXX format.') + raise ValidationError(self.error_messages['invalid']) class USSocialSecurityNumberField(Field): """ @@ -44,28 +50,31 @@ class USSocialSecurityNumberField(Field): promotional use or distribution (e.g., the Woolworth's number or the 1962 promotional number). """ + default_error_messages = { + 'invalid': ugettext('Enter a valid U.S. Social Security number in XXX-XX-XXXX format.'), + } + def clean(self, value): super(USSocialSecurityNumberField, self).clean(value) if value in EMPTY_VALUES: return u'' - msg = ugettext('Enter a valid U.S. Social Security number in XXX-XX-XXXX format.') match = re.match(ssn_re, value) if not match: - raise ValidationError(msg) + raise ValidationError(self.error_messages['invalid']) area, group, serial = match.groupdict()['area'], match.groupdict()['group'], match.groupdict()['serial'] # First pass: no blocks of all zeroes. if area == '000' or \ group == '00' or \ serial == '0000': - raise ValidationError(msg) + raise ValidationError(self.error_messages['invalid']) # Second pass: promotional and otherwise permanently invalid numbers. if area == '666' or \ (area == '987' and group == '65' and 4320 <= int(serial) <= 4329) or \ value == '078-05-1120' or \ value == '219-09-9999': - raise ValidationError(msg) + raise ValidationError(self.error_messages['invalid']) return u'%s-%s-%s' % (area, group, serial) class USStateField(Field): @@ -74,6 +83,10 @@ class USStateField(Field): It normalizes the input to the standard two-leter postal service abbreviation for the given state. """ + default_error_messages = { + 'invalid': u'Enter a U.S. state or territory.', + } + def clean(self, value): from us_states import STATES_NORMALIZED super(USStateField, self).clean(value) @@ -88,7 +101,7 @@ class USStateField(Field): return STATES_NORMALIZED[value.strip().lower()].decode('ascii') except KeyError: pass - raise ValidationError(u'Enter a U.S. state or territory.') + raise ValidationError(self.error_messages['invalid']) class USStateSelect(Select): """ diff --git a/django/contrib/localflavor/za/forms.py b/django/contrib/localflavor/za/forms.py index b04b7cf6ab..b3613b507d 100644 --- a/django/contrib/localflavor/za/forms.py +++ b/django/contrib/localflavor/za/forms.py @@ -16,10 +16,9 @@ class ZAIDField(Field): 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') + default_error_messages = { + 'invalid': _(u'Enter a valid South African ID number'), + } def clean(self, value): # strip spaces and dashes @@ -31,9 +30,9 @@ class ZAIDField(Field): return u'' match = re.match(id_re, value) - + if not match: - raise ValidationError(self.error_message) + raise ValidationError(self.error_messages['invalid']) g = match.groupdict() @@ -43,15 +42,18 @@ class ZAIDField(Field): # 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) + raise ValidationError(self.error_messages['invalid']) if not luhn(value): - raise ValidationError(self.error_message) + raise ValidationError(self.error_messages['invalid']) return value class ZAPostCodeField(RegexField): + default_error_messages = { + 'invalid': _(u'Enter a valid South African postal code'), + } + 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')) + max_length=None, min_length=None) diff --git a/django/core/mail.py b/django/core/mail.py index 77ab8007e6..153dcb6e63 100644 --- a/django/core/mail.py +++ b/django/core/mail.py @@ -67,42 +67,32 @@ def make_msgid(idstring=None): class BadHeaderError(ValueError): pass +def forbid_multi_line_headers(name, val): + "Forbids multi-line headers, to prevent header injection." + if '\n' in val or '\r' in val: + raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name)) + try: + val = force_unicode(val).encode('ascii') + except UnicodeEncodeError: + if name.lower() in ('to', 'from', 'cc'): + result = [] + for item in val.split(', '): + nm, addr = parseaddr(item) + nm = str(Header(nm, settings.DEFAULT_CHARSET)) + result.append(formataddr((nm, str(addr)))) + val = ', '.join(result) + else: + val = Header(force_unicode(val), settings.DEFAULT_CHARSET) + return name, val + class SafeMIMEText(MIMEText): def __setitem__(self, name, val): - "Forbids multi-line headers, to prevent header injection." - if '\n' in val or '\r' in val: - raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name) - try: - val = force_unicode(val).encode('ascii') - except UnicodeEncodeError: - if name.lower() in ('to', 'from', 'cc'): - result = [] - for item in val.split(', '): - nm, addr = parseaddr(item) - nm = str(Header(nm, settings.DEFAULT_CHARSET)) - result.append(formataddr((nm, str(addr)))) - val = ', '.join(result) - else: - val = Header(force_unicode(val), settings.DEFAULT_CHARSET) + name, val = forbid_multi_line_headers(name, val) MIMEText.__setitem__(self, name, val) class SafeMIMEMultipart(MIMEMultipart): def __setitem__(self, name, val): - "Forbids multi-line headers, to prevent header injection." - if '\n' in val or '\r' in val: - raise BadHeaderError, "Header values can't contain newlines (got %r for header %r)" % (val, name) - try: - val = force_unicode(val).encode('ascii') - except UnicodeEncodeError: - if name.lower() in ('to', 'from', 'cc'): - result = [] - for item in val.split(', '): - nm, addr = parseaddr(item) - nm = str(Header(nm, settings.DEFAULT_CHARSET)) - result.append(formataddr((nm, str(addr)))) - val = ', '.join(result) - else: - val = Header(force_unicode(val), settings.DEFAULT_CHARSET) + name, val = forbid_multi_line_headers(name, val) MIMEMultipart.__setitem__(self, name, val) class SMTPConnection(object): @@ -209,8 +199,14 @@ class EmailMessage(object): bytestrings). The SafeMIMEText class will handle any necessary encoding conversions. """ - self.to = to or [] - self.bcc = bcc or [] + if to: + self.to = list(to) + else: + self.to = [] + if bcc: + self.bcc = list(bcc) + else: + self.bcc = [] self.from_email = from_email or settings.DEFAULT_FROM_EMAIL self.subject = subject self.body = body diff --git a/django/core/management/base.py b/django/core/management/base.py index 31c2849075..7b8a3e987f 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -27,6 +27,8 @@ class BaseCommand(object): help='The Python path to a settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.'), make_option('--pythonpath', help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'), + make_option('--traceback', action='store_true', + help='Print traceback on exception'), ) help = '' args = '' diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index 7bdcc4271f..2642ae925e 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -1,11 +1,12 @@ from django.core.management.base import BaseCommand, CommandError +from django.core import serializers from optparse import make_option class Command(BaseCommand): option_list = BaseCommand.option_list + ( make_option('--format', default='json', dest='format', - help='Specifies the output serialization format for fixtures'), + help='Specifies the output serialization format for fixtures.'), make_option('--indent', default=None, dest='indent', type='int', help='Specifies the indent level to use when pretty-printing output'), ) @@ -14,10 +15,10 @@ class Command(BaseCommand): def handle(self, *app_labels, **options): from django.db.models import get_app, get_apps, get_models - from django.core import serializers format = options.get('format', 'json') indent = options.get('indent', None) + show_traceback = options.get('traceback', False) if len(app_labels) == 0: app_list = get_apps() @@ -26,6 +27,9 @@ class Command(BaseCommand): # Check that the serialization format exists; this is a shortcut to # avoid collating all the objects and _then_ failing. + if format not in serializers.get_public_serializer_formats(): + raise CommandError("Unknown serialization format: %s" % format) + try: serializers.get_serializer(format) except KeyError: @@ -34,8 +38,10 @@ class Command(BaseCommand): objects = [] for app in app_list: for model in get_models(app): - objects.extend(model.objects.all()) + objects.extend(model._default_manager.all()) try: return serializers.serialize(format, objects, indent=indent) except Exception, e: + if show_traceback: + raise raise CommandError("Unable to serialize database: %s" % e) diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 216db09141..fb0325906d 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -27,6 +27,7 @@ class Command(BaseCommand): self.style = no_style() verbosity = int(options.get('verbosity', 1)) + show_traceback = options.get('traceback', False) # Keep a count of the installed objects and fixtures count = [0, 0] @@ -50,10 +51,10 @@ class Command(BaseCommand): parts = fixture_label.split('.') if len(parts) == 1: fixture_name = fixture_label - formats = serializers.get_serializer_formats() + formats = serializers.get_public_serializer_formats() else: fixture_name, format = '.'.join(parts[:-1]), parts[-1] - if format in serializers.get_serializer_formats(): + if format in serializers.get_public_serializer_formats(): formats = [format] else: formats = [] @@ -98,11 +99,13 @@ class Command(BaseCommand): label_found = True except Exception, e: fixture.close() + transaction.rollback() + transaction.leave_transaction_management() + if show_traceback: + raise sys.stderr.write( self.style.ERROR("Problem installing fixture '%s': %s\n" % (full_path, str(e)))) - transaction.rollback() - transaction.leave_transaction_management() return fixture.close() except: diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 49f4a80732..46b8d4a985 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -116,6 +116,7 @@ def sql_delete(app, style): "Returns a list of the DROP TABLE SQL statements for the given app." from django.db import connection, models, get_introspection_module from django.db.backends.util import truncate_name + from django.contrib.contenttypes import generic introspection = get_introspection_module() # This should work even if a connection isn't available @@ -179,6 +180,8 @@ def sql_delete(app, style): for model in app_models: opts = model._meta for f in opts.many_to_many: + if isinstance(f.rel, generic.GenericRel): + continue if cursor and table_name_converter(f.m2m_db_table()) in table_names: output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), style.SQL_TABLE(qn(f.m2m_db_table())))) diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py index 1e24e2bd22..47dfb0c87a 100644 --- a/django/core/serializers/__init__.py +++ b/django/core/serializers/__init__.py @@ -53,6 +53,11 @@ def get_serializer_formats(): _load_serializers() return _serializers.keys() +def get_public_serializer_formats(): + if not _serializers: + _load_serializers() + return [k for k, v in _serializers.iteritems() if not v.Serializer.internal_use_only] + def get_deserializer(format): if not _serializers: _load_serializers() diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index ee9e4dd621..3c7dcfa02e 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -22,6 +22,10 @@ class Serializer(object): Abstract serializer base class. """ + # Indicates if the implemented serializer is only available for + # internal Django use. + internal_use_only = False + def serialize(self, queryset, **options): """ Serialize a queryset. diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index fa2dca7295..e17b821f52 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -20,6 +20,8 @@ class Serializer(PythonSerializer): """ Convert a queryset to JSON. """ + internal_use_only = False + def end_serialization(self): self.options.pop('stream', None) self.options.pop('fields', None) diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 6fc13d76b5..cedab06be9 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -13,7 +13,9 @@ class Serializer(base.Serializer): """ Serializes a QuerySet to basic Python objects. """ - + + internal_use_only = True + def start_serialization(self): self._current = None self.objects = [] diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py index 772f34a2e3..4c32a9686f 100644 --- a/django/core/serializers/pyyaml.py +++ b/django/core/serializers/pyyaml.py @@ -19,6 +19,8 @@ class Serializer(PythonSerializer): Convert a queryset to YAML. """ + internal_use_only = False + 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 diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index e98957da2c..05f8756655 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -398,8 +398,20 @@ class ServerHandler(object): self.bytes_sent += len(data) # XXX check Content-Length and truncate if too many bytes written? - self._write(data) - self._flush() + + # If data is too large, socket will choke, so write chunks no larger + # than 32MB at a time. + length = len(data) + if length > 33554432: + offset = 0 + while offset < length: + chunk_size = min(33554432, length) + self._write(data[offset:offset+chunk_size]) + self._flush() + offset += chunk_size + else: + self._write(data) + self._flush() def sendfile(self): """Platform-specific file transmission diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index cd830413fc..5dd62432c5 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -413,6 +413,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): return self.connection is not None def _cursor(self, settings): + cursor = None if not self._valid_connection(): if len(settings.DATABASE_HOST.strip()) == 0: settings.DATABASE_HOST = 'localhost' @@ -422,16 +423,25 @@ class DatabaseWrapper(BaseDatabaseWrapper): else: conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME) self.connection = Database.connect(conn_string, **self.options) + cursor = FormatStylePlaceholderCursor(self.connection) + # Set oracle date to ansi date format. This only needs to execute + # once when we create a new connection. + cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD' " + "NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'") try: self.oracle_version = int(self.connection.version.split('.')[0]) except ValueError: pass - cursor = FormatStylePlaceholderCursor(self.connection) + try: + self.connection.stmtcachesize = 20 + except: + # Django docs specify cx_Oracle version 4.3.1 or higher, but + # stmtcachesize is available only in 4.3.2 and up. + pass + if not cursor: + cursor = FormatStylePlaceholderCursor(self.connection) # Default arraysize of 1 is highly sub-optimal. cursor.arraysize = 100 - # Set oracle date to ansi date format. - cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'") - cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'") return cursor class FormatStylePlaceholderCursor(Database.Cursor): diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 61ccc2d85d..e72f5015b7 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -714,7 +714,7 @@ class EmailField(CharField): class FileField(Field): def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): self.upload_to = upload_to - kwargs['max_length'] = kwargs.get('max_length', 100) + kwargs['max_length'] = kwargs.get('max_length', 100) Field.__init__(self, verbose_name, name, **kwargs) def get_db_prep_save(self, value): @@ -910,6 +910,11 @@ class NullBooleanField(Field): def get_manipulator_field_objs(self): return [oldforms.NullBooleanField] + def formfield(self, **kwargs): + defaults = {'form_class': forms.NullBooleanField} + defaults.update(kwargs) + return super(NullBooleanField, self).formfield(**defaults) + class PhoneNumberField(IntegerField): def get_manipulator_field_objs(self): return [oldforms.PhoneNumberField] @@ -926,11 +931,11 @@ class PhoneNumberField(IntegerField): class PositiveIntegerField(IntegerField): def get_manipulator_field_objs(self): return [oldforms.PositiveIntegerField] - + def formfield(self, **kwargs): defaults = {'min_value': 0} defaults.update(kwargs) - return super(PositiveIntegerField, self).formfield(**defaults) + return super(PositiveIntegerField, self).formfield(**defaults) class PositiveSmallIntegerField(IntegerField): def get_manipulator_field_objs(self): @@ -939,7 +944,7 @@ class PositiveSmallIntegerField(IntegerField): def formfield(self, **kwargs): defaults = {'min_value': 0} defaults.update(kwargs) - return super(PositiveSmallIntegerField, self).formfield(**defaults) + return super(PositiveSmallIntegerField, self).formfield(**defaults) class SlugField(CharField): def __init__(self, *args, **kwargs): diff --git a/django/http/__init__.py b/django/http/__init__.py index 13cc8cea0d..69e9d51204 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -277,7 +277,20 @@ class HttpResponse(object): for key, value in self._headers.values()]) \ + '\n\n' + self.content + def _convert_to_ascii(self, *values): + "Convert all values to ascii strings" + for value in values: + if isinstance(value, unicode): + try: + yield value.encode('us-ascii') + except UnicodeError, e: + e.reason += ', HTTP response headers must be in US-ASCII format' + raise + else: + yield str(value) + def __setitem__(self, header, value): + header, value = self._convert_to_ascii(header, value) self._headers[header.lower()] = (header, value) def __delitem__(self, header): diff --git a/django/newforms/models.py b/django/newforms/models.py index 17fd1cf2e2..fd0087a3b1 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -84,9 +84,8 @@ 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) + 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: @@ -114,9 +113,8 @@ 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) + warn("form_for_instance is deprecated. Use ModelForm instead.", + PendingDeprecationWarning, stacklevel=3) model = instance.__class__ opts = model._meta field_list = [] @@ -149,10 +147,10 @@ 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. @@ -187,7 +185,7 @@ def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda ``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. @@ -214,9 +212,8 @@ class ModelFormOptions(object): 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? + def __new__(cls, name, bases, attrs, + formfield_callback=lambda f: f.formfield()): 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)) @@ -253,7 +250,8 @@ class ModelFormMetaclass(type): 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) + model_fields = fields_for_model(opts.model, opts.fields, + opts.exclude, formfield_callback) # fields declared in base classes override fields from the model model_fields.update(declared_fields) attrs['base_fields'] = model_fields diff --git a/django/template/__init__.py b/django/template/__init__.py index 9b1868cfce..750c48988d 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -154,15 +154,12 @@ class StringOrigin(Origin): class Template(object): def __init__(self, template_string, origin=None, name=''): - "Compilation stage" try: template_string = smart_unicode(template_string) except UnicodeDecodeError: raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.") - if settings.TEMPLATE_DEBUG and origin == None: + if settings.TEMPLATE_DEBUG and origin is None: origin = StringOrigin(template_string) - # Could do some crazy stack-frame stuff to record where this string - # came from... self.nodelist = compile_string(template_string, origin) self.name = name @@ -177,13 +174,18 @@ class Template(object): def compile_string(template_string, origin): "Compiles template_string into NodeList ready for rendering" - lexer = lexer_factory(template_string, origin) - parser = parser_factory(lexer.tokenize()) + if settings.TEMPLATE_DEBUG: + from debug import DebugLexer, DebugParser + lexer_class, parser_class = DebugLexer, DebugParser + else: + lexer_class, parser_class = Lexer, Parser + lexer = lexer_class(template_string, origin) + parser = parser_class(lexer.tokenize()) return parser.parse() class Token(object): def __init__(self, token_type, contents): - "The token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT" + # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT. self.token_type, self.contents = token_type, contents def __str__(self): @@ -200,7 +202,7 @@ class Lexer(object): self.origin = origin def tokenize(self): - "Return a list of tokens from a given template_string" + "Return a list of tokens from a given template_string." in_tag = False result = [] for bit in tag_re.split(self.template_string): @@ -226,30 +228,6 @@ class Lexer(object): token = Token(TOKEN_TEXT, token_string) return token -class DebugLexer(Lexer): - def __init__(self, template_string, origin): - super(DebugLexer, self).__init__(template_string, origin) - - def tokenize(self): - "Return a list of tokens from a given template_string" - result, upto = [], 0 - for match in tag_re.finditer(self.template_string): - start, end = match.span() - if start > upto: - result.append(self.create_token(self.template_string[upto:start], (upto, start), False)) - upto = start - result.append(self.create_token(self.template_string[start:end], (start, end), True)) - upto = end - last_bit = self.template_string[upto:] - if last_bit: - result.append(self.create_token(last_bit, (upto, upto + len(last_bit)), False)) - return result - - def create_token(self, token_string, source, in_tag): - token = super(DebugLexer, self).create_token(token_string, in_tag) - token.source = self.origin, source - return token - class Parser(object): def __init__(self, tokens): self.tokens = tokens @@ -319,17 +297,17 @@ class Parser(object): def exit_command(self): pass - def error(self, token, msg ): + def error(self, token, msg): return TemplateSyntaxError(msg) def empty_variable(self, token): - raise self.error( token, "Empty variable tag") + raise self.error(token, "Empty variable tag") def empty_block_tag(self, token): - raise self.error( token, "Empty block tag") + raise self.error(token, "Empty block tag") def invalid_block_tag(self, token, command): - raise self.error( token, "Invalid block tag: '%s'" % command) + raise self.error(token, "Invalid block tag: '%s'" % command) def unclosed_block_tag(self, parse_until): raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until)) @@ -358,57 +336,7 @@ class Parser(object): if filter_name in self.filters: return self.filters[filter_name] else: - raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name - -class DebugParser(Parser): - def __init__(self, lexer): - super(DebugParser, self).__init__(lexer) - self.command_stack = [] - - def enter_command(self, command, token): - self.command_stack.append( (command, token.source) ) - - def exit_command(self): - self.command_stack.pop() - - def error(self, token, msg): - return self.source_error(token.source, msg) - - def source_error(self, source,msg): - e = TemplateSyntaxError(msg) - e.source = source - return e - - def create_nodelist(self): - return DebugNodeList() - - def create_variable_node(self, contents): - return DebugVariableNode(contents) - - def extend_nodelist(self, nodelist, node, token): - node.source = token.source - super(DebugParser, self).extend_nodelist(nodelist, node, token) - - def unclosed_block_tag(self, parse_until): - command, source = self.command_stack.pop() - msg = "Unclosed tag '%s'. Looking for one of: %s " % (command, ', '.join(parse_until)) - raise self.source_error( source, msg) - - def compile_function_error(self, token, e): - if not hasattr(e, 'source'): - e.source = token.source - -def lexer_factory(*args, **kwargs): - if settings.TEMPLATE_DEBUG: - return DebugLexer(*args, **kwargs) - else: - return Lexer(*args, **kwargs) - -def parser_factory(*args, **kwargs): - if settings.TEMPLATE_DEBUG: - return DebugParser(*args, **kwargs) - else: - return Parser(*args, **kwargs) + raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name) class TokenParser(object): """ @@ -426,7 +354,7 @@ class TokenParser(object): def top(self): "Overload this method to do the actual parsing and return the result." - raise NotImplemented + raise NotImplementedError() def more(self): "Returns True if there is more stuff in the tag." @@ -435,7 +363,7 @@ class TokenParser(object): def back(self): "Undoes the last microparser. Use this for lookahead and backtracking." if not len(self.backout): - raise TemplateSyntaxError, "back called without some previous parsing" + raise TemplateSyntaxError("back called without some previous parsing") self.pointer = self.backout.pop() def tag(self): @@ -443,7 +371,7 @@ class TokenParser(object): subject = self.subject i = self.pointer if i >= len(subject): - raise TemplateSyntaxError, "expected another tag, found end of string: %s" % subject + raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject) p = i while i < len(subject) and subject[i] not in (' ', '\t'): i += 1 @@ -459,14 +387,14 @@ class TokenParser(object): subject = self.subject i = self.pointer if i >= len(subject): - raise TemplateSyntaxError, "Searching for value. Expected another value but found end of string: %s" % subject + raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject) if subject[i] in ('"', "'"): p = i i += 1 while i < len(subject) and subject[i] != subject[p]: i += 1 if i >= len(subject): - raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % (i, subject) + raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) i += 1 res = subject[p:i] while i < len(subject) and subject[i] in (' ', '\t'): @@ -483,7 +411,7 @@ class TokenParser(object): while i < len(subject) and subject[i] != c: i += 1 if i >= len(subject): - raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % (i, subject) + raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) i += 1 s = subject[p:i] while i < len(subject) and subject[i] in (' ', '\t'): @@ -542,8 +470,8 @@ class FilterExpression(object): for match in matches: start = match.start() if upto != start: - raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s" % \ - (token[:upto], token[upto:start], token[start:]) + raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s" % \ + (token[:upto], token[upto:start], token[start:])) if var == None: var, constant, i18n_constant = match.group("var", "constant", "i18n_constant") if i18n_constant: @@ -552,9 +480,9 @@ class FilterExpression(object): var = '"%s"' % constant.replace(r'\"', '"') upto = match.end() if var == None: - raise TemplateSyntaxError, "Could not find variable at start of %s" % token + raise TemplateSyntaxError("Could not find variable at start of %s" % token) elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_': - raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var + raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var) else: filter_name = match.group("filter_name") args = [] @@ -570,7 +498,7 @@ class FilterExpression(object): filters.append( (filter_func,args)) upto = match.end() if upto != len(token): - raise TemplateSyntaxError, "Could not parse the remainder: '%s' from '%s'" % (token[upto:], token) + raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token)) self.filters = filters self.var = Variable(var) @@ -627,7 +555,7 @@ class FilterExpression(object): provided.pop(0) except IndexError: # Not enough - raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen) + raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) # Defaults can be overridden. defaults = defaults and list(defaults) or [] @@ -636,7 +564,7 @@ class FilterExpression(object): defaults.pop(0) except IndexError: # Too many. - raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen) + raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) return True args_check = staticmethod(args_check) @@ -816,22 +744,6 @@ class NodeList(list): def render_node(self, node, context): return node.render(context) -class DebugNodeList(NodeList): - def render_node(self, node, context): - try: - result = node.render(context) - except TemplateSyntaxError, e: - if not hasattr(e, 'source'): - e.source = node.source - raise - except Exception, e: - from sys import exc_info - wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e) - wrapped.source = node.source - wrapped.exc_info = exc_info() - raise wrapped - return result - class TextNode(Node): def __init__(self, s): self.s = s @@ -861,21 +773,6 @@ class VariableNode(Node): else: return force_unicode(output) -class DebugVariableNode(VariableNode): - def render(self, context): - try: - output = force_unicode(self.filter_expression.resolve(context)) - except TemplateSyntaxError, e: - if not hasattr(e, 'source'): - e.source = self.source - raise - except UnicodeDecodeError: - return '' - if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData): - return escape(output) - else: - return output - def generic_tag_compiler(params, defaults, name, node_class, parser, token): "Returns a template.Node subclass." bits = token.split_contents()[1:] @@ -887,7 +784,7 @@ def generic_tag_compiler(params, defaults, name, node_class, parser, token): message = "%s takes %s arguments" % (name, bmin) else: message = "%s takes between %s and %s arguments" % (name, bmin, bmax) - raise TemplateSyntaxError, message + raise TemplateSyntaxError(message) return node_class(bits) class Library(object): @@ -913,7 +810,7 @@ class Library(object): self.tags[name] = compile_function return compile_function else: - raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function) + raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)) def tag_function(self,func): self.tags[getattr(func, "_decorated_function", func).__name__] = func @@ -937,7 +834,7 @@ class Library(object): self.filters[name] = filter_func return filter_func else: - raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func) + raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)) def filter_function(self, func): self.filters[getattr(func, "_decorated_function", func).__name__] = func @@ -966,7 +863,7 @@ class Library(object): if params[0] == 'context': params = params[1:] else: - raise TemplateSyntaxError, "Any tag function decorated with takes_context=True must have a first argument of 'context'" + raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'") class InclusionNode(Node): def __init__(self, vars_to_resolve): @@ -1003,12 +900,12 @@ def get_library(module_name): try: mod = __import__(module_name, {}, {}, ['']) except ImportError, e: - raise InvalidTemplateLibrary, "Could not load template library from %s, %s" % (module_name, e) + raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e)) try: lib = mod.register libraries[module_name] = lib except AttributeError: - raise InvalidTemplateLibrary, "Template library %s does not have a variable named 'register'" % module_name + raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name) return lib def add_to_builtins(module_name): diff --git a/django/template/context.py b/django/template/context.py index 0e41a26618..6ba53f340b 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -9,7 +9,6 @@ class ContextPopException(Exception): class Context(object): "A stack container for variable context" - def __init__(self, dict_=None, autoescape=True): dict_ = dict_ or {} self.dicts = [dict_] @@ -78,11 +77,11 @@ def get_standard_processors(): try: mod = __import__(module, {}, {}, [attr]) except ImportError, e: - raise ImproperlyConfigured, 'Error importing request processor module %s: "%s"' % (module, e) + raise ImproperlyConfigured('Error importing request processor module %s: "%s"' % (module, e)) try: func = getattr(mod, attr) except AttributeError: - raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable request processor' % (module, attr) + raise ImproperlyConfigured('Module "%s" does not define a "%s" callable request processor' % (module, attr)) processors.append(func) _standard_context_processors = tuple(processors) return _standard_context_processors @@ -102,4 +101,3 @@ class RequestContext(Context): processors = tuple(processors) for processor in get_standard_processors() + processors: self.update(processor(request)) - diff --git a/django/template/debug.py b/django/template/debug.py new file mode 100644 index 0000000000..008059c28f --- /dev/null +++ b/django/template/debug.py @@ -0,0 +1,97 @@ +from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError +from django.utils.encoding import force_unicode +from django.utils.html import escape +from django.utils.safestring import SafeData, EscapeData + +class DebugLexer(Lexer): + def __init__(self, template_string, origin): + super(DebugLexer, self).__init__(template_string, origin) + + def tokenize(self): + "Return a list of tokens from a given template_string" + result, upto = [], 0 + for match in tag_re.finditer(self.template_string): + start, end = match.span() + if start > upto: + result.append(self.create_token(self.template_string[upto:start], (upto, start), False)) + upto = start + result.append(self.create_token(self.template_string[start:end], (start, end), True)) + upto = end + last_bit = self.template_string[upto:] + if last_bit: + result.append(self.create_token(last_bit, (upto, upto + len(last_bit)), False)) + return result + + def create_token(self, token_string, source, in_tag): + token = super(DebugLexer, self).create_token(token_string, in_tag) + token.source = self.origin, source + return token + +class DebugParser(Parser): + def __init__(self, lexer): + super(DebugParser, self).__init__(lexer) + self.command_stack = [] + + def enter_command(self, command, token): + self.command_stack.append( (command, token.source) ) + + def exit_command(self): + self.command_stack.pop() + + def error(self, token, msg): + return self.source_error(token.source, msg) + + def source_error(self, source,msg): + e = TemplateSyntaxError(msg) + e.source = source + return e + + def create_nodelist(self): + return DebugNodeList() + + def create_variable_node(self, contents): + return DebugVariableNode(contents) + + def extend_nodelist(self, nodelist, node, token): + node.source = token.source + super(DebugParser, self).extend_nodelist(nodelist, node, token) + + def unclosed_block_tag(self, parse_until): + command, source = self.command_stack.pop() + msg = "Unclosed tag '%s'. Looking for one of: %s " % (command, ', '.join(parse_until)) + raise self.source_error(source, msg) + + def compile_function_error(self, token, e): + if not hasattr(e, 'source'): + e.source = token.source + +class DebugNodeList(NodeList): + def render_node(self, node, context): + try: + result = node.render(context) + except TemplateSyntaxError, e: + if not hasattr(e, 'source'): + e.source = node.source + raise + except Exception, e: + from sys import exc_info + wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e) + wrapped.source = node.source + wrapped.exc_info = exc_info() + raise wrapped + return result + +class DebugVariableNode(VariableNode): + def render(self, context): + try: + output = force_unicode(self.filter_expression.resolve(context)) + except TemplateSyntaxError, e: + if not hasattr(e, 'source'): + e.source = self.source + raise + except UnicodeDecodeError: + return '' + if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData): + return escape(output) + else: + return output diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 2f211dadb5..5cee0ab244 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -254,7 +254,7 @@ urlize.is_safe=True urlize.needs_autoescape = True urlize = stringfilter(urlize) -def urlizetrunc(value, limit): +def urlizetrunc(value, limit, autoescape=None): """ Converts URLs into clickable links, truncating URLs to the given character limit, and adding 'rel=nofollow' attribute to discourage spamming. @@ -262,8 +262,10 @@ def urlizetrunc(value, limit): Argument: Length to truncate URLs to. """ from django.utils.html import urlize - return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True)) + return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True, + autoescape=autoescape)) urlizetrunc.is_safe = True +urlizetrunc.needs_autoescape = True urlizetrunc = stringfilter(urlizetrunc) def wordcount(value): diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index d91e30bbb5..726a37012b 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -84,19 +84,16 @@ class FirstOfNode(Node): return u'' class ForNode(Node): - def __init__(self, loopvars, sequence, reversed, nodelist_loop): + def __init__(self, loopvars, sequence, is_reversed, nodelist_loop): self.loopvars, self.sequence = loopvars, sequence - self.reversed = reversed + self.is_reversed = is_reversed self.nodelist_loop = nodelist_loop def __repr__(self): - if self.reversed: - reversed = ' reversed' - else: - reversed = '' + reversed_text = self.is_reversed and ' reversed' or '' return "" % \ (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop), - reversed) + reversed_text) def __iter__(self): for node in self.nodelist_loop: @@ -125,22 +122,23 @@ class ForNode(Node): if not hasattr(values, '__len__'): values = list(values) len_values = len(values) - if self.reversed: + if self.is_reversed: values = reversed(values) unpack = len(self.loopvars) > 1 + # Create a forloop value in the context. We'll update counters on each + # iteration just below. + loop_dict = context['forloop'] = {'parentloop': parentloop} for i, item in enumerate(values): - context['forloop'] = { - # Shortcuts for current loop iteration number. - 'counter0': i, - 'counter': i+1, - # Reverse counter iteration numbers. - 'revcounter': len_values - i, - 'revcounter0': len_values - i - 1, - # Boolean values designating first and last times through loop. - 'first': (i == 0), - 'last': (i == len_values - 1), - 'parentloop': parentloop, - } + # Shortcuts for current loop iteration number. + loop_dict['counter0'] = i + loop_dict['counter'] = i+1 + # Reverse counter iteration numbers. + loop_dict['revcounter'] = len_values - i + loop_dict['revcounter0'] = len_values - i - 1 + # Boolean values designating first and last times through loop. + loop_dict['first'] = (i == 0) + loop_dict['last'] = (i == len_values - 1) + if unpack: # If there are multiple loop variables, unpack the item into # them. @@ -619,8 +617,8 @@ def do_for(parser, token): raise TemplateSyntaxError("'for' statements should have at least four" " words: %s" % token.contents) - reversed = bits[-1] == 'reversed' - in_index = reversed and -3 or -2 + is_reversed = bits[-1] == 'reversed' + in_index = is_reversed and -3 or -2 if bits[in_index] != 'in': raise TemplateSyntaxError("'for' statements should use the format" " 'for x in y': %s" % token.contents) @@ -634,7 +632,7 @@ def do_for(parser, token): sequence = parser.compile_filter(bits[in_index+1]) nodelist_loop = parser.parse(('endfor',)) parser.delete_first_token() - return ForNode(loopvars, sequence, reversed, nodelist_loop) + return ForNode(loopvars, sequence, is_reversed, nodelist_loop) do_for = register.tag("for", do_for) def do_ifequal(parser, token, negate): @@ -814,7 +812,7 @@ def ssi(parser, token): Outputs the contents of a given file into the page. Like a simple "include" tag, the ``ssi`` tag includes the contents - of another file -- which must be specified using an absolute page -- + of another file -- which must be specified using an absolute path -- in the current page:: {% ssi /home/html/ljworld.com/includes/right_generic.html %} @@ -926,20 +924,18 @@ def regroup(parser, token): {% regroup people|dictsort:"gender" by gender as grouped %} """ - firstbits = token.contents.split(None, 3) - if len(firstbits) != 4: + bits = token.contents.split() + if len(bits) != 6: raise TemplateSyntaxError, "'regroup' tag takes five arguments" - target = parser.compile_filter(firstbits[1]) - if firstbits[2] != 'by': + target = parser.compile_filter(bits[1]) + if bits[2] != 'by': raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") - lastbits_reversed = firstbits[3][::-1].split(None, 2) - if lastbits_reversed[1][::-1] != 'as': + if bits[4] != 'as': raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" " be 'as'") - expression = parser.compile_filter(lastbits_reversed[2][::-1]) - - var_name = lastbits_reversed[0][::-1] + expression = parser.compile_filter(bits[3]) + var_name = bits[5] return RegroupNode(target, expression, var_name) regroup = register.tag(regroup) diff --git a/django/test/utils.py b/django/test/utils.py index 51ca37e2f0..b1c528873c 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -1,4 +1,4 @@ -import sys, time +import sys, time, os from django.conf import settings from django.db import connection, get_creation_module from django.core import mail @@ -106,9 +106,32 @@ def create_test_db(verbosity=1, autoclobber=False): if verbosity >= 1: print "Creating test database..." # If we're using SQLite, it's more convenient to test against an - # in-memory database. + # in-memory database. Using the TEST_DATABASE_NAME setting you can still choose + # to run on a physical database. if settings.DATABASE_ENGINE == "sqlite3": - TEST_DATABASE_NAME = ":memory:" + if settings.TEST_DATABASE_NAME and settings.TEST_DATABASE_NAME != ":memory:": + TEST_DATABASE_NAME = settings.TEST_DATABASE_NAME + # Erase the old test database + if verbosity >= 1: + print "Destroying old test database..." + if os.access(TEST_DATABASE_NAME, os.F_OK): + if not autoclobber: + confirm = raw_input("Type 'yes' if you would like to try deleting the test database '%s', or 'no' to cancel: " % TEST_DATABASE_NAME) + if autoclobber or confirm == 'yes': + try: + if verbosity >= 1: + print "Destroying old test database..." + os.remove(TEST_DATABASE_NAME) + except Exception, e: + sys.stderr.write("Got an error deleting the old test database: %s\n" % e) + sys.exit(2) + else: + print "Tests cancelled." + sys.exit(1) + if verbosity >= 1: + print "Creating test database..." + else: + TEST_DATABASE_NAME = ":memory:" else: suffix = { 'postgresql': get_postgresql_create_suffix, @@ -171,17 +194,20 @@ def destroy_test_db(old_database_name, verbosity=1): creation_module.destroy_test_db(settings, connection, old_database_name, verbosity) return - # Unless we're using SQLite, remove the test database to clean up after - # ourselves. Connect to the previous database (not the test database) - # to do so, because it's not allowed to delete a database while being - # connected to it. if verbosity >= 1: print "Destroying test database..." connection.close() TEST_DATABASE_NAME = settings.DATABASE_NAME settings.DATABASE_NAME = old_database_name - - if settings.DATABASE_ENGINE != "sqlite3": + if settings.DATABASE_ENGINE == "sqlite3": + if TEST_DATABASE_NAME and TEST_DATABASE_NAME != ":memory:": + # Remove the SQLite database file + os.remove(TEST_DATABASE_NAME) + else: + # Remove the test database to clean up after + # ourselves. Connect to the previous database (not the test database) + # to do so, because it's not allowed to delete a database while being + # connected to it. cursor = connection.cursor() _set_autocommit(connection) time.sleep(1) # To avoid "database is being accessed by other users" errors. diff --git a/django/views/debug.py b/django/views/debug.py index 18a396d3a6..e3be2a7dcd 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -507,7 +507,7 @@ Exception Type: {{ exception_type|escape }} at {{ request.path|escape }} Exception Value: {{ exception_value|escape }}

- + diff --git a/django/views/static.py b/django/views/static.py index 8095fc9231..5a4d3ab55c 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -59,10 +59,11 @@ def serve(request, path, document_root=None, show_indexes=False): if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): return HttpResponseNotModified() - mimetype = mimetypes.guess_type(fullpath)[0] + mimetype = mimetypes.guess_type(fullpath)[0] or 'application/octet-stream' contents = open(fullpath, 'rb').read() response = HttpResponse(contents, mimetype=mimetype) response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) + response["Content-Length"] = len(contents) return response DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ diff --git a/docs/add_ons.txt b/docs/add_ons.txt index c6fdb4a3a2..029e314f12 100644 --- a/docs/add_ons.txt +++ b/docs/add_ons.txt @@ -47,7 +47,11 @@ contenttypes ============ A light framework for hooking into "types" of content, where each installed -Django model is a separate content type. This is not yet documented. +Django model is a separate content type. + +See the `contenttypes documentation`_. + +.. _contenttypes documentation: ../contenttypes/ csrf ==== @@ -177,9 +181,13 @@ localflavor =========== A collection of various Django snippets that are useful only for a particular -country or culture. For example, ``django.contrib.localflavor.usa.forms`` +country or culture. For example, ``django.contrib.localflavor.us.forms`` contains a ``USZipCodeField`` that you can use to validate U.S. zip codes. +See the `localflavor documentation`_. + +.. _localflavor documentation: ../localflavor/ + markup ====== diff --git a/docs/authentication.txt b/docs/authentication.txt index 8f305ba4d1..5134e90267 100644 --- a/docs/authentication.txt +++ b/docs/authentication.txt @@ -247,8 +247,8 @@ Anonymous users the ``django.contrib.auth.models.User`` interface, with these differences: * ``id`` is always ``None``. - * ``is_staff`` and ``is_superuser`` are always False. - * ``is_active`` is always True. + * ``is_staff`` and ``is_superuser`` are always ``False``. + * ``is_active`` is always ``False``. * ``groups`` and ``user_permissions`` are always empty. * ``is_anonymous()`` returns ``True`` instead of ``False``. * ``is_authenticated()`` returns ``False`` instead of ``True``. diff --git a/docs/custom_model_fields.txt b/docs/custom_model_fields.txt index 80c9de7d16..923b331a95 100644 --- a/docs/custom_model_fields.txt +++ b/docs/custom_model_fields.txt @@ -44,8 +44,8 @@ Our class looks something like this:: # ... (other possibly useful methods omitted) ... This is just an ordinary Python class, with nothing Django-specific about it. -We'd like to be able to things like this in our models (we assume the ``hand`` -attribute on the model is an instance of ``Hand``):: +We'd like to be able to do things like this in our models (we assume the +``hand`` attribute on the model is an instance of ``Hand``):: example = MyModel.objects.get(pk=1) print example.hand.north diff --git a/docs/django-admin.txt b/docs/django-admin.txt index 2977f9908f..21821ab2e9 100644 --- a/docs/django-admin.txt +++ b/docs/django-admin.txt @@ -132,6 +132,13 @@ If no application name is provided, all installed applications will be dumped. The output of ``dumpdata`` can be used as input for ``loaddata``. +Note that ``dumpdata`` uses the default manager on the model for selecting the +records to dump. If you're using a `custom manager`_ as the default manager +and it filters some of the available records, not all of the objects will be +dumped. + +.. _custom manager: ../model-api/#custom-managers + --format ~~~~~~~~ @@ -709,8 +716,8 @@ in Python package syntax, e.g. ``mysite.settings``. If this isn't provided, ``django-admin.py`` will use the ``DJANGO_SETTINGS_MODULE`` environment variable. -Note that this option is unnecessary in ``manage.py``, because it takes care of -setting ``DJANGO_SETTINGS_MODULE`` for you. +Note that this option is unnecessary in ``manage.py``, because it uses +``settings.py`` from the current project by default. Extra niceties ============== diff --git a/docs/form_for_model.txt b/docs/form_for_model.txt index 6761c15331..ddca9aae18 100644 --- a/docs/form_for_model.txt +++ b/docs/form_for_model.txt @@ -1,6 +1,13 @@ Generating forms for models =========================== +.. admonition:: Note + + The APIs described in this document have been deprecated. If you're + developing new code, use `ModelForms`_ instead. + +.. _ModelForms: ../modelforms/ + If you're building a database-driven app, chances are you'll have forms that map closely to Django models. For instance, you might have a ``BlogComment`` model, and you want to create a form that lets people submit comments. In this diff --git a/docs/localflavor.txt b/docs/localflavor.txt index 3eecba57e7..fc227fee28 100644 --- a/docs/localflavor.txt +++ b/docs/localflavor.txt @@ -2,16 +2,22 @@ The "local flavor" add-ons ========================== -Django comes with assorted pieces of code that are useful only for a particular -country or culture. These pieces of code are organized as a set of +Following its "batteries included" philosophy, Django comes with assorted +pieces of code that are useful for particular countries or cultures. These are +called the "local flavor" add-ons and live in the ``django.contrib.localflavor`` +package. + +Inside that package, country- or culture-specific code is organized into subpackages, named using `ISO 3166 country codes`_. -.. _ISO 3166 country codes: http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm - Most of the ``localflavor`` add-ons are localized form components deriving from -the newforms_ framework. To use one of these localized components, just import -the relevant subpackage. For example, a form with a field for French telephone -numbers is created like so:: +the newforms_ framework -- for example, a ``USStateField`` that knows how to +validate U.S. state abbreviations, and a ``FISocialSecurityNumber`` that knows +how to validate Finnish social security numbers. + +To use one of these localized components, just import the relevant subpackage. +For example, here's how you can create a form with a field representing a +French telephone number:: from django import newforms as forms from django.contrib.localflavor import fr @@ -19,32 +25,48 @@ numbers is created like so:: class MyForm(forms.Form): my_french_phone_no = fr.forms.FRPhoneNumberField() +Supported countries +=================== + Countries currently supported by ``localflavor`` are: -* Argentina_ -* Australia_ -* Brazil_ -* Canada_ -* Chile_ -* Finland_ -* France_ -* Germany_ -* Holland_ -* Iceland_ -* India_ -* Italy_ -* Japan_ -* Mexico_ -* Norway_ -* Peru_ -* Poland_ -* Slovakia_ -* `South Africa`_ -* Spain_ -* Switzerland_ -* `United Kingdom`_ -* `United States of America`_ + * Argentina_ + * Australia_ + * Brazil_ + * Canada_ + * Chile_ + * Finland_ + * France_ + * Germany_ + * Holland_ + * Iceland_ + * India_ + * Italy_ + * Japan_ + * Mexico_ + * Norway_ + * Peru_ + * Poland_ + * Slovakia_ + * `South Africa`_ + * Spain_ + * Switzerland_ + * `United Kingdom`_ + * `United States of America`_ +The ``localflavor`` package also includes a ``generic`` subpackage, containing +useful code that is not specific to one particular country or culture. +Currently, it defines date and datetime input fields based on those from +newforms_, but with non-US default formats. Here's an example of how to use +them:: + + from django import newforms as forms + from django.contrib.localflavor import generic + + class MyForm(forms.Form): + my_date_field = generic.forms.DateField() + +.. _ISO 3166 country codes: http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm .. _Argentina: `Argentina (django.contrib.localflavor.ar)`_ .. _Australia: `Australia (django.contrib.localflavor.au)`_ .. _Brazil: `Brazil (django.contrib.localflavor.br)`_ @@ -68,29 +90,15 @@ Countries currently supported by ``localflavor`` are: .. _Switzerland: `Switzerland (django.contrib.localflavor.ch)`_ .. _United Kingdom: `United Kingdom (django.contrib.localflavor.uk)`_ .. _United States of America: `United States of America (django.contrib.localflavor.us)`_ - -The ``localflavor`` add-on also includes the ``generic`` subpackage, containing -useful code that is not specific to one particular country or culture. -Currently, it defines date and date & time input fields based on those from -newforms_, but with non-US default formats. Here's an example of how to use -them:: - - from django import newforms as forms - from django.contrib.localflavor import generic - - class MyForm(forms.Form): - my_date_field = generic.forms.DateField() - .. _newforms: ../newforms/ +Adding flavors +============== -.. admonition:: Adding a Flavor - - We'd love to add more of these to Django, so please create a ticket for - anything that you've found useful. Please use unicode objects - (``u'mystring'``) for strings, rather than setting the encoding in the file - (see any of the existing flavors for examples). - +We'd love to add more of these to Django, so please `create a ticket`_ with +any code you'd like to contribute. One thing we ask is that you please use +Unicode objects (``u'mystring'``) for strings, rather than setting the encoding +in the file. See any of the existing flavors for examples. Argentina (``django.contrib.localflavor.ar``) ============================================= @@ -108,7 +116,6 @@ ARProvinceSelect A ``Select`` widget that uses a list of Argentina's provinces as its choices. - Australia (``django.contrib.localflavor.au``) ============================================= @@ -129,7 +136,6 @@ AUStateSelect A ``Select`` widget that uses a list of Australian states/territories as its choices. - Brazil (``django.contrib.localflavor.br``) ========================================== @@ -151,7 +157,6 @@ BRStateSelect A ``Select`` widget that uses a list of Brazilian states/territories as its choices. - Canada (``django.contrib.localflavor.ca``) ========================================== @@ -176,7 +181,7 @@ CASocialInsuranceNumberField ---------------------------- A form field that validates input as a Canadian Social Insurance Number (SIN). -A valid number must have the format XXX-XXX-XXXX and pass a `Luhn mod-10 +A valid number must have the format XXX-XXX-XXX and pass a `Luhn mod-10 checksum`_. .. _Luhn mod-10 checksum: http://en.wikipedia.org/wiki/Luhn_algorithm @@ -187,7 +192,6 @@ CAProvinceSelect A ``Select`` widget that uses a list of Canadian provinces and territories as its choices. - Chile (``django.contrib.localflavor.cl``) ========================================= @@ -203,7 +207,6 @@ CLRegionSelect A ``Select`` widget that uses a list of Chilean regions (Regiones) as its choices. - Finland (``django.contrib.localflavor.fi``) =========================================== @@ -215,7 +218,7 @@ A form field that validates input as a Finnish social security number. FIZipCodeField -------------- -A form field that validates input as a Finnish zip code. Valid codes +A form field that validates input as a Finnish zip code. Valid codes consist of five digits. FIMunicipalitySelect @@ -224,7 +227,6 @@ FIMunicipalitySelect A ``Select`` widget that uses a list of Finnish municipalities as its choices. - France (``django.contrib.localflavor.fr``) ========================================== @@ -232,13 +234,13 @@ FRPhoneNumberField ------------------ A form field that validates input as a French local phone number. The -correct format is 0X XX XX XX XX. 0X.XX.XX.XX.XX and 0XXXXXXXXX validate +correct format is 0X XX XX XX XX. 0X.XX.XX.XX.XX and 0XXXXXXXXX validate but are corrected to 0X XX XX XX XX. FRZipCodeField -------------- -A form field that validates input as a French zip code. Valid codes +A form field that validates input as a French zip code. Valid codes consist of five digits. FRDepartmentSelect @@ -246,7 +248,6 @@ FRDepartmentSelect A ``Select`` widget that uses a list of French departments as its choices. - Germany (``django.contrib.localflavor.de``) =========================================== @@ -254,7 +255,7 @@ DEIdentityCardNumberField ------------------------- A form field that validates input as a German identity card number -(Personalausweis_). Valid numbers have the format +(Personalausweis_). Valid numbers have the format XXXXXXXXXXX-XXXXXXX-XXXXXXX-X, with no group consisting entirely of zeroes. .. _Personalausweis: http://de.wikipedia.org/wiki/Personalausweis @@ -262,7 +263,7 @@ XXXXXXXXXXX-XXXXXXX-XXXXXXX-X, with no group consisting entirely of zeroes. DEZipCodeField -------------- -A form field that validates input as a German zip code. Valid codes +A form field that validates input as a German zip code. Valid codes consist of five digits. DEStateSelect @@ -270,7 +271,6 @@ DEStateSelect A ``Select`` widget that uses a list of German states as its choices. - Holland (``django.contrib.localflavor.nl``) =========================================== @@ -296,7 +296,6 @@ NLProvinceSelect A ``Select`` widget that uses a list of Dutch provinces as its list of choices. - Iceland (``django.contrib.localflavor.is_``) ============================================ @@ -304,7 +303,7 @@ ISIdNumberField --------------- A form field that validates input as an Icelandic identification number -(kennitala). The format is XXXXXX-XXXX. +(kennitala). The format is XXXXXX-XXXX. ISPhoneNumberField ------------------ @@ -318,7 +317,6 @@ ISPostalCodeSelect A ``Select`` widget that uses a list of Icelandic postal codes as its choices. - India (``django.contrib.localflavor.in_``) ========================================== @@ -326,7 +324,7 @@ INStateField ------------ A form field that validates input as an Indian state/territory name or -abbreviation. Input is normalized to the standard two-letter vehicle +abbreviation. Input is normalized to the standard two-letter vehicle registration abbreviation for the given state or territory. INZipCodeField @@ -341,7 +339,6 @@ INStateSelect A ``Select`` widget that uses a list of Indian states/territories as its choices. - Italy (``django.contrib.localflavor.it``) ========================================= @@ -361,7 +358,7 @@ A form field that validates Italian VAT numbers (partita IVA). ITZipCodeField -------------- -A form field that validates input as an Italian zip code. Valid codes +A form field that validates input as an Italian zip code. Valid codes must have five digits. ITProvinceSelect @@ -374,22 +371,20 @@ ITRegionSelect A ``Select`` widget that uses a list of Italian regions as its choices. - Japan (``django.contrib.localflavor.jp``) ========================================= JPPostalCodeField ----------------- -A form field that validates input as a Japanese postcode. -It accepts seven digits, with or without a hyphen. +A form field that validates input as a Japanese postcode. It accepts seven +digits, with or without a hyphen. JPPrefectureSelect ------------------ A ``Select`` widget that uses a list of Japanese prefectures as its choices. - Mexico (``django.contrib.localflavor.mx``) ========================================== @@ -398,7 +393,6 @@ MXStateSelect A ``Select`` widget that uses a list of Mexican states as its choices. - Norway (``django.contrib.localflavor.no``) ========================================== @@ -413,7 +407,7 @@ A form field that validates input as a Norwegian social security number NOZipCodeField -------------- -A form field that validates input as a Norwegian zip code. Valid codes +A form field that validates input as a Norwegian zip code. Valid codes have four digits. NOMunicipalitySelect @@ -422,7 +416,6 @@ NOMunicipalitySelect A ``Select`` widget that uses a list of Norwegian municipalities (fylker) as its choices. - Peru (``django.contrib.localflavor.pe``) ======================================== @@ -436,14 +429,13 @@ PERUCField ---------- A form field that validates input as an RUC (Registro Unico de -Contribuyentes) number. Valid RUC numbers have eleven digits. +Contribuyentes) number. Valid RUC numbers have 11 digits. PEDepartmentSelect ------------------ A ``Select`` widget that uses a list of Peruvian Departments as its choices. - Poland (``django.contrib.localflavor.pl``) ========================================== @@ -459,7 +451,7 @@ PLNationalBusinessRegisterField ------------------------------- A form field that validates input as a Polish National Official Business -Register Number (REGON_), having either seven or nine digits. The checksum +Register Number (REGON_), having either seven or nine digits. The checksum algorithm used for REGONs is documented at http://wipos.p.lodz.pl/zylla/ut/nip-rego.html. @@ -468,14 +460,14 @@ http://wipos.p.lodz.pl/zylla/ut/nip-rego.html. PLPostalCodeField ----------------- -A form field that validates input as a Polish postal code. The valid format +A form field that validates input as a Polish postal code. The valid format is XX-XXX, where X is a digit. PLTaxNumberField ---------------- -A form field that validates input as a Polish Tax Number (NIP). Valid -formats are XXX-XXX-XX-XX or XX-XX-XXX-XXX. The checksum algorithm used +A form field that validates input as a Polish Tax Number (NIP). Valid +formats are XXX-XXX-XX-XX or XX-XX-XXX-XXX. The checksum algorithm used for NIPs is documented at http://wipos.p.lodz.pl/zylla/ut/nip-rego.html. PLAdministrativeUnitSelect @@ -490,14 +482,13 @@ PLVoivodeshipSelect A ``Select`` widget that uses a list of Polish voivodeships (administrative provinces) as its choices. - Slovakia (``django.contrib.localflavor.sk``) ============================================ SKPostalCodeField ----------------- -A form field that validates input as a Slovak postal code. Valid formats +A form field that validates input as a Slovak postal code. Valid formats are XXXXX or XXX XX, where X is a digit. SKDistrictSelect @@ -510,24 +501,22 @@ SKRegionSelect A ``Select`` widget that uses a list of Slovak regions as its choices. - South Africa (``django.contrib.localflavor.za``) ================================================ ZAIDField --------- -A form field that validates input as a South African ID number. Validation +A form field that validates input as a South African ID number. Validation uses the Luhn checksum and a simplistic (i.e., not entirely accurate) check for birth date. ZAPostCodeField --------------- -A form field that validates input as a South African postcode. Valid +A form field that validates input as a South African postcode. Valid postcodes must have four digits. - Spain (``django.contrib.localflavor.es``) ========================================= @@ -541,23 +530,23 @@ ESCCCField ---------- A form field that validates input as a Spanish bank account number (Codigo -Cuenta Cliente or CCC). A valid CCC number has the format +Cuenta Cliente or CCC). A valid CCC number has the format EEEE-OOOO-CC-AAAAAAAAAA, where the E, O, C and A digits denote the entity, -office, checksum and account, respectively. The first checksum digit -validates the entity and office. The second checksum digit validates the -account. It is also valid to use a space as a delimiter, or to use no +office, checksum and account, respectively. The first checksum digit +validates the entity and office. The second checksum digit validates the +account. It is also valid to use a space as a delimiter, or to use no delimiter. ESPhoneNumberField ------------------ -A form field that validates input as a Spanish phone number. Valid numbers +A form field that validates input as a Spanish phone number. Valid numbers have nine digits, the first of which is 6, 8 or 9. ESPostalCodeField ----------------- -A form field that validates input as a Spanish postal code. Valid codes +A form field that validates input as a Spanish postal code. Valid codes have five digits, the first two being in the range 01 to 52, representing the province. @@ -571,7 +560,6 @@ ESRegionSelect A ``Select`` widget that uses a list of Spanish regions as its choices. - Switzerland (``django.contrib.localflavor.ch``) =============================================== @@ -585,14 +573,14 @@ have the correct checksums -- see http://adi.kousz.ch/artikel/IDCHE.htm. CHPhoneNumberField ------------------ -A form field that validates input as a Swiss phone number. The correct -format is 0XX XXX XX XX. 0XX.XXX.XX.XX and 0XXXXXXXXX validate but are +A form field that validates input as a Swiss phone number. The correct +format is 0XX XXX XX XX. 0XX.XXX.XX.XX and 0XXXXXXXXX validate but are corrected to 0XX XXX XX XX. CHZipCodeField -------------- -A form field that validates input as a Swiss zip code. Valid codes +A form field that validates input as a Swiss zip code. Valid codes consist of four digits. CHStateSelect @@ -600,7 +588,6 @@ CHStateSelect A ``Select`` widget that uses a list of Swiss states as its choices. - United Kingdom (``django.contrib.localflavor.uk``) ================================================== @@ -611,6 +598,15 @@ A form field that validates input as a UK postcode. The regular expression used is sourced from the schema for British Standard BS7666 address types at http://www.govtalk.gov.uk/gdsc/schemas/bs7666-v2-0.xsd. +UKCountySelect +-------------- + +A ``Select`` widget that uses a list of UK counties/regions as its choices. + +UKNationSelect +-------------- + +A ``Select`` widget that uses a list of UK nations as its choices. United States of America (``django.contrib.localflavor.us``) ============================================================ @@ -626,13 +622,13 @@ USSocialSecurityNumberField A form field that validates input as a U.S. Social Security Number (SSN). A valid SSN must obey the following rules: -* Format of XXX-XX-XXXX -* No group of digits consisting entirely of zeroes -* Leading group of digits cannot be 666 -* Number not in promotional block 987-65-4320 through 987-65-4329 -* Number not one known to be invalid due to widespread promotional - use or distribution (e.g., the Woolworth's number or the 1962 - promotional number) + * Format of XXX-XX-XXXX + * No group of digits consisting entirely of zeroes + * Leading group of digits cannot be 666 + * Number not in promotional block 987-65-4320 through 987-65-4329 + * Number not one known to be invalid due to widespread promotional + use or distribution (e.g., the Woolworth's number or the 1962 + promotional number) USStateField ------------ @@ -644,11 +640,11 @@ for the given state. USZipCodeField -------------- -A form field that validates input as a U.S. zip code. Valid formats are +A form field that validates input as a U.S. ZIP code. Valid formats are XXXXX or XXXXX-XXXX. USStateSelect ------------- -A form Select widget that uses a list of U.S. states/territories as its +A form ``Select`` widget that uses a list of U.S. states/territories as its choices. diff --git a/docs/middleware.txt b/docs/middleware.txt index 39019caf1e..a2853e2965 100644 --- a/docs/middleware.txt +++ b/docs/middleware.txt @@ -61,17 +61,18 @@ Adds a few conveniences for perfectionists: settings. If ``APPEND_SLASH`` is ``True`` 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. + and it is not found in the URLconf, then a new URL is formed by appending a + slash at the end. If this new URL is found in the URLconf, then Django + redirects the request to this new URL. Otherwise, the initial URL is + processed as usual. - So ``foo.com/bar`` will be redirected to ``foo.com/bar/`` if you do not - have a valid urlpattern for ``foo.com/bar``, and do have a valid urlpattern - for ``foo.com/bar/``. + For example, ``foo.com/bar`` will be redirected to ``foo.com/bar/`` if you + don't have a valid URL pattern for ``foo.com/bar`` but *do* have a valid + pattern for ``foo.com/bar/``. **New in Django development version:** The behavior of ``APPEND_SLASH`` has - changed slightly in the development version. It didn't used to check to see - whether the pattern was matched in the URLconf. + changed slightly in the development version. It didn't used to check whether + the pattern was matched in the URLconf. If ``PREPEND_WWW`` is ``True``, URLs that lack a leading "www." will be redirected to the same URL with a leading "www." @@ -153,6 +154,17 @@ every incoming ``HttpRequest`` object. See `Authentication in Web requests`_. .. _Authentication in Web requests: ../authentication/#authentication-in-web-requests +django.contrib.csrf.middleware.CsrfMiddleware +--------------------------------------------- + +**New in Django development version** + +Adds protection against Cross Site Request Forgeries by adding hidden form +fields to POST forms and checking requests for the correct value. See the +`Cross Site Request Forgery protection documentation`_. + +.. _`Cross Site Request Forgery protection documentation`: ../csrf/ + django.middleware.transaction.TransactionMiddleware --------------------------------------------------- diff --git a/docs/modelforms.txt b/docs/modelforms.txt index 372abf9811..0136540bed 100644 --- a/docs/modelforms.txt +++ b/docs/modelforms.txt @@ -277,7 +277,7 @@ model fields: any attempt to ``save()`` a ``ModelForm`` with missing fields will fail. To avoid this failure, you must instantiate your model with initial values for the missing, but required fields, or use ``save(commit=False)`` and - manually set anyextra required fields:: + manually set any extra required fields:: instance = Instance(required_field='value') form = InstanceForm(request.POST, instance=instance) @@ -296,7 +296,7 @@ Overriding the default field types ---------------------------------- The default field types, as described in the "Field types" table above, are -sensible defaults; if you have a ``DateField`` in your model, chances are you'd +sensible defaults. If you have a ``DateField`` in your model, chances are you'd want that to be represented as a ``DateField`` in your form. But ``ModelForm`` gives you the flexibility of changing the form field type for a given model field. You do this by declaratively specifying fields like diff --git a/docs/request_response.txt b/docs/request_response.txt index 2445b3e6b9..e3d794c9ba 100644 --- a/docs/request_response.txt +++ b/docs/request_response.txt @@ -156,6 +156,18 @@ Methods Returns ``True`` or ``False``, designating whether ``request.GET`` or ``request.POST`` has the given key. +``get_host()`` + **New in Django development version** + + Returns the originating host of the request using information from the + ``HTTP_X_FORWARDED_HOST`` and ``HTTP_HOST`` headers (in that order). If + they don't provide a value, the method uses a combination of + ``SERVER_NAME`` and ``SERVER_PORT`` as detailed in `PEP 333`_. + + .. _PEP 333: http://www.python.org/dev/peps/pep-0333/ + + Example: ``"127.0.0.1:8000"`` + ``get_full_path()`` Returns the ``path``, plus an appended query string, if applicable. @@ -452,7 +464,7 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in ``HttpResponseNotModified`` The constructor doesn't take any arguments. Use this to designate that a - page hasn't been modified since the user's last request. + page hasn't been modified since the user's last request (status code 304). ``HttpResponseBadRequest`` **New in Django development version.** diff --git a/docs/sessions.txt b/docs/sessions.txt index eddc3f174a..6355524d2e 100644 --- a/docs/sessions.txt +++ b/docs/sessions.txt @@ -99,7 +99,7 @@ It implements the following standard dictionary methods: * ``items()`` - * ``setdefault()`` + * ``setdefault()`` (**New in Django development version**) It also has these three methods: diff --git a/docs/settings.txt b/docs/settings.txt index bc73f3e759..8478e0ce96 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -981,8 +981,13 @@ TEST_DATABASE_NAME Default: ``None`` -The name of database to use when running the test suite. If a value of -``None`` is specified, the test database will use the name ``'test_' + settings.DATABASE_NAME``. See `Testing Django Applications`_. +The name of database to use when running the test suite. + +If the default value (``None``) is used with the SQLite database engine, the +tests will use a memory resident database. For all other database engines the +test database will use the name ``'test_' + settings.DATABASE_NAME``. + +See `Testing Django Applications`_. .. _Testing Django Applications: ../testing/ diff --git a/docs/sites.txt b/docs/sites.txt index 5896afcf41..9516b43995 100644 --- a/docs/sites.txt +++ b/docs/sites.txt @@ -97,7 +97,7 @@ Hooking into the current site from views ---------------------------------------- On a lower level, you can use the sites framework in your Django views to do -particular things based on what site in which the view is being called. +particular things based on the site in which the view is being called. For example:: from django.conf import settings @@ -330,13 +330,13 @@ Here's how Django uses the sites framework: retrieving flatpages to display. * In the `syndication framework`_, the templates for ``title`` and - ``description`` automatically have access to a variable ``{{{ site }}}``, + ``description`` automatically have access to a variable ``{{ site }}``, which is the ``Site`` object representing the current site. Also, the hook for providing item URLs will use the ``domain`` from the current ``Site`` object if you don't specify a fully-qualified domain. * In the `authentication framework`_, the ``django.contrib.auth.views.login`` - view passes the current ``Site`` name to the template as ``{{{ site_name }}}``. + view passes the current ``Site`` name to the template as ``{{ site_name }}``. * The shortcut view (``django.views.defaults.shortcut``) uses the domain of the current ``Site`` object when calculating an object's URL. diff --git a/docs/syndication_feeds.txt b/docs/syndication_feeds.txt index b3edf4c008..ebd6af26f8 100644 --- a/docs/syndication_feeds.txt +++ b/docs/syndication_feeds.txt @@ -201,7 +201,7 @@ the feed. An example makes this clear. Here's the code for these beat-specific feeds:: - from django.contrib.syndication import FeedDoesNotExist + from django.contrib.syndication.feeds import FeedDoesNotExist class BeatFeed(Feed): def get_object(self, bits): diff --git a/docs/templates_python.txt b/docs/templates_python.txt index 4865f65331..014a853a41 100644 --- a/docs/templates_python.txt +++ b/docs/templates_python.txt @@ -691,8 +691,8 @@ This way, you'll be able to pass, say, an integer to this filter, and it won't cause an ``AttributeError`` (because integers don't have ``lower()`` methods). -Registering a custom filters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Registering custom filters +~~~~~~~~~~~~~~~~~~~~~~~~~~ Once you've written your filter definition, you need to register it with your ``Library`` instance, to make it available to Django's template language:: diff --git a/docs/testing.txt b/docs/testing.txt index 7705380eff..54b8ba9ae6 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -270,27 +270,21 @@ a test case, add the name of the test method to the label:: $ ./manage.py test animals.AnimalTestCase.testFluffyAnimals -Understanding the test output ------------------------------ +The test database +----------------- -When you run your tests, you'll see a number of messages as the test runner -prepares itself:: +Tests that require a database (namely, model tests) will not use +your "real" (production) database. A separate, blank database is created +for the tests. - Creating test database... - Creating table myapp_animal - Creating table myapp_mineral - Loading 'initial_data' fixtures... - No fixtures found. +Regardless of whether the tests pass or fail, the test database is destroyed +when all the tests have been executed. -This tells you that the test runner is creating a test database -- a blank, -from-scratch database that it will use for any tests that happen to require a -database (namely, model tests). - -Don't worry -- the test runner will not touch your "real" (production) -database. It creates a separate database purely for the tests. This test -database gets its name by prepending ``test_`` to the value of the -``DATABASE_NAME`` setting. If you want to use a different name, specify the -``TEST_DATABASE_NAME`` setting. +By default this test database gets its name by prepending ``test_`` to the +value of the ``DATABASE_NAME`` setting. When using the SQLite database engine +the tests will by default use an in-memory database (i.e., the database will be +created in memory, bypassing the filesystem entirely!). If you want to use a +different database name, specify the ``TEST_DATABASE_NAME`` setting. Aside from using a separate database, the test runner will otherwise use all of the same database settings you have in your settings file: ``DATABASE_ENGINE``, @@ -306,6 +300,22 @@ settings_ documentation for details of these advanced settings. .. _settings: ../settings/ +Understanding the test output +----------------------------- + +When you run your tests, you'll see a number of messages as the test runner +prepares itself. You can control the level of detail of these messages with the +``verbosity`` option on the command line:: + + Creating test database... + Creating table myapp_animal + Creating table myapp_mineral + Loading 'initial_data' fixtures... + No fixtures found. + +This tells you that the test runner is creating a test database, as described +in the previous section. + Once the test database has been created, Django will run your tests. If everything goes well, you'll see something like this:: @@ -349,9 +359,6 @@ failed and erroneous tests. If all the tests pass, the return code is 0. This feature is useful if you're using the test-runner script in a shell script and need to test for success or failure at that level. -Regardless of whether the tests pass or fail, the test database is destroyed when -all the tests have been executed. - Testing tools ============= diff --git a/tests/regressiontests/forms/localflavor/ca.py b/tests/regressiontests/forms/localflavor/ca.py index a13a6de65f..48171b0558 100644 --- a/tests/regressiontests/forms/localflavor/ca.py +++ b/tests/regressiontests/forms/localflavor/ca.py @@ -213,13 +213,13 @@ u'046-454-286' >>> f.clean('046-454-287') Traceback (most recent call last): ... -ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.'] +ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format.'] >>> f.clean('046 454 286') Traceback (most recent call last): ... -ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.'] +ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format.'] >>> f.clean('046-44-286') Traceback (most recent call last): ... -ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.'] +ValidationError: [u'Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format.'] """ diff --git a/tests/regressiontests/forms/localflavor/cl.py b/tests/regressiontests/forms/localflavor/cl.py index 407f6610e4..23e7a41902 100644 --- a/tests/regressiontests/forms/localflavor/cl.py +++ b/tests/regressiontests/forms/localflavor/cl.py @@ -41,7 +41,7 @@ Strict RUT usage (does not allow imposible values) >>> rut.clean('11-6') Traceback (most recent call last): ... -ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] +ValidationError: [u'Enter a valid Chilean RUT. The format is XX.XXX.XXX-X.'] # valid format, bad verifier. >>> rut.clean('11.111.111-0') @@ -53,17 +53,17 @@ ValidationError: [u'The Chilean RUT is not valid.'] >>> rut.clean('767484100') Traceback (most recent call last): ... -ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] +ValidationError: [u'Enter a valid Chilean RUT. The format is XX.XXX.XXX-X.'] >>> rut.clean('78.412.790-7') u'78.412.790-7' >>> rut.clean('8.334.6043') Traceback (most recent call last): ... -ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] +ValidationError: [u'Enter a valid Chilean RUT. The format is XX.XXX.XXX-X.'] >>> rut.clean('76793310-K') Traceback (most recent call last): ... -ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] +ValidationError: [u'Enter a valid Chilean RUT. The format is XX.XXX.XXX-X.'] ## CLRegionSelect ######################################################### >>> from django.contrib.localflavor.cl.forms import CLRegionSelect diff --git a/tests/regressiontests/forms/localflavor/uk.py b/tests/regressiontests/forms/localflavor/uk.py index d7848f70a8..258c22e5a9 100644 --- a/tests/regressiontests/forms/localflavor/uk.py +++ b/tests/regressiontests/forms/localflavor/uk.py @@ -12,13 +12,15 @@ u'BT32 4PX' >>> f.clean('GIR 0AA') u'GIR 0AA' >>> f.clean('BT324PX') -Traceback (most recent call last): -... -ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +u'BT32 4PX' >>> f.clean('1NV 4L1D') Traceback (most recent call last): ... -ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +ValidationError: [u'Enter a valid postcode.'] +>>> f.clean('1NV4L1D') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid postcode.'] >>> f.clean(None) Traceback (most recent call last): ... @@ -27,7 +29,20 @@ ValidationError: [u'This field is required.'] Traceback (most recent call last): ... ValidationError: [u'This field is required.'] - +>>> f.clean(' so11aa ') +u'SO1 1AA' +>>> f.clean(' so1 1aa ') +u'SO1 1AA' +>>> f.clean('G2 3wt') +u'G2 3WT' +>>> f.clean('EC1A 1BB') +u'EC1A 1BB' +>>> f.clean('Ec1a1BB') +u'EC1A 1BB' +>>> f.clean(' b0gUS') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid postcode.'] >>> f = UKPostcodeField(required=False) >>> f.clean('BT32 4PX') u'BT32 4PX' @@ -36,11 +51,9 @@ u'GIR 0AA' >>> f.clean('1NV 4L1D') Traceback (most recent call last): ... -ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +ValidationError: [u'Enter a valid postcode.'] >>> f.clean('BT324PX') -Traceback (most recent call last): -... -ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +u'BT32 4PX' >>> f.clean(None) u'' >>> f.clean('') diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 5cfae029bb..31b956a99d 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -391,9 +391,45 @@ u'\ufffd' >>> q.getlist('foo') [u'bar', u'\ufffd'] + +###################################### +# HttpResponse with Unicode headers # +###################################### + +>>> r = HttpResponse() + +If we insert a unicode value it will be converted to an ascii +string. This makes sure we comply with the HTTP specifications. + +>>> r['value'] = u'test value' +>>> isinstance(r['value'], str) +True + +An error is raised When a unicode object with non-ascii is assigned. + +>>> r['value'] = u't\xebst value' # doctest:+ELLIPSIS +Traceback (most recent call last): +... +UnicodeEncodeError: ..., HTTP response headers must be in US-ASCII format + +The response also converts unicode keys to strings. + +>>> r[u'test'] = 'testing key' +>>> l = list(r.items()) +>>> l.sort() +>>> l[1] +('test', 'testing key') + +It will also raise errors for keys with non-ascii data. + +>>> r[u't\xebst'] = 'testing key' # doctest:+ELLIPSIS +Traceback (most recent call last): +... +UnicodeEncodeError: ..., HTTP response headers must be in US-ASCII format + """ -from django.http import QueryDict +from django.http import QueryDict, HttpResponse if __name__ == "__main__": import doctest diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py index 4175bdbe5f..f38b2cdef1 100644 --- a/tests/regressiontests/templates/filters.py +++ b/tests/regressiontests/templates/filters.py @@ -108,8 +108,8 @@ def get_filter_tests(): 'filter-urlize05': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": ""}, ""), 'filter-urlize06': ('{{ a|urlize }}', {"a": ""}, '<script>alert('foo')</script>'), - 'filter-urlizetrunc01': ('{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'http:... http:...'), - 'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'http:... http:...'), + 'filter-urlizetrunc01': ('{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('"Safe" http://example.com?x=&y=')}, u'"Unsafe" http:... "Safe" http:...'), + 'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('"Safe" http://example.com?x=&y=')}, u'"Unsafe" http:... "Safe" http:...'), 'filter-wordcount01': ('{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), 'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 846023afc9..ef8fd43292 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -441,6 +441,8 @@ class Templates(unittest.TestCase): 'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"), 'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"), 'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"), + 'for-tag-vars05': ("{% for val in values %}{% if forloop.first %}f{% else %}x{% endif %}{% endfor %}", {"values": [6, 6, 6]}, "fxx"), + 'for-tag-vars06': ("{% for val in values %}{% if forloop.last %}l{% else %}x{% endif %}{% endfor %}", {"values": [6, 6, 6]}, "xxl"), 'for-tag-unpack01': ("{% for key,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), 'for-tag-unpack03': ("{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), 'for-tag-unpack04': ("{% for key , value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), @@ -807,6 +809,16 @@ class Templates(unittest.TestCase): '{% endfor %},' + \ '{% endfor %}', {}, ''), + + # Test syntax. + 'regroup03': ('{% regroup data by bar as %}', {}, + template.TemplateSyntaxError), + 'regroup04': ('{% regroup data by bar thisaintright grouped %}', {}, + template.TemplateSyntaxError), + 'regroup05': ('{% regroup data thisaintright bar as grouped %}', {}, + template.TemplateSyntaxError), + 'regroup06': ('{% regroup data by bar as grouped toomanyargs %}', {}, + template.TemplateSyntaxError), ### TEMPLATETAG TAG ####################################################### 'templatetag01': ('{% templatetag openblock %}', {}, '{%'), diff --git a/tests/regressiontests/views/media/file.unknown b/tests/regressiontests/views/media/file.unknown new file mode 100644 index 0000000000..77dcda8970 --- /dev/null +++ b/tests/regressiontests/views/media/file.unknown @@ -0,0 +1 @@ +An unknown file extension. diff --git a/tests/regressiontests/views/tests/static.py b/tests/regressiontests/views/tests/static.py index c731b249e8..d7e87d19d2 100644 --- a/tests/regressiontests/views/tests/static.py +++ b/tests/regressiontests/views/tests/static.py @@ -13,11 +13,15 @@ class StaticTests(TestCase): response = self.client.get('/views/site_media/%s' % filename) file = open(path.join(media_dir, filename)) self.assertEquals(file.read(), response.content) + self.assertEquals(len(response.content), int(response['Content-Length'])) + + def test_unknown_mime_type(self): + response = self.client.get('/views/site_media/file.unknown') + self.assertEquals('application/octet-stream', response['Content-Type']) def test_copes_with_empty_path_component(self): file_name = 'file.txt' response = self.client.get('/views/site_media//%s' % file_name) file = open(path.join(media_dir, file_name)) self.assertEquals(file.read(), response.content) - - + diff --git a/tests/runtests.py b/tests/runtests.py index 2d3b737cec..599916ab23 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -93,6 +93,7 @@ def django_tests(verbosity, interactive, test_labels): old_root_urlconf = settings.ROOT_URLCONF old_template_dirs = settings.TEMPLATE_DIRS old_use_i18n = settings.USE_I18N + old_login_url = settings.LOGIN_URL old_language_code = settings.LANGUAGE_CODE old_middleware_classes = settings.MIDDLEWARE_CLASSES @@ -102,6 +103,7 @@ def django_tests(verbosity, interactive, test_labels): settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),) settings.USE_I18N = True settings.LANGUAGE_CODE = 'en' + settings.LOGIN_URL = '/accounts/login/' settings.MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', @@ -153,6 +155,7 @@ def django_tests(verbosity, interactive, test_labels): settings.TEMPLATE_DIRS = old_template_dirs settings.USE_I18N = old_use_i18n settings.LANGUAGE_CODE = old_language_code + settings.LOGIN_URL = old_login_url settings.MIDDLEWARE_CLASSES = old_middleware_classes if __name__ == "__main__":