1
0
mirror of https://github.com/django/django.git synced 2025-07-05 10:19:20 +00:00

gis: Merged revisions 6920-6989 via svnmerge from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6990 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2008-01-02 16:57:53 +00:00
parent ef0f46f1d0
commit 7e322b5908
75 changed files with 1074 additions and 689 deletions

View File

@ -130,6 +130,7 @@ answer newbie questions, and generally made Django that much better:
Matthew Flanagan <http://wadofstuff.blogspot.com> Matthew Flanagan <http://wadofstuff.blogspot.com>
Eric Floehr <eric@intellovations.com> Eric Floehr <eric@intellovations.com>
Vincent Foley <vfoleybourgon@yahoo.ca> Vincent Foley <vfoleybourgon@yahoo.ca>
Rudolph Froger <rfroger@estrate.nl>
Jorge Gajon <gajon@gajon.org> Jorge Gajon <gajon@gajon.org>
gandalf@owca.info gandalf@owca.info
Marc Garcia <marc.garcia@accopensys.com> Marc Garcia <marc.garcia@accopensys.com>
@ -300,6 +301,7 @@ answer newbie questions, and generally made Django that much better:
Georgi Stanojevski <glisha@gmail.com> Georgi Stanojevski <glisha@gmail.com>
Vasiliy Stavenko <stavenko@gmail.com> Vasiliy Stavenko <stavenko@gmail.com>
Thomas Steinacher <http://www.eggdrop.ch/> Thomas Steinacher <http://www.eggdrop.ch/>
Johan C. Stöver <johan@nilling.nl>
nowell strite nowell strite
Thomas Stromberg <tstromberg@google.com> Thomas Stromberg <tstromberg@google.com>
Sundance Sundance
@ -327,6 +329,7 @@ answer newbie questions, and generally made Django that much better:
tt@gurgle.no tt@gurgle.no
Amit Upadhyay Amit Upadhyay
Geert Vanderkelen Geert Vanderkelen
I.S. van Oostveen <v.oostveen@idca.nl>
viestards.lists@gmail.com viestards.lists@gmail.com
George Vilches <gav@thataddress.com> George Vilches <gav@thataddress.com>
Vlado <vlado@labath.org> Vlado <vlado@labath.org>

View File

@ -52,7 +52,9 @@ class LazySettings(object):
if not settings_module: # If it's set but is an empty string. if not settings_module: # If it's set but is an empty string.
raise KeyError raise KeyError
except 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) self._target = Settings(settings_module)

View File

@ -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 <johan@nilling.nl>, 2005.
# Rudolph Froger <rfroger@estrate.nl>, 2006.
#
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Django 1.0\n" "Project-Id-Version: Django 1.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2006-12-09 15:51+0100\n" "POT-Creation-Date: 2006-12-09 15:51+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: 2007-12-17 21:14+0100\n"
"Last-Translator: Johan C. Stöver <johan@nilling.nl>\n" "Last-Translator: I.S. van Oostveen\n"
"Language-Team: \n" "Language-Team: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
@ -69,7 +64,7 @@ msgstr "De waarde moet een geheel getal zijn."
#: db/models/fields/__init__.py:381 #: db/models/fields/__init__.py:381
msgid "This value must be either True or False." 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 #: db/models/fields/__init__.py:397
msgid "This field cannot be null." msgid "This field cannot be null."
@ -279,7 +274,7 @@ msgstr "Alleen alfabetische karakters zijn toegestaan"
#: core/validators.py:139 #: core/validators.py:139
msgid "Year must be 1900 or later." 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 #: core/validators.py:143
#, python-format #, 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 #: core/validators.py:173 core/validators.py:442 forms/__init__.py:667
msgid "No file was submitted. Check the encoding type on the form." 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 #: core/validators.py:177
msgid "" msgid ""
"Upload a valid image. The file you uploaded was either not an image or a " "Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image." "corrupted image."
msgstr "" 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." "beschadigd."
#: core/validators.py:184 #: core/validators.py:184
#, python-format #, python-format
msgid "The URL %s does not point to a valid image." 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 #: core/validators.py:188
#, python-format #, python-format
@ -321,7 +316,7 @@ msgstr ""
#: core/validators.py:196 #: core/validators.py:196
#, python-format #, python-format
msgid "The URL %s does not point to a valid QuickTime video." 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 #: core/validators.py:200
msgid "A valid URL is required." msgid "A valid URL is required."
@ -349,18 +344,18 @@ msgstr "Ongeldige URL: %s"
#: core/validators.py:243 core/validators.py:245 #: core/validators.py:243 core/validators.py:245
#, python-format #, python-format
msgid "The URL %s is a broken link." 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 #: core/validators.py:251
msgid "Enter a valid U.S. state abbreviation." 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 #: core/validators.py:265
#, python-format #, python-format
msgid "Watch your mouth! The word %s is not allowed here." msgid "Watch your mouth! The word %s is not allowed here."
msgid_plural "Watch your mouth! The words %s are not allowed here." msgid_plural "Watch your mouth! The words %s are not allowed here."
msgstr[0] "Pas op uw taalgebruik! Gebruik van %s niet toegestaan." 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 #: core/validators.py:272
#, python-format #, python-format
@ -373,7 +368,7 @@ msgstr "Voer tenminste één veld in."
#: core/validators.py:300 core/validators.py:311 #: core/validators.py:300 core/validators.py:311
msgid "Please enter both fields or leave them both empty." 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 #: core/validators.py:318
#, python-format #, python-format
@ -450,7 +445,7 @@ msgstr "Zorg ervoor dat het bestand hoogstens %s bytes groot is."
#: core/validators.py:453 #: core/validators.py:453
msgid "The format for this field is wrong." 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 #: core/validators.py:468
msgid "This field is invalid." msgid "This field is invalid."
@ -536,7 +531,7 @@ msgid ""
"Your Web browser doesn't appear to have cookies enabled. Cookies are " "Your Web browser doesn't appear to have cookies enabled. Cookies are "
"required for logging in." "required for logging in."
msgstr "" msgstr ""
"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." "moeten cookies worden geaccepteerd."
#: contrib/auth/forms.py:59 contrib/admin/views/decorators.py:10 #: contrib/auth/forms.py:59 contrib/admin/views/decorators.py:10
@ -645,7 +640,7 @@ msgstr "supergebruiker status"
msgid "" msgid ""
"Designates that this user has all permissions without explicitly assigning " "Designates that this user has all permissions without explicitly assigning "
"them." "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 #: contrib/auth/models.py:98
msgid "last login" msgid "last login"
@ -685,7 +680,7 @@ msgstr "Rechten"
#: contrib/auth/models.py:113 #: contrib/auth/models.py:113
msgid "Important dates" msgid "Important dates"
msgstr "Belangrijke data" msgstr "Belangrijke datums"
#: contrib/auth/models.py:114 #: contrib/auth/models.py:114
msgid "Groups" msgid "Groups"
@ -783,7 +778,7 @@ msgid ""
"Please log in again, because your session has expired. Don't worry: Your " "Please log in again, because your session has expired. Don't worry: Your "
"submission has been saved." "submission has been saved."
msgstr "" msgstr ""
"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." "is opgeslagen."
#: contrib/admin/views/decorators.py:69 #: contrib/admin/views/decorators.py:69
@ -791,7 +786,7 @@ msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable " "Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again." "cookies, reload this page, and try again."
msgstr "" 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." "cookies aan in uw browser, laad deze pagina nogmaals en probeer het opnieuw."
#: contrib/admin/views/decorators.py:83 #: contrib/admin/views/decorators.py:83
@ -928,7 +923,7 @@ msgstr "Model %r niet gevonden in app %r"
#: contrib/admin/views/doc.py:183 #: contrib/admin/views/doc.py:183
#, python-format #, python-format
msgid "the related `%s.%s` object" 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:183 contrib/admin/views/doc.py:205
#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 #: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224
@ -938,7 +933,7 @@ msgstr "model:"
#: contrib/admin/views/doc.py:214 #: contrib/admin/views/doc.py:214
#, python-format #, python-format
msgid "related `%s.%s` objects" msgid "related `%s.%s` objects"
msgstr "related `%s.%s` objects" msgstr "de gerelateerde `%s.%s` objecten"
#: contrib/admin/views/doc.py:219 #: contrib/admin/views/doc.py:219
#, python-format #, python-format
@ -948,12 +943,12 @@ msgstr "alle %s"
#: contrib/admin/views/doc.py:224 #: contrib/admin/views/doc.py:224
#, python-format #, python-format
msgid "number of %s" msgid "number of %s"
msgstr "nummer van %s" msgstr "aantal %s"
#: contrib/admin/views/doc.py:229 #: contrib/admin/views/doc.py:229
#, python-format #, python-format
msgid "Fields on %s objects" 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:291 contrib/admin/views/doc.py:301
#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 #: 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:" "All of the following related items will be deleted:"
msgstr "" msgstr ""
"Weet u zeker dat u %(object_name)s \"%(escaped_object)s\" wilt verwijderen? Alle " "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 #: contrib/admin/templates/admin/delete_confirmation.html:26
msgid "Yes, I'm sure" 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:4
#: contrib/admin/templates/admin/404.html:8 #: contrib/admin/templates/admin/404.html:8
@ -1197,7 +1192,7 @@ msgstr "Opslaan en nieuw item"
#: contrib/admin/templates/admin/submit_line.html:6 #: contrib/admin/templates/admin/submit_line.html:6
msgid "Save and continue editing" msgid "Save and continue editing"
msgstr "Opslaan en bewerk opnieuw" msgstr "Opslaan en opnieuw bewerken"
#: contrib/admin/templates/admin/submit_line.html:7 #: contrib/admin/templates/admin/submit_line.html:7
msgid "Save" msgid "Save"
@ -1224,7 +1219,7 @@ msgstr "Wijzigen"
#: contrib/admin/templates/admin/index.html:44 #: contrib/admin/templates/admin/index.html:44
msgid "You don't have permission to edit anything." 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 #: contrib/admin/templates/admin/index.html:52
msgid "Recent Actions" msgid "Recent Actions"
@ -1380,7 +1375,7 @@ msgstr ""
"de bookmarklet vanuit elke pagina op de site worden gekozen. Let erop dat " "de bookmarklet vanuit elke pagina op de site worden gekozen. Let erop dat "
"het soms\n" "het soms\n"
"noodzakelijk is dat de computer van waaruit de pagina wordt bekeken intern " "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 " "(Raadpleeg uw systeembeheerder of uw computer zich op het interne netwerk "
"bevind).<p>\n" "bevind).<p>\n"
@ -1393,7 +1388,7 @@ msgid ""
"Jumps you from any page to the documentation for the view that generates " "Jumps you from any page to the documentation for the view that generates "
"that page." "that page."
msgstr "" msgstr ""
"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" "wordt door die pagina"
#: contrib/admin/templates/admin_doc/bookmarklets.html:22 #: contrib/admin/templates/admin_doc/bookmarklets.html:22
@ -1406,7 +1401,7 @@ msgid ""
"object." "object."
msgstr "" msgstr ""
"Toont de content-type en unieke ID voor pagina's die een enkel object " "Toont de content-type en unieke ID voor pagina's die een enkel object "
"voorsteld." "voorstellen."
#: contrib/admin/templates/admin_doc/bookmarklets.html:25 #: contrib/admin/templates/admin_doc/bookmarklets.html:25
msgid "Edit this object (current window)" msgid "Edit this object (current window)"
@ -1414,7 +1409,7 @@ msgstr "Bewerk dit object (huidig venster)"
#: contrib/admin/templates/admin_doc/bookmarklets.html:26 #: contrib/admin/templates/admin_doc/bookmarklets.html:26
msgid "Jumps to the admin page for pages that represent a single object." msgid "Jumps to the admin page for pages that represent a single object."
msgstr "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 #: contrib/admin/templates/admin_doc/bookmarklets.html:28
msgid "Edit this object (new window)" msgid "Edit this object (new window)"
@ -1527,7 +1522,7 @@ msgid ""
"password twice so we can verify you typed it in correctly." "password twice so we can verify you typed it in correctly."
msgstr "" msgstr ""
"Vanwege de beveiliging moet u uw oude en twee keer een nieuw " "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." "gemaakt."
#: contrib/admin/templates/registration/password_change_form.html:17 #: 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 " "Example: 'flatpages/contact_page.html'. If this isn't provided, the system "
"will use 'flatpages/default.html'." "will use 'flatpages/default.html'."
msgstr "" 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." "'flatpages/default.html' gebruikt."
#: contrib/flatpages/models.py:14 #: 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:196
#: contrib/comments/views/comments.py:286 #: contrib/comments/views/comments.py:286
msgid "Somebody tampered with the comment form (security violation)" 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:206
#: contrib/comments/views/comments.py:292 #: contrib/comments/views/comments.py:292

View File

@ -55,6 +55,12 @@ var POLISH_MAP = {
'Ź':'Z', 'Ż':'Z' 'Ź':'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() var ALL_DOWNCODE_MAPS=new Array()
ALL_DOWNCODE_MAPS[0]=LATIN_MAP ALL_DOWNCODE_MAPS[0]=LATIN_MAP
ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_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[5]=UKRAINIAN_MAP
ALL_DOWNCODE_MAPS[6]=CZECH_MAP ALL_DOWNCODE_MAPS[6]=CZECH_MAP
ALL_DOWNCODE_MAPS[7]=POLISH_MAP ALL_DOWNCODE_MAPS[7]=POLISH_MAP
ALL_DOWNCODE_MAPS[8]=LATVIAN_MAP
var Downcoder = new Object(); var Downcoder = new Object();
Downcoder.Initialize = function() Downcoder.Initialize = function()

View File

@ -12,6 +12,7 @@ from django.utils.safestring import mark_safe
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
EMPTY_VALUE = '(None)' EMPTY_VALUE = '(None)'
DISPLAY_SIZE = 100
class EasyModel(object): class EasyModel(object):
def __init__(self, site, model): def __init__(self, site, model):
@ -93,8 +94,8 @@ class EasyInstance(object):
def __unicode__(self): def __unicode__(self):
val = smart_unicode(self.instance) val = smart_unicode(self.instance)
if len(val) > 30: if len(val) > DISPLAY_SIZE:
return val[:30] + u'...' return val[:DISPLAY_SIZE] + u'...'
return val return val
def __str__(self): def __str__(self):

View File

@ -24,18 +24,20 @@ class ARPostalCodeField(RegexField):
See http://www.correoargentino.com.ar/consulta_cpa/home.php 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): def __init__(self, *args, **kwargs):
super(ARPostalCodeField, self).__init__(r'^\d{4}$|^[A-HJ-NP-Za-hj-np-z]\d{4}\D{3}$', super(ARPostalCodeField, self).__init__(r'^\d{4}$|^[A-HJ-NP-Za-hj-np-z]\d{4}\D{3}$',
min_length=4, max_length=8, min_length=4, max_length=8, *args, **kwargs)
error_message=ugettext("Enter a postal code in the format NNNN or ANNNNAAA."),
*args, **kwargs)
def clean(self, value): def clean(self, value):
value = super(ARPostalCodeField, self).clean(value) value = super(ARPostalCodeField, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return u'' return u''
if len(value) not in (4, 8): 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: if len(value) == 8:
return u'%s%s%s' % (value[0].upper(), value[1:5], value[5:].upper()) return u'%s%s%s' % (value[0].upper(), value[1:5], value[5:].upper())
return value return value
@ -44,6 +46,11 @@ class ARDNIField(CharField):
""" """
A field that validates `Documento Nacional de Identidad´ (DNI) numbers. 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): def __init__(self, *args, **kwargs):
super(ARDNIField, self).__init__(max_length=10, min_length=7, *args, super(ARDNIField, self).__init__(max_length=10, min_length=7, *args,
**kwargs) **kwargs)
@ -58,10 +65,9 @@ class ARDNIField(CharField):
if not value.isdigit(): if not value.isdigit():
value = value.replace('.', '') value = value.replace('.', '')
if not value.isdigit(): 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): if len(value) not in (7, 8):
raise ValidationError( raise ValidationError(self.error_messages['max_digits'])
ugettext("This field requires 7 or 8 digits."))
return value return value
@ -70,9 +76,13 @@ class ARCUITField(RegexField):
This field validates a CUIT (Código Único de Identificación Tributaria). A This field validates a CUIT (Código Único de Identificación Tributaria). A
CUIT is of the form XX-XXXXXXXX-V. The last digit is a check digit. 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): def __init__(self, *args, **kwargs):
super(ARCUITField, self).__init__(r'^\d{2}-?\d{8}-?\d$', 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) *args, **kwargs)
def clean(self, value): def clean(self, value):
@ -85,7 +95,7 @@ class ARCUITField(RegexField):
return u'' return u''
value, cd = self._canon(value) value, cd = self._canon(value)
if self._calc_cd(value) != cd: if self._calc_cd(value) != cd:
raise ValidationError(ugettext("Invalid CUIT.")) raise ValidationError(self.error_messages['checksum'])
return self._format(value, cd) return self._format(value, cd)
def _canon(self, cuit): def _canon(self, cuit):

View File

@ -12,14 +12,20 @@ PHONE_DIGITS_RE = re.compile(r'^(\d{10})$')
class AUPostCodeField(RegexField): class AUPostCodeField(RegexField):
"""Australian post code field.""" """Australian post code field."""
default_error_messages = {
'invalid': ugettext('Enter a 4 digit post code.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(AUPostCodeField, self).__init__(r'^\d{4}$', super(AUPostCodeField, self).__init__(r'^\d{4}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=ugettext('Enter a 4 digit post code.'),
*args, **kwargs)
class AUPhoneNumberField(Field): class AUPhoneNumberField(Field):
"""Australian phone number field.""" """Australian phone number field."""
default_error_messages = {
'invalid': u'Phone numbers must contain 10 digits.',
}
def clean(self, value): def clean(self, value):
""" """
Validate a phone number. Strips parentheses, whitespace and hyphens. Validate a phone number. Strips parentheses, whitespace and hyphens.
@ -31,7 +37,7 @@ class AUPhoneNumberField(Field):
phone_match = PHONE_DIGITS_RE.search(value) phone_match = PHONE_DIGITS_RE.search(value)
if phone_match: if phone_match:
return u'%s' % phone_match.group(1) 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): class AUStateSelect(Select):
""" """

View File

@ -17,13 +17,19 @@ except NameError:
phone_digits_re = re.compile(r'^(\d{2})[-\.]?(\d{4})[-\.]?(\d{4})$') phone_digits_re = re.compile(r'^(\d{2})[-\.]?(\d{4})[-\.]?(\d{4})$')
class BRZipCodeField(RegexField): class BRZipCodeField(RegexField):
default_error_messages = {
'invalid': _('Enter a zip code in the format XXXXX-XXX.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(BRZipCodeField, self).__init__(r'^\d{5}-\d{3}$', super(BRZipCodeField, self).__init__(r'^\d{5}-\d{3}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=_('Enter a zip code in the format XXXXX-XXX.'),
*args, **kwargs)
class BRPhoneNumberField(Field): class BRPhoneNumberField(Field):
default_error_messages = {
'invalid': _('Phone numbers must be in XX-XXXX-XXXX format.'),
}
def clean(self, value): def clean(self, value):
super(BRPhoneNumberField, self).clean(value) super(BRPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
@ -32,7 +38,7 @@ class BRPhoneNumberField(Field):
m = phone_digits_re.search(value) m = phone_digits_re.search(value)
if m: if m:
return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) 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): class BRStateSelect(Select):
""" """
@ -48,6 +54,9 @@ class BRStateChoiceField(Field):
A choice field that uses a list of Brazilian states as its choices. A choice field that uses a list of Brazilian states as its choices.
""" """
widget = Select 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, def __init__(self, required=True, widget=None, label=None,
initial=None, help_text=None): initial=None, help_text=None):
@ -65,9 +74,7 @@ class BRStateChoiceField(Field):
return value return value
valid_values = set([smart_unicode(k) for k, v in self.widget.choices]) valid_values = set([smart_unicode(k) for k, v in self.widget.choices])
if value not in valid_values: if value not in valid_values:
raise ValidationError(_(u'Select a valid brazilian state.' raise ValidationError(self.error_messages['invalid'])
u' That state is not one'
u' of the available states.'))
return value return value
def DV_maker(v): def DV_maker(v):
@ -83,6 +90,12 @@ class BRCPFField(CharField):
More information: More information:
http://en.wikipedia.org/wiki/Cadastro_de_Pessoas_F%C3%ADsicas 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): def __init__(self, *args, **kwargs):
super(BRCPFField, self).__init__(max_length=14, min_length=11, *args, **kwargs) super(BRCPFField, self).__init__(max_length=14, min_length=11, *args, **kwargs)
@ -100,9 +113,9 @@ class BRCPFField(CharField):
try: try:
int(value) int(value)
except ValueError: except ValueError:
raise ValidationError(_("This field requires only numbers.")) raise ValidationError(self.error_messages['digits_only'])
if len(value) != 11: 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:] orig_dv = value[-2:]
new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(10, 1, -1))]) 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) new_2dv = DV_maker(new_2dv % 11)
value = value[:-1] + str(new_2dv) value = value[:-1] + str(new_2dv)
if value[-2:] != orig_dv: if value[-2:] != orig_dv:
raise ValidationError(_("Invalid CPF number.")) raise ValidationError(self.error_messages['invalid'])
return orig_value return orig_value
class BRCNPJField(Field): 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): def clean(self, value):
""" """
Value can be either a string in the format XX.XXX.XXX/XXXX-XX or a Value can be either a string in the format XX.XXX.XXX/XXXX-XX or a
@ -131,10 +150,9 @@ class BRCNPJField(Field):
try: try:
int(value) int(value)
except ValueError: except ValueError:
raise ValidationError("This field requires only numbers.") raise ValidationError(self.error_messages['digits_only'])
if len(value) != 14: if len(value) != 14:
raise ValidationError( raise ValidationError(self.error_messages['max_digits'])
_("This field requires at least 14 digits"))
orig_dv = value[-2:] orig_dv = value[-2:]
new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(5, 1, -1) + range(9, 1, -1))]) 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) new_2dv = DV_maker(new_2dv % 11)
value = value[:-1] + str(new_2dv) value = value[:-1] + str(new_2dv)
if value[-2:] != orig_dv: if value[-2:] != orig_dv:
raise ValidationError(_("Invalid CNPJ number.")) raise ValidationError(self.error_messages['invalid'])
return orig_value return orig_value

View File

@ -1,37 +1,43 @@
""" """
Canada-specific Form helpers Canada-specific Form helpers
""" """
from django.newforms import ValidationError from django.newforms import ValidationError
from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
from django.newforms.util import smart_unicode from django.newforms.util import smart_unicode
from django.utils.translation import gettext, ugettext from django.utils.translation import gettext, ugettext
import re import re
phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$') phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$") sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$")
class CAPostalCodeField(RegexField): class CAPostalCodeField(RegexField):
"""Canadian postal code field.""" """Canadian postal code field."""
def __init__(self, *args, **kwargs): default_error_messages = {
super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXYZ]\d[A-Z] \d[A-Z]\d$', 'invalid': gettext(u'Enter a postal code in the format XXX XXX.'),
max_length=None, min_length=None, }
error_message=gettext(u'Enter a postal code in the format XXX XXX.'),
*args, **kwargs) def __init__(self, *args, **kwargs):
super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXYZ]\d[A-Z] \d[A-Z]\d$',
class CAPhoneNumberField(Field): max_length=None, min_length=None, *args, **kwargs)
"""Canadian phone number field."""
def clean(self, value): class CAPhoneNumberField(Field):
"""Validate a phone number. """Canadian phone number field."""
""" default_error_messages = {
super(CAPhoneNumberField, self).clean(value) '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: if value in EMPTY_VALUES:
return u'' return u''
value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
m = phone_digits_re.search(value) m = phone_digits_re.search(value)
if m: if m:
return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) 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): class CAProvinceField(Field):
""" """
@ -39,6 +45,10 @@ class CAProvinceField(Field):
It normalizes the input to the standard two-leter postal service It normalizes the input to the standard two-leter postal service
abbreviation for the given province. abbreviation for the given province.
""" """
default_error_messages = {
'invalid': u'Enter a Canadian province or territory.',
}
def clean(self, value): def clean(self, value):
from ca_provinces import PROVINCES_NORMALIZED from ca_provinces import PROVINCES_NORMALIZED
super(CAProvinceField, self).clean(value) super(CAProvinceField, self).clean(value)
@ -53,45 +63,49 @@ class CAProvinceField(Field):
return PROVINCES_NORMALIZED[value.strip().lower()].decode('ascii') return PROVINCES_NORMALIZED[value.strip().lower()].decode('ascii')
except KeyError: except KeyError:
pass pass
raise ValidationError(u'Enter a Canadian province or territory.') raise ValidationError(self.error_messages['invalid'])
class CAProvinceSelect(Select): class CAProvinceSelect(Select):
""" """
A Select widget that uses a list of Canadian provinces and A Select widget that uses a list of Canadian provinces and
territories as its choices. territories as its choices.
""" """
def __init__(self, attrs=None): def __init__(self, attrs=None):
from ca_provinces import PROVINCE_CHOICES # relative import from ca_provinces import PROVINCE_CHOICES # relative import
super(CAProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES) super(CAProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
class CASocialInsuranceNumberField(Field): class CASocialInsuranceNumberField(Field):
""" """
A Canadian Social Insurance Number (SIN). A Canadian Social Insurance Number (SIN).
Checks the following rules to determine whether the number is valid: 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" * Passes the check digit process "Luhn Algorithm"
See: http://en.wikipedia.org/wiki/Social_Insurance_Number 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): def clean(self, value):
super(CASocialInsuranceNumberField, self).clean(value) super(CASocialInsuranceNumberField, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return u'' return u''
msg = ugettext('Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.')
match = re.match(sin_re, value) match = re.match(sin_re, value)
if not match: if not match:
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
number = u'%s-%s-%s' % (match.group(1), match.group(2), match.group(3)) 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)) check_number = u'%s%s%s' % (match.group(1), match.group(2), match.group(3))
if not self.luhn_checksum_is_valid(check_number): if not self.luhn_checksum_is_valid(check_number):
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
return number return number
def luhn_checksum_is_valid(self, 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 See: http://en.wikipedia.org/wiki/Luhn_algorithm
""" """
@ -109,4 +123,4 @@ class CASocialInsuranceNumberField(Field):
sum = sum + digit sum = sum + digit
return ( (sum % 10) == 0 ) return ( (sum % 10) == 0 )

View File

@ -12,11 +12,13 @@ id_re = re.compile(r"^(?P<idnumber>\w{8})(?P<pos9>(\d{1}|<))(?P<checksum>\d{1})$
phone_digits_re = re.compile(r'^0([1-9]{1})\d{8}$') phone_digits_re = re.compile(r'^0([1-9]{1})\d{8}$')
class CHZipCodeField(RegexField): class CHZipCodeField(RegexField):
default_error_messages = {
'invalid': ugettext('Enter a zip code in the format XXXX.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(CHZipCodeField, self).__init__(r'^\d{4}$', super(CHZipCodeField, self).__init__(r'^\d{4}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=ugettext('Enter a zip code in the format XXXX.'),
*args, **kwargs)
class CHPhoneNumberField(Field): 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' and '0XXXXXXXXX' validate but are corrected to
'0XX XXX XX XX'. '0XX XXX XX XX'.
""" """
default_error_messages = {
'invalid': 'Phone numbers must be in 0XX XXX XX XX format.',
}
def clean(self, value): def clean(self, value):
super(CHPhoneNumberField, self).clean(value) super(CHPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
@ -33,7 +39,7 @@ class CHPhoneNumberField(Field):
m = phone_digits_re.search(value) m = phone_digits_re.search(value)
if m: if m:
return u'%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10]) 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): class CHStateSelect(Select):
""" """
@ -54,6 +60,10 @@ class CHIdentityCardNumberField(Field):
Algorithm is documented at http://adi.kousz.ch/artikel/IDCHE.htm 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): def has_valid_checksum(self, number):
given_number, given_checksum = number[:-1], number[-1] given_number, given_checksum = number[:-1], number[-1]
new_number = given_number new_number = given_number
@ -87,23 +97,22 @@ class CHIdentityCardNumberField(Field):
def clean(self, value): def clean(self, value):
super(CHIdentityCardNumberField, self).clean(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: if value in EMPTY_VALUES:
return u'' return u''
match = re.match(id_re, value) match = re.match(id_re, value)
if not match: 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'] idnumber, pos9, checksum = match.groupdict()['idnumber'], match.groupdict()['pos9'], match.groupdict()['checksum']
if idnumber == '00000000' or \ if idnumber == '00000000' or \
idnumber == 'A0000000': idnumber == 'A0000000':
raise ValidationError(error_msg) raise ValidationError(self.error_messages['invalid'])
all_digits = "%s%s%s" % (idnumber, pos9, checksum) all_digits = "%s%s%s" % (idnumber, pos9, checksum)
if not self.has_valid_checksum(all_digits): 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) return u'%s%s%s' % (idnumber, pos9, checksum)

View File

@ -25,16 +25,21 @@ class CLRutField(RegexField):
Samples for testing are available from Samples for testing are available from
https://palena.sii.cl/cvc/dte/ee_empresas_emisoras.html 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): def __init__(self, *args, **kwargs):
if 'strict' in kwargs: if 'strict' in kwargs:
del kwargs['strict'] del kwargs['strict']
super(CLRutField, self).__init__(r'^(\d{1,2}\.)?\d{3}\.\d{3}-[\dkK]$', 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.'), error_message=self.default_error_messages['strict'], *args, **kwargs)
*args, **kwargs)
else: else:
# In non-strict mode, accept RUTs that validate but do not exist in # In non-strict mode, accept RUTs that validate but do not exist in
# the real world. # 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): def clean(self, value):
""" """
@ -47,7 +52,7 @@ class CLRutField(RegexField):
if self._algorithm(rut) == verificador: if self._algorithm(rut) == verificador:
return self._format(rut, verificador) return self._format(rut, verificador)
else: else:
raise ValidationError(u'The Chilean RUT is not valid.') raise ValidationError(self.error_messages['checksum'])
def _algorithm(self, rut): def _algorithm(self, rut):
""" """

View File

@ -10,11 +10,12 @@ import re
id_re = re.compile(r"^(?P<residence>\d{10})(?P<origin>\w{1,3})[-\ ]?(?P<birthday>\d{7})[-\ ]?(?P<validity>\d{7})[-\ ]?(?P<checksum>\d{1})$") id_re = re.compile(r"^(?P<residence>\d{10})(?P<origin>\w{1,3})[-\ ]?(?P<birthday>\d{7})[-\ ]?(?P<validity>\d{7})[-\ ]?(?P<checksum>\d{1})$")
class DEZipCodeField(RegexField): class DEZipCodeField(RegexField):
default_error_messages = {
'invalid': ugettext('Enter a zip code in the format XXXXX.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(DEZipCodeField, self).__init__(r'^\d{5}$', super(DEZipCodeField, self).__init__(r'^\d{5}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=ugettext('Enter a zip code in the format XXXXX.'),
*args, **kwargs)
class DEStateSelect(Select): class DEStateSelect(Select):
""" """
@ -36,6 +37,10 @@ class DEIdentityCardNumberField(Field):
Algorithm is documented at http://de.wikipedia.org/wiki/Personalausweis 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): def has_valid_checksum(self, number):
given_number, given_checksum = number[:-1], number[-1] given_number, given_checksum = number[:-1], number[-1]
calculated_checksum = 0 calculated_checksum = 0
@ -57,23 +62,22 @@ class DEIdentityCardNumberField(Field):
def clean(self, value): def clean(self, value):
super(DEIdentityCardNumberField, self).clean(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: if value in EMPTY_VALUES:
return u'' return u''
match = re.match(id_re, value) match = re.match(id_re, value)
if not match: if not match:
raise ValidationError(error_msg) raise ValidationError(self.error_messages['invalid'])
gd = match.groupdict() gd = match.groupdict()
residence, origin = gd['residence'], gd['origin'] residence, origin = gd['residence'], gd['origin']
birthday, validity, checksum = gd['birthday'], gd['validity'], gd['checksum'] birthday, validity, checksum = gd['birthday'], gd['validity'], gd['checksum']
if residence == '0000000000' or birthday == '0000000' or validity == '0000000': 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) 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 \ 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): 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) return u'%s%s-%s-%s-%s' % (residence, origin, birthday, validity, checksum)

View File

@ -15,12 +15,14 @@ class ESPostalCodeField(RegexField):
Spanish postal code is a five digits string, with two first digits Spanish postal code is a five digits string, with two first digits
between 01 and 52, assigned to provinces code. 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): def __init__(self, *args, **kwargs):
super(ESPostalCodeField, self).__init__( super(ESPostalCodeField, self).__init__(
r'^(0[1-9]|[1-4][0-9]|5[0-2])\d{3}$', r'^(0[1-9]|[1-4][0-9]|5[0-2])\d{3}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=_('Enter a valid postal code in the range and format 01XXX - 52XXX.'),
*args, **kwargs)
class ESPhoneNumberField(RegexField): class ESPhoneNumberField(RegexField):
""" """
@ -33,11 +35,13 @@ class ESPhoneNumberField(RegexField):
TODO: accept and strip characters like dot, hyphen... in phone number 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): def __init__(self, *args, **kwargs):
super(ESPhoneNumberField, self).__init__(r'^(6|8|9)\d{8}$', super(ESPhoneNumberField, self).__init__(r'^(6|8|9)\d{8}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=_('Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or 9XXXXXXXX.'),
*args, **kwargs)
class ESIdentityCardNumberField(RegexField): class ESIdentityCardNumberField(RegexField):
""" """
@ -58,19 +62,23 @@ class ESIdentityCardNumberField(RegexField):
public, and different authors have different opinions on which ones allows public, and different authors have different opinions on which ones allows
letters, so both validations are assumed true for all types. 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): def __init__(self, only_nif=False, *args, **kwargs):
self.only_nif = only_nif self.only_nif = only_nif
self.nif_control = 'TRWAGMYFPDXBNJZSQVHLCKE' self.nif_control = 'TRWAGMYFPDXBNJZSQVHLCKE'
self.cif_control = 'JABCDEFGHI' self.cif_control = 'JABCDEFGHI'
self.cif_types = 'ABCDEFGHKLMNPQS' self.cif_types = 'ABCDEFGHKLMNPQS'
self.nie_types = 'XT' 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()), 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, 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) *args, **kwargs)
def clean(self, value): def clean(self, value):
@ -88,13 +96,13 @@ class ESIdentityCardNumberField(RegexField):
if letter2 == nif_get_checksum(number): if letter2 == nif_get_checksum(number):
return value return value
else: else:
raise ValidationError, _('Invalid checksum for NIF.') raise ValidationError, self.error_messages['invalid_nif']
elif letter1 in self.nie_types and letter2: elif letter1 in self.nie_types and letter2:
# NIE # NIE
if letter2 == nif_get_checksum(number): if letter2 == nif_get_checksum(number):
return value return value
else: 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]: elif not self.only_nif and letter1 in self.cif_types and len(number) in [7, 8]:
# CIF # CIF
if not letter2: if not letter2:
@ -103,9 +111,9 @@ class ESIdentityCardNumberField(RegexField):
if letter2 in [checksum, self.cif_control[checksum]]: if letter2 in [checksum, self.cif_control[checksum]]:
return value return value
else: else:
raise ValidationError, _('Invalid checksum for CIF.') raise ValidationError, self.error_messages['invalid_cif']
else: else:
raise ValidationError, _('Please enter a valid %s.' % self.id_types) raise ValidationError, self.error_messages['invalid']
class ESCCCField(RegexField): class ESCCCField(RegexField):
""" """
@ -130,11 +138,14 @@ class ESCCCField(RegexField):
TODO: allow IBAN validation too 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): def __init__(self, *args, **kwargs):
super(ESCCCField, self).__init__(r'^\d{4}[ -]?\d{4}[ -]?\d{2}[ -]?\d{10}$', super(ESCCCField, self).__init__(r'^\d{4}[ -]?\d{4}[ -]?\d{2}[ -]?\d{10}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=_('Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX.'),
*args, **kwargs)
def clean(self, value): def clean(self, value):
super(ESCCCField, self).clean(value) super(ESCCCField, self).clean(value)
@ -147,7 +158,7 @@ class ESCCCField(RegexField):
if get_checksum('00' + entity + office) + get_checksum(account) == checksum: if get_checksum('00' + entity + office) + get_checksum(account) == checksum:
return value return value
else: else:
raise ValidationError, _('Invalid checksum for bank account number.') raise ValidationError, self.error_messages['checksum']
class ESRegionSelect(Select): class ESRegionSelect(Select):
""" """

View File

@ -8,11 +8,12 @@ from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
from django.utils.translation import ugettext from django.utils.translation import ugettext
class FIZipCodeField(RegexField): class FIZipCodeField(RegexField):
default_error_messages = {
'invalid': ugettext('Enter a zip code in the format XXXXX.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(FIZipCodeField, self).__init__(r'^\d{5}$', super(FIZipCodeField, self).__init__(r'^\d{5}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=ugettext('Enter a zip code in the format XXXXX.'),
*args, **kwargs)
class FIMunicipalitySelect(Select): class FIMunicipalitySelect(Select):
""" """
@ -23,6 +24,10 @@ class FIMunicipalitySelect(Select):
super(FIMunicipalitySelect, self).__init__(attrs, choices=MUNICIPALITY_CHOICES) super(FIMunicipalitySelect, self).__init__(attrs, choices=MUNICIPALITY_CHOICES)
class FISocialSecurityNumber(Field): class FISocialSecurityNumber(Field):
default_error_messages = {
'invalid': ugettext('Enter a valid Finnish social security number.'),
}
def clean(self, value): def clean(self, value):
super(FISocialSecurityNumber, self).clean(value) super(FISocialSecurityNumber, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
@ -37,9 +42,9 @@ class FISocialSecurityNumber(Field):
(?P<serial>(\d{3})) (?P<serial>(\d{3}))
(?P<checksum>[%s])$""" % checkmarks, value, re.VERBOSE | re.IGNORECASE) (?P<checksum>[%s])$""" % checkmarks, value, re.VERBOSE | re.IGNORECASE)
if not result: if not result:
raise ValidationError(ugettext('Enter a valid Finnish social security number.')) raise ValidationError(self.error_messages['invalid'])
gd = result.groupdict() gd = result.groupdict()
checksum = int(gd['date'] + gd['serial']) checksum = int(gd['date'] + gd['serial'])
if checkmarks[checksum % len(checkmarks)] == gd['checksum'].upper(): if checkmarks[checksum % len(checkmarks)] == gd['checksum'].upper():
return u'%s' % value.upper() return u'%s' % value.upper()
raise ValidationError(ugettext('Enter a valid Finnish social security number.')) raise ValidationError(self.error_messages['invalid'])

View File

@ -11,11 +11,13 @@ import re
phone_digits_re = re.compile(r'^0\d(\s|\.)?(\d{2}(\s|\.)?){3}\d{2}$') phone_digits_re = re.compile(r'^0\d(\s|\.)?(\d{2}(\s|\.)?){3}\d{2}$')
class FRZipCodeField(RegexField): class FRZipCodeField(RegexField):
default_error_messages = {
'invalid': ugettext('Enter a zip code in the format XXXXX.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(FRZipCodeField, self).__init__(r'^\d{5}$', super(FRZipCodeField, self).__init__(r'^\d{5}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=ugettext('Enter a zip code in the format XXXXX.'),
*args, **kwargs)
class FRPhoneNumberField(Field): 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' and '0XXXXXXXXX' validate but are corrected to
'0X XX XX XX XX'. '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): def clean(self, value):
super(FRPhoneNumberField, self).clean(value) super(FRPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
@ -32,7 +38,7 @@ class FRPhoneNumberField(Field):
m = phone_digits_re.search(value) m = phone_digits_re.search(value)
if m: if m:
return u'%s %s %s %s %s' % (value[0:2], value[2:4], value[4:6], value[6:8], value[8:10]) 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): class FRDepartmentSelect(Select):
""" """

View File

@ -10,11 +10,13 @@ import re
class INZipCodeField(RegexField): class INZipCodeField(RegexField):
default_error_messages = {
'invalid': gettext(u'Enter a zip code in the format XXXXXXX.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(INZipCodeField, self).__init__(r'^\d{6}$', super(INZipCodeField, self).__init__(r'^\d{6}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=gettext(u'Enter a zip code in the format XXXXXXX.'),
*args, **kwargs)
class INStateField(Field): class INStateField(Field):
""" """
@ -22,6 +24,10 @@ class INStateField(Field):
abbreviation. It normalizes the input to the standard two-letter vehicle abbreviation. It normalizes the input to the standard two-letter vehicle
registration abbreviation for the given state or union territory registration abbreviation for the given state or union territory
""" """
default_error_messages = {
'invalid': u'Enter a Indian state or territory.',
}
def clean(self, value): def clean(self, value):
from in_states import STATES_NORMALIZED from in_states import STATES_NORMALIZED
super(INStateField, self).clean(value) super(INStateField, self).clean(value)
@ -36,7 +42,7 @@ class INStateField(Field):
return smart_unicode(STATES_NORMALIZED[value.strip().lower()]) return smart_unicode(STATES_NORMALIZED[value.strip().lower()])
except KeyError: except KeyError:
pass pass
raise ValidationError(u'Enter a Indian state or territory.') raise ValidationError(self.error_messages['invalid'])
class INStateSelect(Select): class INStateSelect(Select):
""" """

View File

@ -13,10 +13,14 @@ class ISIdNumberField(RegexField):
Icelandic identification number (kennitala). This is a number every citizen Icelandic identification number (kennitala). This is a number every citizen
of Iceland has. 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): 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 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): def clean(self, value):
value = super(ISIdNumberField, self).clean(value) value = super(ISIdNumberField, self).clean(value)
@ -28,7 +32,7 @@ class ISIdNumberField(RegexField):
if self._validate(value): if self._validate(value):
return self._format(value) return self._format(value)
else: else:
raise ValidationError(ugettext(u'The Icelandic identification number is not valid.')) raise ValidationError(self.error_messages['checksum'])
def _canonify(self, value): def _canonify(self, value):
""" """

View File

@ -10,11 +10,12 @@ from django.contrib.localflavor.it.util import ssn_check_digit, vat_number_check
import re import re
class ITZipCodeField(RegexField): class ITZipCodeField(RegexField):
default_error_messages = {
'invalid': ugettext('Enter a valid zip code.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ITZipCodeField, self).__init__(r'^\d{5}$', super(ITZipCodeField, self).__init__(r'^\d{5}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=ugettext('Enter a valid zip code.'),
*args, **kwargs)
class ITRegionSelect(Select): class ITRegionSelect(Select):
""" """
@ -38,11 +39,13 @@ class ITSocialSecurityNumberField(RegexField):
For reference see http://www.agenziaentrate.it/ and search for For reference see http://www.agenziaentrate.it/ and search for
'Informazioni sulla codificazione delle persone fisiche'. '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): def __init__(self, *args, **kwargs):
super(ITSocialSecurityNumberField, self).__init__(r'^\w{3}\s*\w{3}\s*\w{5}\s*\w{5}$', 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, max_length=None, min_length=None, *args, **kwargs)
*args, **kwargs)
def clean(self, value): def clean(self, value):
value = super(ITSocialSecurityNumberField, self).clean(value) value = super(ITSocialSecurityNumberField, self).clean(value)
@ -52,26 +55,29 @@ class ITSocialSecurityNumberField(RegexField):
try: try:
check_digit = ssn_check_digit(value) check_digit = ssn_check_digit(value)
except ValueError: except ValueError:
raise ValidationError(self.err_msg) raise ValidationError(self.error_messages['invalid'])
if not value[15] == check_digit: if not value[15] == check_digit:
raise ValidationError(self.err_msg) raise ValidationError(self.error_messages['invalid'])
return value return value
class ITVatNumberField(Field): class ITVatNumberField(Field):
""" """
A form field that validates Italian VAT numbers (partita IVA). 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): def clean(self, value):
value = super(ITVatNumberField, self).clean(value) value = super(ITVatNumberField, self).clean(value)
if value == u'': if value == u'':
return value return value
err_msg = ugettext(u'Enter a valid VAT number.')
try: try:
vat_number = int(value) vat_number = int(value)
except ValueError: except ValueError:
raise ValidationError(err_msg) raise ValidationError(self.error_messages['invalid'])
vat_number = str(vat_number).zfill(11) vat_number = str(vat_number).zfill(11)
check_digit = vat_number_check_digit(vat_number[0:10]) check_digit = vat_number_check_digit(vat_number[0:10])
if not vat_number[10] == check_digit: if not vat_number[10] == check_digit:
raise ValidationError(err_msg) raise ValidationError(self.error_messages['invalid'])
return smart_unicode(vat_number) return smart_unicode(vat_number)

View File

@ -15,11 +15,13 @@ class JPPostalCodeField(RegexField):
Accepts 7 digits, with or without a hyphen. 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): def __init__(self, *args, **kwargs):
super(JPPostalCodeField, self).__init__(r'^\d{3}-\d{4}$|^\d{7}$', super(JPPostalCodeField, self).__init__(r'^\d{3}-\d{4}$|^\d{7}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=ugettext('Enter a postal code in the format XXXXXXX or XXX-XXXX.'),
*args, **kwargs)
def clean(self, value): def clean(self, value):
""" """

View File

@ -17,24 +17,27 @@ class NLZipCodeField(Field):
""" """
A Dutch postal code field. A Dutch postal code field.
""" """
default_error_messages = {
'invalid': _('Enter a valid postal code'),
}
def clean(self, value): def clean(self, value):
super(NLZipCodeField, self).clean(value) super(NLZipCodeField, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return u'' return u''
msg = _('Enter a valid postal code')
value = value.strip().upper().replace(' ', '') value = value.strip().upper().replace(' ', '')
if not pc_re.search(value): if not pc_re.search(value):
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
if int(value[:4]) < 1000: if int(value[:4]) < 1000:
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
return u'%s %s' % (value[:4], value[4:]) return u'%s %s' % (value[:4], value[4:])
class NLProvinceSelect(Select): 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. choices.
""" """
def __init__(self, attrs=None): def __init__(self, attrs=None):
@ -45,48 +48,53 @@ class NLPhoneNumberField(Field):
""" """
A Dutch telephone number field. A Dutch telephone number field.
""" """
default_error_messages = {
'invalid': _('Enter a valid phone number'),
}
def clean(self, value): def clean(self, value):
super(NLPhoneNumberField, self).clean(value) super(NLPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return u'' return u''
msg = _('Enter a valid phone number')
phone_nr = re.sub('[\-\s\(\)]', '', smart_unicode(value)) phone_nr = re.sub('[\-\s\(\)]', '', smart_unicode(value))
if len(phone_nr) == 10 and numeric_re.search(phone_nr): if len(phone_nr) == 10 and numeric_re.search(phone_nr):
return value return value
if phone_nr[:3] == '+31' and len(phone_nr) == 12 and \ if phone_nr[:3] == '+31' and len(phone_nr) == 12 and \
numeric_re.search(phone_nr[3:]): numeric_re.search(phone_nr[3:]):
return value return value
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
class NLSoFiNumberField(Field): class NLSoFiNumberField(Field):
""" """
A Dutch social security number (SoFi/BSN) field. A Dutch social security number (SoFi/BSN) field.
http://nl.wikipedia.org/wiki/Sofinummer http://nl.wikipedia.org/wiki/Sofinummer
""" """
default_error_messages = {
'invalid': _('Enter a valid SoFi number'),
}
def clean(self, value): def clean(self, value):
super(NLSoFiNumberField, self).clean(value) super(NLSoFiNumberField, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return u'' return u''
msg = _('Enter a valid SoFi number')
if not sofi_re.search(value): if not sofi_re.search(value):
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
if int(value) == 0: if int(value) == 0:
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
checksum = 0 checksum = 0
for i in range(9, 1, -1): for i in range(9, 1, -1):
checksum += int(value[9-i]) * i checksum += int(value[9-i]) * i
checksum -= int(value[-1]) checksum -= int(value[-1])
if checksum % 11 != 0: if checksum % 11 != 0:
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
return value return value

View File

@ -8,11 +8,13 @@ from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
from django.utils.translation import ugettext from django.utils.translation import ugettext
class NOZipCodeField(RegexField): class NOZipCodeField(RegexField):
default_error_messages = {
'invalid': ugettext('Enter a zip code in the format XXXX.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(NOZipCodeField, self).__init__(r'^\d{4}$', super(NOZipCodeField, self).__init__(r'^\d{4}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=ugettext('Enter a zip code in the format XXXX.'),
*args, **kwargs)
class NOMunicipalitySelect(Select): class NOMunicipalitySelect(Select):
""" """
@ -27,14 +29,17 @@ class NOSocialSecurityNumber(Field):
""" """
Algorithm is documented at http://no.wikipedia.org/wiki/Personnummer 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): def clean(self, value):
super(NOSocialSecurityNumber, self).clean(value) super(NOSocialSecurityNumber, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return u'' return u''
msg = ugettext(u'Enter a valid Norwegian social security number.')
if not re.match(r'^\d{11}$', value): if not re.match(r'^\d{11}$', value):
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
day = int(value[:2]) day = int(value[:2])
month = int(value[2:4]) month = int(value[2:4])
@ -52,7 +57,7 @@ class NOSocialSecurityNumber(Field):
if 900 <= inum < 1000 and year2 > 39: if 900 <= inum < 1000 and year2 > 39:
self.birthday = datetime.date(1900+year2, month, day) self.birthday = datetime.date(1900+year2, month, day)
except ValueError: except ValueError:
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
sexnum = int(value[8]) sexnum = int(value[8])
if sexnum % 2 == 0: if sexnum % 2 == 0:
@ -68,9 +73,9 @@ class NOSocialSecurityNumber(Field):
return sum([(a * b) for (a, b) in zip(aval, bval)]) return sum([(a * b) for (a, b) in zip(aval, bval)])
if multiply_reduce(digits, weight_1) % 11 != 0: 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: if multiply_reduce(digits, weight_2) % 11 != 0:
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
return value return value

View File

@ -19,6 +19,11 @@ class PEDNIField(CharField):
""" """
A field that validates `Documento Nacional de IdentidadŽ (DNI) numbers. 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): def __init__(self, *args, **kwargs):
super(PEDNIField, self).__init__(max_length=8, min_length=8, *args, super(PEDNIField, self).__init__(max_length=8, min_length=8, *args,
**kwargs) **kwargs)
@ -31,9 +36,9 @@ class PEDNIField(CharField):
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return u'' return u''
if not value.isdigit(): if not value.isdigit():
raise ValidationError(ugettext("This field requires only numbers.")) raise ValidationError(self.error_messages['invalid'])
if len(value) != 8: if len(value) != 8:
raise ValidationError(ugettext("This field requires 8 digits.")) raise ValidationError(self.error_messages['max_digits'])
return value return value
@ -42,6 +47,11 @@ class PERUCField(RegexField):
This field validates a RUC (Registro Unico de Contribuyentes). A RUC is of This field validates a RUC (Registro Unico de Contribuyentes). A RUC is of
the form XXXXXXXXXXX. 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): def __init__(self, *args, **kwargs):
super(PERUCField, self).__init__(max_length=11, min_length=11, *args, super(PERUCField, self).__init__(max_length=11, min_length=11, *args,
**kwargs) **kwargs)
@ -54,8 +64,8 @@ class PERUCField(RegexField):
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return u'' return u''
if not value.isdigit(): if not value.isdigit():
raise ValidationError(ugettext("This field requires only numbers.")) raise ValidationError(self.error_messages['invalid'])
if len(value) != 11: if len(value) != 11:
raise ValidationError(ugettext("This field requires 11 digits.")) raise ValidationError(self.error_messages['max_digits'])
return value return value

View File

@ -35,16 +35,19 @@ class PLNationalIdentificationNumberField(RegexField):
The algorithm is documented at http://en.wikipedia.org/wiki/PESEL. 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): def __init__(self, *args, **kwargs):
super(PLNationalIdentificationNumberField, self).__init__(r'^\d{11}$', super(PLNationalIdentificationNumberField, self).__init__(r'^\d{11}$',
max_length=None, min_length=None, error_message=_(u'National Identification Number consists of 11 digits.'), max_length=None, min_length=None, *args, **kwargs)
*args, **kwargs)
def clean(self,value): def clean(self,value):
super(PLNationalIdentificationNumberField, self).clean(value) super(PLNationalIdentificationNumberField, self).clean(value)
if not self.has_valid_checksum(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 return u'%s' % value
def has_valid_checksum(self, number): def has_valid_checksum(self, number):
@ -65,17 +68,20 @@ class PLTaxNumberField(RegexField):
Checksum algorithm based on documentation at Checksum algorithm based on documentation at
http://wipos.p.lodz.pl/zylla/ut/nip-rego.html 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): 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}$', 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, max_length=None, min_length=None, *args, **kwargs)
error_message=_(u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX.'), *args, **kwargs)
def clean(self,value): def clean(self,value):
super(PLTaxNumberField, self).clean(value) super(PLTaxNumberField, self).clean(value)
value = re.sub("[-]", "", value) value = re.sub("[-]", "", value)
if not self.has_valid_checksum(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 return u'%s' % value
def has_valid_checksum(self, number): 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 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): def __init__(self, *args, **kwargs):
super(PLNationalBusinessRegisterField, self).__init__(r'^\d{7,9}$', 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.'), max_length=None, min_length=None, *args, **kwargs)
*args, **kwargs)
def clean(self,value): def clean(self,value):
super(PLNationalBusinessRegisterField, self).clean(value) super(PLNationalBusinessRegisterField, self).clean(value)
if not self.has_valid_checksum(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 return u'%s' % value
def has_valid_checksum(self, number): def has_valid_checksum(self, number):
@ -142,9 +152,10 @@ class PLPostalCodeField(RegexField):
A form field that validates as Polish postal code. A form field that validates as Polish postal code.
Valid code is XX-XXX where X is digit. 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): def __init__(self, *args, **kwargs):
super(PLPostalCodeField, self).__init__(r'^\d{2}-\d{3}$', super(PLPostalCodeField, self).__init__(r'^\d{2}-\d{3}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=_(u'Enter a postal code in the format XX-XXX.'),
*args, **kwargs)

View File

@ -26,11 +26,13 @@ class SKPostalCodeField(RegexField):
A form field that validates its input as Slovak postal code. A form field that validates its input as Slovak postal code.
Valid form is XXXXX or XXX XX, where X represents integer. 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): def __init__(self, *args, **kwargs):
super(SKPostalCodeField, self).__init__(r'^\d{5}$|^\d{3} \d{2}$', super(SKPostalCodeField, self).__init__(r'^\d{5}$|^\d{3} \d{2}$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=ugettext(u'Enter a postal code in the format XXXXX or XXX XX.'),
*args, **kwargs)
def clean(self, value): def clean(self, value):
""" """

View File

@ -2,21 +2,39 @@
UK-specific Form helpers 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 from django.utils.translation import ugettext
class UKPostcodeField(RegexField): class UKPostcodeField(CharField):
""" """
A form field that validates its input is a UK postcode. A form field that validates its input is a UK postcode.
The regular expression used is sourced from the schema for British Standard 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 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): default_error_messages = {
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})$', 'invalid': ugettext(u'Enter a valid postcode.'),
max_length=None, min_length=None, }
error_message=ugettext(u'Enter a postcode. A space is required between the two postcode parts.'), outcode_pattern = '[A-PR-UWYZ]([0-9]{1,2}|([A-HIK-Y][0-9](|[0-9]|[ABEHMNPRVWXY]))|[0-9][A-HJKSTUW])'
*args, **kwargs) 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): class UKCountySelect(Select):
""" """

View File

@ -12,13 +12,19 @@ phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
ssn_re = re.compile(r"^(?P<area>\d{3})[-\ ]?(?P<group>\d{2})[-\ ]?(?P<serial>\d{4})$") ssn_re = re.compile(r"^(?P<area>\d{3})[-\ ]?(?P<group>\d{2})[-\ ]?(?P<serial>\d{4})$")
class USZipCodeField(RegexField): class USZipCodeField(RegexField):
default_error_messages = {
'invalid': ugettext('Enter a zip code in the format XXXXX or XXXXX-XXXX.'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(USZipCodeField, self).__init__(r'^\d{5}(?:-\d{4})?$', super(USZipCodeField, self).__init__(r'^\d{5}(?:-\d{4})?$',
max_length=None, min_length=None, max_length=None, min_length=None, *args, **kwargs)
error_message=ugettext('Enter a zip code in the format XXXXX or XXXXX-XXXX.'),
*args, **kwargs)
class USPhoneNumberField(Field): class USPhoneNumberField(Field):
default_error_messages = {
'invalid': u'Phone numbers must be in XXX-XXX-XXXX format.',
}
def clean(self, value): def clean(self, value):
super(USPhoneNumberField, self).clean(value) super(USPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
@ -27,7 +33,7 @@ class USPhoneNumberField(Field):
m = phone_digits_re.search(value) m = phone_digits_re.search(value)
if m: if m:
return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) 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): class USSocialSecurityNumberField(Field):
""" """
@ -44,28 +50,31 @@ class USSocialSecurityNumberField(Field):
promotional use or distribution (e.g., the Woolworth's number or the promotional use or distribution (e.g., the Woolworth's number or the
1962 promotional number). 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): def clean(self, value):
super(USSocialSecurityNumberField, self).clean(value) super(USSocialSecurityNumberField, self).clean(value)
if value in EMPTY_VALUES: if value in EMPTY_VALUES:
return u'' return u''
msg = ugettext('Enter a valid U.S. Social Security number in XXX-XX-XXXX format.')
match = re.match(ssn_re, value) match = re.match(ssn_re, value)
if not match: if not match:
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
area, group, serial = match.groupdict()['area'], match.groupdict()['group'], match.groupdict()['serial'] area, group, serial = match.groupdict()['area'], match.groupdict()['group'], match.groupdict()['serial']
# First pass: no blocks of all zeroes. # First pass: no blocks of all zeroes.
if area == '000' or \ if area == '000' or \
group == '00' or \ group == '00' or \
serial == '0000': serial == '0000':
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
# Second pass: promotional and otherwise permanently invalid numbers. # Second pass: promotional and otherwise permanently invalid numbers.
if area == '666' or \ if area == '666' or \
(area == '987' and group == '65' and 4320 <= int(serial) <= 4329) or \ (area == '987' and group == '65' and 4320 <= int(serial) <= 4329) or \
value == '078-05-1120' or \ value == '078-05-1120' or \
value == '219-09-9999': value == '219-09-9999':
raise ValidationError(msg) raise ValidationError(self.error_messages['invalid'])
return u'%s-%s-%s' % (area, group, serial) return u'%s-%s-%s' % (area, group, serial)
class USStateField(Field): class USStateField(Field):
@ -74,6 +83,10 @@ class USStateField(Field):
It normalizes the input to the standard two-leter postal service It normalizes the input to the standard two-leter postal service
abbreviation for the given state. abbreviation for the given state.
""" """
default_error_messages = {
'invalid': u'Enter a U.S. state or territory.',
}
def clean(self, value): def clean(self, value):
from us_states import STATES_NORMALIZED from us_states import STATES_NORMALIZED
super(USStateField, self).clean(value) super(USStateField, self).clean(value)
@ -88,7 +101,7 @@ class USStateField(Field):
return STATES_NORMALIZED[value.strip().lower()].decode('ascii') return STATES_NORMALIZED[value.strip().lower()].decode('ascii')
except KeyError: except KeyError:
pass pass
raise ValidationError(u'Enter a U.S. state or territory.') raise ValidationError(self.error_messages['invalid'])
class USStateSelect(Select): class USStateSelect(Select):
""" """

View File

@ -16,10 +16,9 @@ class ZAIDField(Field):
using the Luhn checksum, and uses a simlistic (read: not entirely accurate) using the Luhn checksum, and uses a simlistic (read: not entirely accurate)
check for the birthdate check for the birthdate
""" """
default_error_messages = {
def __init__(self, *args, **kwargs): 'invalid': _(u'Enter a valid South African ID number'),
super(ZAIDField, self).__init__() }
self.error_message = _(u'Enter a valid South African ID number')
def clean(self, value): def clean(self, value):
# strip spaces and dashes # strip spaces and dashes
@ -31,9 +30,9 @@ class ZAIDField(Field):
return u'' return u''
match = re.match(id_re, value) match = re.match(id_re, value)
if not match: if not match:
raise ValidationError(self.error_message) raise ValidationError(self.error_messages['invalid'])
g = match.groupdict() g = match.groupdict()
@ -43,15 +42,18 @@ class ZAIDField(Field):
# There is no way to guess the century of a ZA ID number # 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'])) d = date(int(g['yy']) + 2000, int(g['mm']), int(g['dd']))
except ValueError: except ValueError:
raise ValidationError(self.error_message) raise ValidationError(self.error_messages['invalid'])
if not luhn(value): if not luhn(value):
raise ValidationError(self.error_message) raise ValidationError(self.error_messages['invalid'])
return value return value
class ZAPostCodeField(RegexField): class ZAPostCodeField(RegexField):
default_error_messages = {
'invalid': _(u'Enter a valid South African postal code'),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ZAPostCodeField, self).__init__(r'^\d{4}$', super(ZAPostCodeField, self).__init__(r'^\d{4}$',
max_length=None, min_length=None, max_length=None, min_length=None)
error_message=_(u'Enter a valid South African postal code'))

View File

@ -67,42 +67,32 @@ def make_msgid(idstring=None):
class BadHeaderError(ValueError): class BadHeaderError(ValueError):
pass 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): class SafeMIMEText(MIMEText):
def __setitem__(self, name, val): def __setitem__(self, name, val):
"Forbids multi-line headers, to prevent header injection." name, val = forbid_multi_line_headers(name, val)
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)
MIMEText.__setitem__(self, name, val) MIMEText.__setitem__(self, name, val)
class SafeMIMEMultipart(MIMEMultipart): class SafeMIMEMultipart(MIMEMultipart):
def __setitem__(self, name, val): def __setitem__(self, name, val):
"Forbids multi-line headers, to prevent header injection." name, val = forbid_multi_line_headers(name, val)
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)
MIMEMultipart.__setitem__(self, name, val) MIMEMultipart.__setitem__(self, name, val)
class SMTPConnection(object): class SMTPConnection(object):
@ -209,8 +199,14 @@ class EmailMessage(object):
bytestrings). The SafeMIMEText class will handle any necessary encoding bytestrings). The SafeMIMEText class will handle any necessary encoding
conversions. conversions.
""" """
self.to = to or [] if to:
self.bcc = bcc or [] 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.from_email = from_email or settings.DEFAULT_FROM_EMAIL
self.subject = subject self.subject = subject
self.body = body self.body = body

View File

@ -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.'), 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', make_option('--pythonpath',
help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'), 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 = '' help = ''
args = '' args = ''

View File

@ -1,11 +1,12 @@
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.core import serializers
from optparse import make_option from optparse import make_option
class Command(BaseCommand): class Command(BaseCommand):
option_list = BaseCommand.option_list + ( option_list = BaseCommand.option_list + (
make_option('--format', default='json', dest='format', 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', make_option('--indent', default=None, dest='indent', type='int',
help='Specifies the indent level to use when pretty-printing output'), help='Specifies the indent level to use when pretty-printing output'),
) )
@ -14,10 +15,10 @@ class Command(BaseCommand):
def handle(self, *app_labels, **options): def handle(self, *app_labels, **options):
from django.db.models import get_app, get_apps, get_models from django.db.models import get_app, get_apps, get_models
from django.core import serializers
format = options.get('format', 'json') format = options.get('format', 'json')
indent = options.get('indent', None) indent = options.get('indent', None)
show_traceback = options.get('traceback', False)
if len(app_labels) == 0: if len(app_labels) == 0:
app_list = get_apps() app_list = get_apps()
@ -26,6 +27,9 @@ class Command(BaseCommand):
# Check that the serialization format exists; this is a shortcut to # Check that the serialization format exists; this is a shortcut to
# avoid collating all the objects and _then_ failing. # 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: try:
serializers.get_serializer(format) serializers.get_serializer(format)
except KeyError: except KeyError:
@ -34,8 +38,10 @@ class Command(BaseCommand):
objects = [] objects = []
for app in app_list: for app in app_list:
for model in get_models(app): for model in get_models(app):
objects.extend(model.objects.all()) objects.extend(model._default_manager.all())
try: try:
return serializers.serialize(format, objects, indent=indent) return serializers.serialize(format, objects, indent=indent)
except Exception, e: except Exception, e:
if show_traceback:
raise
raise CommandError("Unable to serialize database: %s" % e) raise CommandError("Unable to serialize database: %s" % e)

View File

@ -27,6 +27,7 @@ class Command(BaseCommand):
self.style = no_style() self.style = no_style()
verbosity = int(options.get('verbosity', 1)) verbosity = int(options.get('verbosity', 1))
show_traceback = options.get('traceback', False)
# Keep a count of the installed objects and fixtures # Keep a count of the installed objects and fixtures
count = [0, 0] count = [0, 0]
@ -50,10 +51,10 @@ class Command(BaseCommand):
parts = fixture_label.split('.') parts = fixture_label.split('.')
if len(parts) == 1: if len(parts) == 1:
fixture_name = fixture_label fixture_name = fixture_label
formats = serializers.get_serializer_formats() formats = serializers.get_public_serializer_formats()
else: else:
fixture_name, format = '.'.join(parts[:-1]), parts[-1] 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] formats = [format]
else: else:
formats = [] formats = []
@ -98,11 +99,13 @@ class Command(BaseCommand):
label_found = True label_found = True
except Exception, e: except Exception, e:
fixture.close() fixture.close()
transaction.rollback()
transaction.leave_transaction_management()
if show_traceback:
raise
sys.stderr.write( sys.stderr.write(
self.style.ERROR("Problem installing fixture '%s': %s\n" % self.style.ERROR("Problem installing fixture '%s': %s\n" %
(full_path, str(e)))) (full_path, str(e))))
transaction.rollback()
transaction.leave_transaction_management()
return return
fixture.close() fixture.close()
except: except:

View File

@ -116,6 +116,7 @@ def sql_delete(app, style):
"Returns a list of the DROP TABLE SQL statements for the given app." "Returns a list of the DROP TABLE SQL statements for the given app."
from django.db import connection, models, get_introspection_module from django.db import connection, models, get_introspection_module
from django.db.backends.util import truncate_name from django.db.backends.util import truncate_name
from django.contrib.contenttypes import generic
introspection = get_introspection_module() introspection = get_introspection_module()
# This should work even if a connection isn't available # This should work even if a connection isn't available
@ -179,6 +180,8 @@ def sql_delete(app, style):
for model in app_models: for model in app_models:
opts = model._meta opts = model._meta
for f in opts.many_to_many: for f in opts.many_to_many:
if isinstance(f.rel, generic.GenericRel):
continue
if cursor and table_name_converter(f.m2m_db_table()) in table_names: if cursor and table_name_converter(f.m2m_db_table()) in table_names:
output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
style.SQL_TABLE(qn(f.m2m_db_table())))) style.SQL_TABLE(qn(f.m2m_db_table()))))

View File

@ -53,6 +53,11 @@ def get_serializer_formats():
_load_serializers() _load_serializers()
return _serializers.keys() 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): def get_deserializer(format):
if not _serializers: if not _serializers:
_load_serializers() _load_serializers()

View File

@ -22,6 +22,10 @@ class Serializer(object):
Abstract serializer base class. 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): def serialize(self, queryset, **options):
""" """
Serialize a queryset. Serialize a queryset.

View File

@ -20,6 +20,8 @@ class Serializer(PythonSerializer):
""" """
Convert a queryset to JSON. Convert a queryset to JSON.
""" """
internal_use_only = False
def end_serialization(self): def end_serialization(self):
self.options.pop('stream', None) self.options.pop('stream', None)
self.options.pop('fields', None) self.options.pop('fields', None)

View File

@ -13,7 +13,9 @@ class Serializer(base.Serializer):
""" """
Serializes a QuerySet to basic Python objects. Serializes a QuerySet to basic Python objects.
""" """
internal_use_only = True
def start_serialization(self): def start_serialization(self):
self._current = None self._current = None
self.objects = [] self.objects = []

View File

@ -19,6 +19,8 @@ class Serializer(PythonSerializer):
Convert a queryset to YAML. Convert a queryset to YAML.
""" """
internal_use_only = False
def handle_field(self, obj, field): def handle_field(self, obj, field):
# A nasty special case: base YAML doesn't support serialization of time # A nasty special case: base YAML doesn't support serialization of time
# types (as opposed to dates or datetimes, which it does support). Since # types (as opposed to dates or datetimes, which it does support). Since

View File

@ -398,8 +398,20 @@ class ServerHandler(object):
self.bytes_sent += len(data) self.bytes_sent += len(data)
# XXX check Content-Length and truncate if too many bytes written? # XXX check Content-Length and truncate if too many bytes written?
self._write(data)
self._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): def sendfile(self):
"""Platform-specific file transmission """Platform-specific file transmission

View File

@ -413,6 +413,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
return self.connection is not None return self.connection is not None
def _cursor(self, settings): def _cursor(self, settings):
cursor = None
if not self._valid_connection(): if not self._valid_connection():
if len(settings.DATABASE_HOST.strip()) == 0: if len(settings.DATABASE_HOST.strip()) == 0:
settings.DATABASE_HOST = 'localhost' settings.DATABASE_HOST = 'localhost'
@ -422,16 +423,25 @@ class DatabaseWrapper(BaseDatabaseWrapper):
else: else:
conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME) conn_string = "%s/%s@%s" % (settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME)
self.connection = Database.connect(conn_string, **self.options) self.connection = Database.connect(conn_string, **self.options)
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: try:
self.oracle_version = int(self.connection.version.split('.')[0]) self.oracle_version = int(self.connection.version.split('.')[0])
except ValueError: except ValueError:
pass 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. # Default arraysize of 1 is highly sub-optimal.
cursor.arraysize = 100 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 return cursor
class FormatStylePlaceholderCursor(Database.Cursor): class FormatStylePlaceholderCursor(Database.Cursor):

View File

@ -714,7 +714,7 @@ class EmailField(CharField):
class FileField(Field): class FileField(Field):
def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs):
self.upload_to = upload_to 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) Field.__init__(self, verbose_name, name, **kwargs)
def get_db_prep_save(self, value): def get_db_prep_save(self, value):
@ -910,6 +910,11 @@ class NullBooleanField(Field):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [oldforms.NullBooleanField] return [oldforms.NullBooleanField]
def formfield(self, **kwargs):
defaults = {'form_class': forms.NullBooleanField}
defaults.update(kwargs)
return super(NullBooleanField, self).formfield(**defaults)
class PhoneNumberField(IntegerField): class PhoneNumberField(IntegerField):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [oldforms.PhoneNumberField] return [oldforms.PhoneNumberField]
@ -926,11 +931,11 @@ class PhoneNumberField(IntegerField):
class PositiveIntegerField(IntegerField): class PositiveIntegerField(IntegerField):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
return [oldforms.PositiveIntegerField] return [oldforms.PositiveIntegerField]
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = {'min_value': 0} defaults = {'min_value': 0}
defaults.update(kwargs) defaults.update(kwargs)
return super(PositiveIntegerField, self).formfield(**defaults) return super(PositiveIntegerField, self).formfield(**defaults)
class PositiveSmallIntegerField(IntegerField): class PositiveSmallIntegerField(IntegerField):
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
@ -939,7 +944,7 @@ class PositiveSmallIntegerField(IntegerField):
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = {'min_value': 0} defaults = {'min_value': 0}
defaults.update(kwargs) defaults.update(kwargs)
return super(PositiveSmallIntegerField, self).formfield(**defaults) return super(PositiveSmallIntegerField, self).formfield(**defaults)
class SlugField(CharField): class SlugField(CharField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -277,7 +277,20 @@ class HttpResponse(object):
for key, value in self._headers.values()]) \ for key, value in self._headers.values()]) \
+ '\n\n' + self.content + '\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): def __setitem__(self, header, value):
header, value = self._convert_to_ascii(header, value)
self._headers[header.lower()] = (header, value) self._headers[header.lower()] = (header, value)
def __delitem__(self, header): def __delitem__(self, header):

View File

@ -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 determining the formfield for a given database field. It's a callable that
takes a database Field instance and returns a form Field instance. takes a database Field instance and returns a form Field instance.
""" """
warn("form_for_model is deprecated, use ModelForm instead.", warn("form_for_model is deprecated. Use ModelForm instead.",
PendingDeprecationWarning, PendingDeprecationWarning, stacklevel=3)
stacklevel=3)
opts = model._meta opts = model._meta
field_list = [] field_list = []
for f in opts.fields + opts.many_to_many: 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 takes a database Field instance, plus **kwargs, and returns a form Field
instance with the given kwargs (i.e. 'initial'). instance with the given kwargs (i.e. 'initial').
""" """
warn("form_for_instance is deprecated, use ModelForm instead.", warn("form_for_instance is deprecated. Use ModelForm instead.",
PendingDeprecationWarning, PendingDeprecationWarning, stacklevel=3)
stacklevel=3)
model = instance.__class__ model = instance.__class__
opts = model._meta opts = model._meta
field_list = [] 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 Returns a dict containing the data in ``instance`` suitable for passing as
a Form's ``initial`` keyword argument. a Form's ``initial`` keyword argument.
``fields`` is an optional list of field names. If provided, only the named ``fields`` is an optional list of field names. If provided, only the named
fields will be included in the returned dict. fields will be included in the returned dict.
``exclude`` is an optional list of field names. If provided, the named ``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 fields will be excluded from the returned dict, even if they are listed in
the ``fields`` argument. 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`` is an optional list of field names. If provided, only the named
fields will be included in the returned fields. fields will be included in the returned fields.
``exclude`` is an optional list of field names. If provided, the named ``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 fields will be excluded from the returned fields, even if they are listed
in the ``fields`` argument. in the ``fields`` argument.
@ -214,9 +212,8 @@ class ModelFormOptions(object):
self.exclude = getattr(options, 'exclude', None) self.exclude = getattr(options, 'exclude', None)
class ModelFormMetaclass(type): class ModelFormMetaclass(type):
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs,
# TODO: no way to specify formfield_callback yet, do we need one, or formfield_callback=lambda f: f.formfield()):
# should it be a special case for the admin?
fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] fields = [(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)) 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) base_model = getattr(base_opts, 'model', None)
if base_model and base_model is not opts.model: if base_model and base_model is not opts.model:
raise ImproperlyConfigured('%s defines a different model than its parent.' % name) 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 # fields declared in base classes override fields from the model
model_fields.update(declared_fields) model_fields.update(declared_fields)
attrs['base_fields'] = model_fields attrs['base_fields'] = model_fields

View File

@ -154,15 +154,12 @@ class StringOrigin(Origin):
class Template(object): class Template(object):
def __init__(self, template_string, origin=None, name='<Unknown Template>'): def __init__(self, template_string, origin=None, name='<Unknown Template>'):
"Compilation stage"
try: try:
template_string = smart_unicode(template_string) template_string = smart_unicode(template_string)
except UnicodeDecodeError: except UnicodeDecodeError:
raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.") 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) 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.nodelist = compile_string(template_string, origin)
self.name = name self.name = name
@ -177,13 +174,18 @@ class Template(object):
def compile_string(template_string, origin): def compile_string(template_string, origin):
"Compiles template_string into NodeList ready for rendering" "Compiles template_string into NodeList ready for rendering"
lexer = lexer_factory(template_string, origin) if settings.TEMPLATE_DEBUG:
parser = parser_factory(lexer.tokenize()) 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() return parser.parse()
class Token(object): class Token(object):
def __init__(self, token_type, contents): 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 self.token_type, self.contents = token_type, contents
def __str__(self): def __str__(self):
@ -200,7 +202,7 @@ class Lexer(object):
self.origin = origin self.origin = origin
def tokenize(self): 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 in_tag = False
result = [] result = []
for bit in tag_re.split(self.template_string): for bit in tag_re.split(self.template_string):
@ -226,30 +228,6 @@ class Lexer(object):
token = Token(TOKEN_TEXT, token_string) token = Token(TOKEN_TEXT, token_string)
return token 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): class Parser(object):
def __init__(self, tokens): def __init__(self, tokens):
self.tokens = tokens self.tokens = tokens
@ -319,17 +297,17 @@ class Parser(object):
def exit_command(self): def exit_command(self):
pass pass
def error(self, token, msg ): def error(self, token, msg):
return TemplateSyntaxError(msg) return TemplateSyntaxError(msg)
def empty_variable(self, token): 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): 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): 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): def unclosed_block_tag(self, parse_until):
raise self.error(None, "Unclosed tags: %s " % ', '.join(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: if filter_name in self.filters:
return self.filters[filter_name] return self.filters[filter_name]
else: else:
raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name 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)
class TokenParser(object): class TokenParser(object):
""" """
@ -426,7 +354,7 @@ class TokenParser(object):
def top(self): def top(self):
"Overload this method to do the actual parsing and return the result." "Overload this method to do the actual parsing and return the result."
raise NotImplemented raise NotImplementedError()
def more(self): def more(self):
"Returns True if there is more stuff in the tag." "Returns True if there is more stuff in the tag."
@ -435,7 +363,7 @@ class TokenParser(object):
def back(self): def back(self):
"Undoes the last microparser. Use this for lookahead and backtracking." "Undoes the last microparser. Use this for lookahead and backtracking."
if not len(self.backout): 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() self.pointer = self.backout.pop()
def tag(self): def tag(self):
@ -443,7 +371,7 @@ class TokenParser(object):
subject = self.subject subject = self.subject
i = self.pointer i = self.pointer
if i >= len(subject): 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 p = i
while i < len(subject) and subject[i] not in (' ', '\t'): while i < len(subject) and subject[i] not in (' ', '\t'):
i += 1 i += 1
@ -459,14 +387,14 @@ class TokenParser(object):
subject = self.subject subject = self.subject
i = self.pointer i = self.pointer
if i >= len(subject): 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 ('"', "'"): if subject[i] in ('"', "'"):
p = i p = i
i += 1 i += 1
while i < len(subject) and subject[i] != subject[p]: while i < len(subject) and subject[i] != subject[p]:
i += 1 i += 1
if i >= len(subject): 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 i += 1
res = subject[p:i] res = subject[p:i]
while i < len(subject) and subject[i] in (' ', '\t'): while i < len(subject) and subject[i] in (' ', '\t'):
@ -483,7 +411,7 @@ class TokenParser(object):
while i < len(subject) and subject[i] != c: while i < len(subject) and subject[i] != c:
i += 1 i += 1
if i >= len(subject): 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 i += 1
s = subject[p:i] s = subject[p:i]
while i < len(subject) and subject[i] in (' ', '\t'): while i < len(subject) and subject[i] in (' ', '\t'):
@ -542,8 +470,8 @@ class FilterExpression(object):
for match in matches: for match in matches:
start = match.start() start = match.start()
if upto != start: if upto != start:
raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s" % \ raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s" % \
(token[:upto], token[upto:start], token[start:]) (token[:upto], token[upto:start], token[start:]))
if var == None: if var == None:
var, constant, i18n_constant = match.group("var", "constant", "i18n_constant") var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
if i18n_constant: if i18n_constant:
@ -552,9 +480,9 @@ class FilterExpression(object):
var = '"%s"' % constant.replace(r'\"', '"') var = '"%s"' % constant.replace(r'\"', '"')
upto = match.end() upto = match.end()
if var == None: 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] == '_': 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: else:
filter_name = match.group("filter_name") filter_name = match.group("filter_name")
args = [] args = []
@ -570,7 +498,7 @@ class FilterExpression(object):
filters.append( (filter_func,args)) filters.append( (filter_func,args))
upto = match.end() upto = match.end()
if upto != len(token): 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.filters = filters
self.var = Variable(var) self.var = Variable(var)
@ -627,7 +555,7 @@ class FilterExpression(object):
provided.pop(0) provided.pop(0)
except IndexError: except IndexError:
# Not enough # 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 can be overridden.
defaults = defaults and list(defaults) or [] defaults = defaults and list(defaults) or []
@ -636,7 +564,7 @@ class FilterExpression(object):
defaults.pop(0) defaults.pop(0)
except IndexError: except IndexError:
# Too many. # 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 return True
args_check = staticmethod(args_check) args_check = staticmethod(args_check)
@ -816,22 +744,6 @@ class NodeList(list):
def render_node(self, node, context): def render_node(self, node, context):
return node.render(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): class TextNode(Node):
def __init__(self, s): def __init__(self, s):
self.s = s self.s = s
@ -861,21 +773,6 @@ class VariableNode(Node):
else: else:
return force_unicode(output) 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): def generic_tag_compiler(params, defaults, name, node_class, parser, token):
"Returns a template.Node subclass." "Returns a template.Node subclass."
bits = token.split_contents()[1:] 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) message = "%s takes %s arguments" % (name, bmin)
else: else:
message = "%s takes between %s and %s arguments" % (name, bmin, bmax) message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
raise TemplateSyntaxError, message raise TemplateSyntaxError(message)
return node_class(bits) return node_class(bits)
class Library(object): class Library(object):
@ -913,7 +810,7 @@ class Library(object):
self.tags[name] = compile_function self.tags[name] = compile_function
return compile_function return compile_function
else: 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): def tag_function(self,func):
self.tags[getattr(func, "_decorated_function", func).__name__] = func self.tags[getattr(func, "_decorated_function", func).__name__] = func
@ -937,7 +834,7 @@ class Library(object):
self.filters[name] = filter_func self.filters[name] = filter_func
return filter_func return filter_func
else: 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): def filter_function(self, func):
self.filters[getattr(func, "_decorated_function", func).__name__] = func self.filters[getattr(func, "_decorated_function", func).__name__] = func
@ -966,7 +863,7 @@ class Library(object):
if params[0] == 'context': if params[0] == 'context':
params = params[1:] params = params[1:]
else: 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): class InclusionNode(Node):
def __init__(self, vars_to_resolve): def __init__(self, vars_to_resolve):
@ -1003,12 +900,12 @@ def get_library(module_name):
try: try:
mod = __import__(module_name, {}, {}, ['']) mod = __import__(module_name, {}, {}, [''])
except ImportError, e: 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: try:
lib = mod.register lib = mod.register
libraries[module_name] = lib libraries[module_name] = lib
except AttributeError: 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 return lib
def add_to_builtins(module_name): def add_to_builtins(module_name):

View File

@ -9,7 +9,6 @@ class ContextPopException(Exception):
class Context(object): class Context(object):
"A stack container for variable context" "A stack container for variable context"
def __init__(self, dict_=None, autoescape=True): def __init__(self, dict_=None, autoescape=True):
dict_ = dict_ or {} dict_ = dict_ or {}
self.dicts = [dict_] self.dicts = [dict_]
@ -78,11 +77,11 @@ def get_standard_processors():
try: try:
mod = __import__(module, {}, {}, [attr]) mod = __import__(module, {}, {}, [attr])
except ImportError, e: 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: try:
func = getattr(mod, attr) func = getattr(mod, attr)
except AttributeError: 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) processors.append(func)
_standard_context_processors = tuple(processors) _standard_context_processors = tuple(processors)
return _standard_context_processors return _standard_context_processors
@ -102,4 +101,3 @@ class RequestContext(Context):
processors = tuple(processors) processors = tuple(processors)
for processor in get_standard_processors() + processors: for processor in get_standard_processors() + processors:
self.update(processor(request)) self.update(processor(request))

97
django/template/debug.py Normal file
View File

@ -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

View File

@ -254,7 +254,7 @@ urlize.is_safe=True
urlize.needs_autoescape = True urlize.needs_autoescape = True
urlize = stringfilter(urlize) urlize = stringfilter(urlize)
def urlizetrunc(value, limit): def urlizetrunc(value, limit, autoescape=None):
""" """
Converts URLs into clickable links, truncating URLs to the given character Converts URLs into clickable links, truncating URLs to the given character
limit, and adding 'rel=nofollow' attribute to discourage spamming. limit, and adding 'rel=nofollow' attribute to discourage spamming.
@ -262,8 +262,10 @@ def urlizetrunc(value, limit):
Argument: Length to truncate URLs to. Argument: Length to truncate URLs to.
""" """
from django.utils.html import urlize 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.is_safe = True
urlizetrunc.needs_autoescape = True
urlizetrunc = stringfilter(urlizetrunc) urlizetrunc = stringfilter(urlizetrunc)
def wordcount(value): def wordcount(value):

View File

@ -84,19 +84,16 @@ class FirstOfNode(Node):
return u'' return u''
class ForNode(Node): 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.loopvars, self.sequence = loopvars, sequence
self.reversed = reversed self.is_reversed = is_reversed
self.nodelist_loop = nodelist_loop self.nodelist_loop = nodelist_loop
def __repr__(self): def __repr__(self):
if self.reversed: reversed_text = self.is_reversed and ' reversed' or ''
reversed = ' reversed'
else:
reversed = ''
return "<For Node: for %s in %s, tail_len: %d%s>" % \ return "<For Node: for %s in %s, tail_len: %d%s>" % \
(', '.join(self.loopvars), self.sequence, len(self.nodelist_loop), (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
reversed) reversed_text)
def __iter__(self): def __iter__(self):
for node in self.nodelist_loop: for node in self.nodelist_loop:
@ -125,22 +122,23 @@ class ForNode(Node):
if not hasattr(values, '__len__'): if not hasattr(values, '__len__'):
values = list(values) values = list(values)
len_values = len(values) len_values = len(values)
if self.reversed: if self.is_reversed:
values = reversed(values) values = reversed(values)
unpack = len(self.loopvars) > 1 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): for i, item in enumerate(values):
context['forloop'] = { # Shortcuts for current loop iteration number.
# Shortcuts for current loop iteration number. loop_dict['counter0'] = i
'counter0': i, loop_dict['counter'] = i+1
'counter': i+1, # Reverse counter iteration numbers.
# Reverse counter iteration numbers. loop_dict['revcounter'] = len_values - i
'revcounter': len_values - i, loop_dict['revcounter0'] = len_values - i - 1
'revcounter0': len_values - i - 1, # Boolean values designating first and last times through loop.
# Boolean values designating first and last times through loop. loop_dict['first'] = (i == 0)
'first': (i == 0), loop_dict['last'] = (i == len_values - 1)
'last': (i == len_values - 1),
'parentloop': parentloop,
}
if unpack: if unpack:
# If there are multiple loop variables, unpack the item into # If there are multiple loop variables, unpack the item into
# them. # them.
@ -619,8 +617,8 @@ def do_for(parser, token):
raise TemplateSyntaxError("'for' statements should have at least four" raise TemplateSyntaxError("'for' statements should have at least four"
" words: %s" % token.contents) " words: %s" % token.contents)
reversed = bits[-1] == 'reversed' is_reversed = bits[-1] == 'reversed'
in_index = reversed and -3 or -2 in_index = is_reversed and -3 or -2
if bits[in_index] != 'in': if bits[in_index] != 'in':
raise TemplateSyntaxError("'for' statements should use the format" raise TemplateSyntaxError("'for' statements should use the format"
" 'for x in y': %s" % token.contents) " 'for x in y': %s" % token.contents)
@ -634,7 +632,7 @@ def do_for(parser, token):
sequence = parser.compile_filter(bits[in_index+1]) sequence = parser.compile_filter(bits[in_index+1])
nodelist_loop = parser.parse(('endfor',)) nodelist_loop = parser.parse(('endfor',))
parser.delete_first_token() 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) do_for = register.tag("for", do_for)
def do_ifequal(parser, token, negate): def do_ifequal(parser, token, negate):
@ -814,7 +812,7 @@ def ssi(parser, token):
Outputs the contents of a given file into the page. Outputs the contents of a given file into the page.
Like a simple "include" tag, the ``ssi`` tag includes the contents 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:: in the current page::
{% ssi /home/html/ljworld.com/includes/right_generic.html %} {% 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 %} {% regroup people|dictsort:"gender" by gender as grouped %}
""" """
firstbits = token.contents.split(None, 3) bits = token.contents.split()
if len(firstbits) != 4: if len(bits) != 6:
raise TemplateSyntaxError, "'regroup' tag takes five arguments" raise TemplateSyntaxError, "'regroup' tag takes five arguments"
target = parser.compile_filter(firstbits[1]) target = parser.compile_filter(bits[1])
if firstbits[2] != 'by': if bits[2] != 'by':
raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
lastbits_reversed = firstbits[3][::-1].split(None, 2) if bits[4] != 'as':
if lastbits_reversed[1][::-1] != 'as':
raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
" be 'as'") " be 'as'")
expression = parser.compile_filter(lastbits_reversed[2][::-1]) expression = parser.compile_filter(bits[3])
var_name = bits[5]
var_name = lastbits_reversed[0][::-1]
return RegroupNode(target, expression, var_name) return RegroupNode(target, expression, var_name)
regroup = register.tag(regroup) regroup = register.tag(regroup)

View File

@ -1,4 +1,4 @@
import sys, time import sys, time, os
from django.conf import settings from django.conf import settings
from django.db import connection, get_creation_module from django.db import connection, get_creation_module
from django.core import mail from django.core import mail
@ -106,9 +106,32 @@ def create_test_db(verbosity=1, autoclobber=False):
if verbosity >= 1: if verbosity >= 1:
print "Creating test database..." print "Creating test database..."
# If we're using SQLite, it's more convenient to test against an # If we're using SQLite, it's more convenient to test against an
# 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": 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: else:
suffix = { suffix = {
'postgresql': get_postgresql_create_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) creation_module.destroy_test_db(settings, connection, old_database_name, verbosity)
return 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: if verbosity >= 1:
print "Destroying test database..." print "Destroying test database..."
connection.close() connection.close()
TEST_DATABASE_NAME = settings.DATABASE_NAME TEST_DATABASE_NAME = settings.DATABASE_NAME
settings.DATABASE_NAME = old_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() cursor = connection.cursor()
_set_autocommit(connection) _set_autocommit(connection)
time.sleep(1) # To avoid "database is being accessed by other users" errors. time.sleep(1) # To avoid "database is being accessed by other users" errors.

View File

@ -507,7 +507,7 @@ Exception Type: {{ exception_type|escape }} at {{ request.path|escape }}
Exception Value: {{ exception_value|escape }} Exception Value: {{ exception_value|escape }}
</textarea> </textarea>
<br><br> <br><br>
<input type="submit" value="Share this traceback on public Web site"> <input type="submit" value="Share this traceback on a public Web site">
</div> </div>
</form> </form>
</div> </div>

View File

@ -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'), if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]):
return HttpResponseNotModified() return HttpResponseNotModified()
mimetype = mimetypes.guess_type(fullpath)[0] mimetype = mimetypes.guess_type(fullpath)[0] or 'application/octet-stream'
contents = open(fullpath, 'rb').read() contents = open(fullpath, 'rb').read()
response = HttpResponse(contents, mimetype=mimetype) response = HttpResponse(contents, mimetype=mimetype)
response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
response["Content-Length"] = len(contents)
return response return response
DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ DEFAULT_DIRECTORY_INDEX_TEMPLATE = """

View File

@ -47,7 +47,11 @@ contenttypes
============ ============
A light framework for hooking into "types" of content, where each installed 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 csrf
==== ====
@ -177,9 +181,13 @@ localflavor
=========== ===========
A collection of various Django snippets that are useful only for a particular 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. contains a ``USZipCodeField`` that you can use to validate U.S. zip codes.
See the `localflavor documentation`_.
.. _localflavor documentation: ../localflavor/
markup markup
====== ======

View File

@ -247,8 +247,8 @@ Anonymous users
the ``django.contrib.auth.models.User`` interface, with these differences: the ``django.contrib.auth.models.User`` interface, with these differences:
* ``id`` is always ``None``. * ``id`` is always ``None``.
* ``is_staff`` and ``is_superuser`` are always False. * ``is_staff`` and ``is_superuser`` are always ``False``.
* ``is_active`` is always True. * ``is_active`` is always ``False``.
* ``groups`` and ``user_permissions`` are always empty. * ``groups`` and ``user_permissions`` are always empty.
* ``is_anonymous()`` returns ``True`` instead of ``False``. * ``is_anonymous()`` returns ``True`` instead of ``False``.
* ``is_authenticated()`` returns ``False`` instead of ``True``. * ``is_authenticated()`` returns ``False`` instead of ``True``.

View File

@ -44,8 +44,8 @@ Our class looks something like this::
# ... (other possibly useful methods omitted) ... # ... (other possibly useful methods omitted) ...
This is just an ordinary Python class, with nothing Django-specific about it. 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`` We'd like to be able to do things like this in our models (we assume the
attribute on the model is an instance of ``Hand``):: ``hand`` attribute on the model is an instance of ``Hand``)::
example = MyModel.objects.get(pk=1) example = MyModel.objects.get(pk=1)
print example.hand.north print example.hand.north

View File

@ -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``. 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 --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 ``django-admin.py`` will use the ``DJANGO_SETTINGS_MODULE`` environment
variable. variable.
Note that this option is unnecessary in ``manage.py``, because it takes care of Note that this option is unnecessary in ``manage.py``, because it uses
setting ``DJANGO_SETTINGS_MODULE`` for you. ``settings.py`` from the current project by default.
Extra niceties Extra niceties
============== ==============

View File

@ -1,6 +1,13 @@
Generating forms for models 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 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`` 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 model, and you want to create a form that lets people submit comments. In this

View File

@ -2,16 +2,22 @@
The "local flavor" add-ons The "local flavor" add-ons
========================== ==========================
Django comes with assorted pieces of code that are useful only for a particular Following its "batteries included" philosophy, Django comes with assorted
country or culture. These pieces of code are organized as a set of 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`_. 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 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 newforms_ framework -- for example, a ``USStateField`` that knows how to
the relevant subpackage. For example, a form with a field for French telephone validate U.S. state abbreviations, and a ``FISocialSecurityNumber`` that knows
numbers is created like so:: 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 import newforms as forms
from django.contrib.localflavor import fr from django.contrib.localflavor import fr
@ -19,32 +25,48 @@ numbers is created like so::
class MyForm(forms.Form): class MyForm(forms.Form):
my_french_phone_no = fr.forms.FRPhoneNumberField() my_french_phone_no = fr.forms.FRPhoneNumberField()
Supported countries
===================
Countries currently supported by ``localflavor`` are: Countries currently supported by ``localflavor`` are:
* Argentina_ * Argentina_
* Australia_ * Australia_
* Brazil_ * Brazil_
* Canada_ * Canada_
* Chile_ * Chile_
* Finland_ * Finland_
* France_ * France_
* Germany_ * Germany_
* Holland_ * Holland_
* Iceland_ * Iceland_
* India_ * India_
* Italy_ * Italy_
* Japan_ * Japan_
* Mexico_ * Mexico_
* Norway_ * Norway_
* Peru_ * Peru_
* Poland_ * Poland_
* Slovakia_ * Slovakia_
* `South Africa`_ * `South Africa`_
* Spain_ * Spain_
* Switzerland_ * Switzerland_
* `United Kingdom`_ * `United Kingdom`_
* `United States of America`_ * `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)`_ .. _Argentina: `Argentina (django.contrib.localflavor.ar)`_
.. _Australia: `Australia (django.contrib.localflavor.au)`_ .. _Australia: `Australia (django.contrib.localflavor.au)`_
.. _Brazil: `Brazil (django.contrib.localflavor.br)`_ .. _Brazil: `Brazil (django.contrib.localflavor.br)`_
@ -68,29 +90,15 @@ Countries currently supported by ``localflavor`` are:
.. _Switzerland: `Switzerland (django.contrib.localflavor.ch)`_ .. _Switzerland: `Switzerland (django.contrib.localflavor.ch)`_
.. _United Kingdom: `United Kingdom (django.contrib.localflavor.uk)`_ .. _United Kingdom: `United Kingdom (django.contrib.localflavor.uk)`_
.. _United States of America: `United States of America (django.contrib.localflavor.us)`_ .. _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/ .. _newforms: ../newforms/
Adding flavors
==============
.. admonition:: Adding a Flavor 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
We'd love to add more of these to Django, so please create a ticket for Unicode objects (``u'mystring'``) for strings, rather than setting the encoding
anything that you've found useful. Please use unicode objects in the file. See any of the existing flavors for examples.
(``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``) Argentina (``django.contrib.localflavor.ar``)
============================================= =============================================
@ -108,7 +116,6 @@ ARProvinceSelect
A ``Select`` widget that uses a list of Argentina's provinces as its choices. A ``Select`` widget that uses a list of Argentina's provinces as its choices.
Australia (``django.contrib.localflavor.au``) Australia (``django.contrib.localflavor.au``)
============================================= =============================================
@ -129,7 +136,6 @@ AUStateSelect
A ``Select`` widget that uses a list of Australian states/territories as its A ``Select`` widget that uses a list of Australian states/territories as its
choices. choices.
Brazil (``django.contrib.localflavor.br``) Brazil (``django.contrib.localflavor.br``)
========================================== ==========================================
@ -151,7 +157,6 @@ BRStateSelect
A ``Select`` widget that uses a list of Brazilian states/territories as its A ``Select`` widget that uses a list of Brazilian states/territories as its
choices. choices.
Canada (``django.contrib.localflavor.ca``) Canada (``django.contrib.localflavor.ca``)
========================================== ==========================================
@ -176,7 +181,7 @@ CASocialInsuranceNumberField
---------------------------- ----------------------------
A form field that validates input as a Canadian Social Insurance Number (SIN). 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`_. checksum`_.
.. _Luhn mod-10 checksum: http://en.wikipedia.org/wiki/Luhn_algorithm .. _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 A ``Select`` widget that uses a list of Canadian provinces and territories as
its choices. its choices.
Chile (``django.contrib.localflavor.cl``) Chile (``django.contrib.localflavor.cl``)
========================================= =========================================
@ -203,7 +207,6 @@ CLRegionSelect
A ``Select`` widget that uses a list of Chilean regions (Regiones) as its A ``Select`` widget that uses a list of Chilean regions (Regiones) as its
choices. choices.
Finland (``django.contrib.localflavor.fi``) Finland (``django.contrib.localflavor.fi``)
=========================================== ===========================================
@ -215,7 +218,7 @@ A form field that validates input as a Finnish social security number.
FIZipCodeField 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. consist of five digits.
FIMunicipalitySelect FIMunicipalitySelect
@ -224,7 +227,6 @@ FIMunicipalitySelect
A ``Select`` widget that uses a list of Finnish municipalities as its A ``Select`` widget that uses a list of Finnish municipalities as its
choices. choices.
France (``django.contrib.localflavor.fr``) France (``django.contrib.localflavor.fr``)
========================================== ==========================================
@ -232,13 +234,13 @@ FRPhoneNumberField
------------------ ------------------
A form field that validates input as a French local phone number. The 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. but are corrected to 0X XX XX XX XX.
FRZipCodeField 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. consist of five digits.
FRDepartmentSelect FRDepartmentSelect
@ -246,7 +248,6 @@ FRDepartmentSelect
A ``Select`` widget that uses a list of French departments as its choices. A ``Select`` widget that uses a list of French departments as its choices.
Germany (``django.contrib.localflavor.de``) Germany (``django.contrib.localflavor.de``)
=========================================== ===========================================
@ -254,7 +255,7 @@ DEIdentityCardNumberField
------------------------- -------------------------
A form field that validates input as a German identity card number 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. XXXXXXXXXXX-XXXXXXX-XXXXXXX-X, with no group consisting entirely of zeroes.
.. _Personalausweis: http://de.wikipedia.org/wiki/Personalausweis .. _Personalausweis: http://de.wikipedia.org/wiki/Personalausweis
@ -262,7 +263,7 @@ XXXXXXXXXXX-XXXXXXX-XXXXXXX-X, with no group consisting entirely of zeroes.
DEZipCodeField 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. consist of five digits.
DEStateSelect DEStateSelect
@ -270,7 +271,6 @@ DEStateSelect
A ``Select`` widget that uses a list of German states as its choices. A ``Select`` widget that uses a list of German states as its choices.
Holland (``django.contrib.localflavor.nl``) Holland (``django.contrib.localflavor.nl``)
=========================================== ===========================================
@ -296,7 +296,6 @@ NLProvinceSelect
A ``Select`` widget that uses a list of Dutch provinces as its list of A ``Select`` widget that uses a list of Dutch provinces as its list of
choices. choices.
Iceland (``django.contrib.localflavor.is_``) Iceland (``django.contrib.localflavor.is_``)
============================================ ============================================
@ -304,7 +303,7 @@ ISIdNumberField
--------------- ---------------
A form field that validates input as an Icelandic identification number A form field that validates input as an Icelandic identification number
(kennitala). The format is XXXXXX-XXXX. (kennitala). The format is XXXXXX-XXXX.
ISPhoneNumberField ISPhoneNumberField
------------------ ------------------
@ -318,7 +317,6 @@ ISPostalCodeSelect
A ``Select`` widget that uses a list of Icelandic postal codes as its A ``Select`` widget that uses a list of Icelandic postal codes as its
choices. choices.
India (``django.contrib.localflavor.in_``) India (``django.contrib.localflavor.in_``)
========================================== ==========================================
@ -326,7 +324,7 @@ INStateField
------------ ------------
A form field that validates input as an Indian state/territory name or 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. registration abbreviation for the given state or territory.
INZipCodeField INZipCodeField
@ -341,7 +339,6 @@ INStateSelect
A ``Select`` widget that uses a list of Indian states/territories as its A ``Select`` widget that uses a list of Indian states/territories as its
choices. choices.
Italy (``django.contrib.localflavor.it``) Italy (``django.contrib.localflavor.it``)
========================================= =========================================
@ -361,7 +358,7 @@ A form field that validates Italian VAT numbers (partita IVA).
ITZipCodeField 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. must have five digits.
ITProvinceSelect ITProvinceSelect
@ -374,22 +371,20 @@ ITRegionSelect
A ``Select`` widget that uses a list of Italian regions as its choices. A ``Select`` widget that uses a list of Italian regions as its choices.
Japan (``django.contrib.localflavor.jp``) Japan (``django.contrib.localflavor.jp``)
========================================= =========================================
JPPostalCodeField JPPostalCodeField
----------------- -----------------
A form field that validates input as a Japanese postcode. A form field that validates input as a Japanese postcode. It accepts seven
It accepts seven digits, with or without a hyphen. digits, with or without a hyphen.
JPPrefectureSelect JPPrefectureSelect
------------------ ------------------
A ``Select`` widget that uses a list of Japanese prefectures as its choices. A ``Select`` widget that uses a list of Japanese prefectures as its choices.
Mexico (``django.contrib.localflavor.mx``) Mexico (``django.contrib.localflavor.mx``)
========================================== ==========================================
@ -398,7 +393,6 @@ MXStateSelect
A ``Select`` widget that uses a list of Mexican states as its choices. A ``Select`` widget that uses a list of Mexican states as its choices.
Norway (``django.contrib.localflavor.no``) Norway (``django.contrib.localflavor.no``)
========================================== ==========================================
@ -413,7 +407,7 @@ A form field that validates input as a Norwegian social security number
NOZipCodeField 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. have four digits.
NOMunicipalitySelect NOMunicipalitySelect
@ -422,7 +416,6 @@ NOMunicipalitySelect
A ``Select`` widget that uses a list of Norwegian municipalities (fylker) as A ``Select`` widget that uses a list of Norwegian municipalities (fylker) as
its choices. its choices.
Peru (``django.contrib.localflavor.pe``) Peru (``django.contrib.localflavor.pe``)
======================================== ========================================
@ -436,14 +429,13 @@ PERUCField
---------- ----------
A form field that validates input as an RUC (Registro Unico de 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 PEDepartmentSelect
------------------ ------------------
A ``Select`` widget that uses a list of Peruvian Departments as its choices. A ``Select`` widget that uses a list of Peruvian Departments as its choices.
Poland (``django.contrib.localflavor.pl``) Poland (``django.contrib.localflavor.pl``)
========================================== ==========================================
@ -459,7 +451,7 @@ PLNationalBusinessRegisterField
------------------------------- -------------------------------
A form field that validates input as a Polish National Official Business 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 algorithm used for REGONs is documented at
http://wipos.p.lodz.pl/zylla/ut/nip-rego.html. 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 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. is XX-XXX, where X is a digit.
PLTaxNumberField PLTaxNumberField
---------------- ----------------
A form field that validates input as a Polish Tax Number (NIP). Valid 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 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. for NIPs is documented at http://wipos.p.lodz.pl/zylla/ut/nip-rego.html.
PLAdministrativeUnitSelect PLAdministrativeUnitSelect
@ -490,14 +482,13 @@ PLVoivodeshipSelect
A ``Select`` widget that uses a list of Polish voivodeships (administrative A ``Select`` widget that uses a list of Polish voivodeships (administrative
provinces) as its choices. provinces) as its choices.
Slovakia (``django.contrib.localflavor.sk``) Slovakia (``django.contrib.localflavor.sk``)
============================================ ============================================
SKPostalCodeField 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. are XXXXX or XXX XX, where X is a digit.
SKDistrictSelect SKDistrictSelect
@ -510,24 +501,22 @@ SKRegionSelect
A ``Select`` widget that uses a list of Slovak regions as its choices. A ``Select`` widget that uses a list of Slovak regions as its choices.
South Africa (``django.contrib.localflavor.za``) South Africa (``django.contrib.localflavor.za``)
================================================ ================================================
ZAIDField 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 uses the Luhn checksum and a simplistic (i.e., not entirely accurate) check
for birth date. for birth date.
ZAPostCodeField 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. postcodes must have four digits.
Spain (``django.contrib.localflavor.es``) Spain (``django.contrib.localflavor.es``)
========================================= =========================================
@ -541,23 +530,23 @@ ESCCCField
---------- ----------
A form field that validates input as a Spanish bank account number (Codigo 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, EEEE-OOOO-CC-AAAAAAAAAA, where the E, O, C and A digits denote the entity,
office, checksum and account, respectively. The first checksum digit office, checksum and account, respectively. The first checksum digit
validates the entity and office. The second checksum digit validates the 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 account. It is also valid to use a space as a delimiter, or to use no
delimiter. delimiter.
ESPhoneNumberField 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. have nine digits, the first of which is 6, 8 or 9.
ESPostalCodeField 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 have five digits, the first two being in the range 01 to 52, representing
the province. the province.
@ -571,7 +560,6 @@ ESRegionSelect
A ``Select`` widget that uses a list of Spanish regions as its choices. A ``Select`` widget that uses a list of Spanish regions as its choices.
Switzerland (``django.contrib.localflavor.ch``) Switzerland (``django.contrib.localflavor.ch``)
=============================================== ===============================================
@ -585,14 +573,14 @@ have the correct checksums -- see http://adi.kousz.ch/artikel/IDCHE.htm.
CHPhoneNumberField CHPhoneNumberField
------------------ ------------------
A form field that validates input as a Swiss phone number. The correct 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 format is 0XX XXX XX XX. 0XX.XXX.XX.XX and 0XXXXXXXXX validate but are
corrected to 0XX XXX XX XX. corrected to 0XX XXX XX XX.
CHZipCodeField 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. consist of four digits.
CHStateSelect CHStateSelect
@ -600,7 +588,6 @@ CHStateSelect
A ``Select`` widget that uses a list of Swiss states as its choices. A ``Select`` widget that uses a list of Swiss states as its choices.
United Kingdom (``django.contrib.localflavor.uk``) 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 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. 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``) 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 form field that validates input as a U.S. Social Security Number (SSN).
A valid SSN must obey the following rules: A valid SSN must obey the following rules:
* Format of XXX-XX-XXXX * Format of XXX-XX-XXXX
* No group of digits consisting entirely of zeroes * No group of digits consisting entirely of zeroes
* Leading group of digits cannot be 666 * Leading group of digits cannot be 666
* Number not in promotional block 987-65-4320 through 987-65-4329 * Number not in promotional block 987-65-4320 through 987-65-4329
* Number not one known to be invalid due to widespread promotional * Number not one known to be invalid due to widespread promotional
use or distribution (e.g., the Woolworth's number or the 1962 use or distribution (e.g., the Woolworth's number or the 1962
promotional number) promotional number)
USStateField USStateField
------------ ------------
@ -644,11 +640,11 @@ for the given state.
USZipCodeField 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. XXXXX or XXXXX-XXXX.
USStateSelect 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. choices.

View File

@ -61,17 +61,18 @@ Adds a few conveniences for perfectionists:
settings. settings.
If ``APPEND_SLASH`` is ``True`` and the initial URL doesn't end with a slash, 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 and it is not found in the URLconf, then a new URL is formed by appending a
at the end. If this new URL is found in urlpatterns, then an HTTP-redirect is slash at the end. If this new URL is found in the URLconf, then Django
returned to this new URL; otherwise the initial URL is processed as usual. 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 For example, ``foo.com/bar`` will be redirected to ``foo.com/bar/`` if you
have a valid urlpattern for ``foo.com/bar``, and do have a valid urlpattern don't have a valid URL pattern for ``foo.com/bar`` but *do* have a valid
for ``foo.com/bar/``. pattern for ``foo.com/bar/``.
**New in Django development version:** The behavior of ``APPEND_SLASH`` has **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 changed slightly in the development version. It didn't used to check whether
whether the pattern was matched in the URLconf. the pattern was matched in the URLconf.
If ``PREPEND_WWW`` is ``True``, URLs that lack a leading "www." will be If ``PREPEND_WWW`` is ``True``, URLs that lack a leading "www." will be
redirected to the same URL with a leading "www." 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 .. _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 django.middleware.transaction.TransactionMiddleware
--------------------------------------------------- ---------------------------------------------------

View File

@ -277,7 +277,7 @@ model fields:
any attempt to ``save()`` a ``ModelForm`` with missing fields will fail. any attempt to ``save()`` a ``ModelForm`` with missing fields will fail.
To avoid this failure, you must instantiate your model with initial values To avoid this failure, you must instantiate your model with initial values
for the missing, but required fields, or use ``save(commit=False)`` and 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') instance = Instance(required_field='value')
form = InstanceForm(request.POST, instance=instance) 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 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 want that to be represented as a ``DateField`` in your form. But
``ModelForm`` gives you the flexibility of changing the form field type ``ModelForm`` gives you the flexibility of changing the form field type
for a given model field. You do this by declaratively specifying fields like for a given model field. You do this by declaratively specifying fields like

View File

@ -156,6 +156,18 @@ Methods
Returns ``True`` or ``False``, designating whether ``request.GET`` or Returns ``True`` or ``False``, designating whether ``request.GET`` or
``request.POST`` has the given key. ``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()`` ``get_full_path()``
Returns the ``path``, plus an appended query string, if applicable. 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`` ``HttpResponseNotModified``
The constructor doesn't take any arguments. Use this to designate that a 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`` ``HttpResponseBadRequest``
**New in Django development version.** **New in Django development version.**

View File

@ -99,7 +99,7 @@ It implements the following standard dictionary methods:
* ``items()`` * ``items()``
* ``setdefault()`` * ``setdefault()`` (**New in Django development version**)
It also has these three methods: It also has these three methods:

View File

@ -981,8 +981,13 @@ TEST_DATABASE_NAME
Default: ``None`` Default: ``None``
The name of database to use when running the test suite. If a value of The name of database to use when running the test suite.
``None`` is specified, the test database will use the name ``'test_' + settings.DATABASE_NAME``. See `Testing Django Applications`_.
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/ .. _Testing Django Applications: ../testing/

View File

@ -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 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:: For example::
from django.conf import settings from django.conf import settings
@ -330,13 +330,13 @@ Here's how Django uses the sites framework:
retrieving flatpages to display. retrieving flatpages to display.
* In the `syndication framework`_, the templates for ``title`` and * 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 which is the ``Site`` object representing the current site. Also, the
hook for providing item URLs will use the ``domain`` from the current hook for providing item URLs will use the ``domain`` from the current
``Site`` object if you don't specify a fully-qualified domain. ``Site`` object if you don't specify a fully-qualified domain.
* In the `authentication framework`_, the ``django.contrib.auth.views.login`` * 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 shortcut view (``django.views.defaults.shortcut``) uses the domain of
the current ``Site`` object when calculating an object's URL. the current ``Site`` object when calculating an object's URL.

View File

@ -201,7 +201,7 @@ the feed.
An example makes this clear. Here's the code for these beat-specific feeds:: 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): class BeatFeed(Feed):
def get_object(self, bits): def get_object(self, bits):

View File

@ -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()`` won't cause an ``AttributeError`` (because integers don't have ``lower()``
methods). methods).
Registering a custom filters Registering custom filters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
Once you've written your filter definition, you need to register it with 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:: your ``Library`` instance, to make it available to Django's template language::

View File

@ -270,27 +270,21 @@ a test case, add the name of the test method to the label::
$ ./manage.py test animals.AnimalTestCase.testFluffyAnimals $ ./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 Tests that require a database (namely, model tests) will not use
prepares itself:: your "real" (production) database. A separate, blank database is created
for the tests.
Creating test database... Regardless of whether the tests pass or fail, the test database is destroyed
Creating table myapp_animal when all the tests have been executed.
Creating table myapp_mineral
Loading 'initial_data' fixtures...
No fixtures found.
This tells you that the test runner is creating a test database -- a blank, By default this test database gets its name by prepending ``test_`` to the
from-scratch database that it will use for any tests that happen to require a value of the ``DATABASE_NAME`` setting. When using the SQLite database engine
database (namely, model tests). 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
Don't worry -- the test runner will not touch your "real" (production) different database name, specify the ``TEST_DATABASE_NAME`` setting.
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.
Aside from using a separate database, the test runner will otherwise use all of 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``, 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/ .. _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. Once the test database has been created, Django will run your tests.
If everything goes well, you'll see something like this:: 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 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. 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 Testing tools
============= =============

View File

@ -213,13 +213,13 @@ u'046-454-286'
>>> f.clean('046-454-287') >>> f.clean('046-454-287')
Traceback (most recent call last): 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') >>> f.clean('046 454 286')
Traceback (most recent call last): 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') >>> f.clean('046-44-286')
Traceback (most recent call last): 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.']
""" """

View File

@ -41,7 +41,7 @@ Strict RUT usage (does not allow imposible values)
>>> rut.clean('11-6') >>> rut.clean('11-6')
Traceback (most recent call last): 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. # valid format, bad verifier.
>>> rut.clean('11.111.111-0') >>> rut.clean('11.111.111-0')
@ -53,17 +53,17 @@ ValidationError: [u'The Chilean RUT is not valid.']
>>> rut.clean('767484100') >>> rut.clean('767484100')
Traceback (most recent call last): 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') >>> rut.clean('78.412.790-7')
u'78.412.790-7' u'78.412.790-7'
>>> rut.clean('8.334.6043') >>> rut.clean('8.334.6043')
Traceback (most recent call last): 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') >>> rut.clean('76793310-K')
Traceback (most recent call last): 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 ######################################################### ## CLRegionSelect #########################################################
>>> from django.contrib.localflavor.cl.forms import CLRegionSelect >>> from django.contrib.localflavor.cl.forms import CLRegionSelect

View File

@ -12,13 +12,15 @@ u'BT32 4PX'
>>> f.clean('GIR 0AA') >>> f.clean('GIR 0AA')
u'GIR 0AA' u'GIR 0AA'
>>> f.clean('BT324PX') >>> f.clean('BT324PX')
Traceback (most recent call last): u'BT32 4PX'
...
ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.']
>>> f.clean('1NV 4L1D') >>> f.clean('1NV 4L1D')
Traceback (most recent call last): 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) >>> f.clean(None)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -27,7 +29,20 @@ ValidationError: [u'This field is required.']
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValidationError: [u'This field is required.'] 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 = UKPostcodeField(required=False)
>>> f.clean('BT32 4PX') >>> f.clean('BT32 4PX')
u'BT32 4PX' u'BT32 4PX'
@ -36,11 +51,9 @@ u'GIR 0AA'
>>> f.clean('1NV 4L1D') >>> f.clean('1NV 4L1D')
Traceback (most recent call last): 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') >>> f.clean('BT324PX')
Traceback (most recent call last): u'BT32 4PX'
...
ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.']
>>> f.clean(None) >>> f.clean(None)
u'' u''
>>> f.clean('') >>> f.clean('')

View File

@ -391,9 +391,45 @@ u'\ufffd'
>>> q.getlist('foo') >>> q.getlist('foo')
[u'bar', u'\ufffd'] [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__": if __name__ == "__main__":
import doctest import doctest

View File

@ -108,8 +108,8 @@ def get_filter_tests():
'filter-urlize05': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": "<script>alert('foo')</script>"}, "<script>alert('foo')</script>"), 'filter-urlize05': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": "<script>alert('foo')</script>"}, "<script>alert('foo')</script>"),
'filter-urlize06': ('{{ a|urlize }}', {"a": "<script>alert('foo')</script>"}, '&lt;script&gt;alert(&#39;foo&#39;)&lt;/script&gt;'), 'filter-urlize06': ('{{ a|urlize }}', {"a": "<script>alert('foo')</script>"}, '&lt;script&gt;alert(&#39;foo&#39;)&lt;/script&gt;'),
'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'<a href="http://example.com/x=&y=" rel="nofollow">http:...</a> <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'), 'filter-urlizetrunc01': ('{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('&quot;Safe&quot; http://example.com?x=&y=')}, u'"Unsafe" <a href="http://example.com/x=&y=" rel="nofollow">http:...</a> &quot;Safe&quot; <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'),
'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'<a href="http://example.com/x=&y=" rel="nofollow">http:...</a> <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'), 'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('&quot;Safe&quot; http://example.com?x=&y=')}, u'&quot;Unsafe&quot; <a href="http://example.com/x=&y=" rel="nofollow">http:...</a> &quot;Safe&quot; <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'),
'filter-wordcount01': ('{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "3 3"), 'filter-wordcount01': ('{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "3 3"),
'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "3 3"), 'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "3 3"),

View File

@ -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-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-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-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-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-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/"), '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 %},' + \
'{% 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 ####################################################### ### TEMPLATETAG TAG #######################################################
'templatetag01': ('{% templatetag openblock %}', {}, '{%'), 'templatetag01': ('{% templatetag openblock %}', {}, '{%'),

View File

@ -0,0 +1 @@
An unknown file extension.

View File

@ -13,11 +13,15 @@ class StaticTests(TestCase):
response = self.client.get('/views/site_media/%s' % filename) response = self.client.get('/views/site_media/%s' % filename)
file = open(path.join(media_dir, filename)) file = open(path.join(media_dir, filename))
self.assertEquals(file.read(), response.content) 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): def test_copes_with_empty_path_component(self):
file_name = 'file.txt' file_name = 'file.txt'
response = self.client.get('/views/site_media//%s' % file_name) response = self.client.get('/views/site_media//%s' % file_name)
file = open(path.join(media_dir, file_name)) file = open(path.join(media_dir, file_name))
self.assertEquals(file.read(), response.content) self.assertEquals(file.read(), response.content)

View File

@ -93,6 +93,7 @@ def django_tests(verbosity, interactive, test_labels):
old_root_urlconf = settings.ROOT_URLCONF old_root_urlconf = settings.ROOT_URLCONF
old_template_dirs = settings.TEMPLATE_DIRS old_template_dirs = settings.TEMPLATE_DIRS
old_use_i18n = settings.USE_I18N old_use_i18n = settings.USE_I18N
old_login_url = settings.LOGIN_URL
old_language_code = settings.LANGUAGE_CODE old_language_code = settings.LANGUAGE_CODE
old_middleware_classes = settings.MIDDLEWARE_CLASSES 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.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
settings.USE_I18N = True settings.USE_I18N = True
settings.LANGUAGE_CODE = 'en' settings.LANGUAGE_CODE = 'en'
settings.LOGIN_URL = '/accounts/login/'
settings.MIDDLEWARE_CLASSES = ( settings.MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
@ -153,6 +155,7 @@ def django_tests(verbosity, interactive, test_labels):
settings.TEMPLATE_DIRS = old_template_dirs settings.TEMPLATE_DIRS = old_template_dirs
settings.USE_I18N = old_use_i18n settings.USE_I18N = old_use_i18n
settings.LANGUAGE_CODE = old_language_code settings.LANGUAGE_CODE = old_language_code
settings.LOGIN_URL = old_login_url
settings.MIDDLEWARE_CLASSES = old_middleware_classes settings.MIDDLEWARE_CLASSES = old_middleware_classes
if __name__ == "__main__": if __name__ == "__main__":