From ffd5564a87c5c8e5d5e1e201ae3462807b955efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Honza=20Kr=C3=A1l?= Date: Mon, 4 Jan 2010 01:06:26 +0000 Subject: [PATCH] [soc2009/model-validation] Merged to trunk at r12070 git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@12073 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 6 + django/conf/global_settings.py | 1 + django/conf/locale/it/formats.py | 47 +++- django/conf/locale/no/formats.py | 16 +- django/conf/locale/pl/LC_MESSAGES/django.po | 49 ++-- .../admin/media/js/admin/DateTimeShortcuts.js | 44 ++-- django/contrib/admin/media/js/core.js | 39 ++++ django/contrib/admin/options.py | 32 +-- .../contrib/admin/templatetags/admin_list.py | 4 +- django/contrib/admin/util.py | 3 +- django/contrib/admin/widgets.py | 12 +- django/contrib/auth/models.py | 27 ++- django/contrib/auth/tests/auth_backends.py | 15 ++ django/contrib/databrowse/datastructures.py | 2 +- .../contrib/databrowse/plugins/calendars.py | 2 +- .../templates/databrowse/calendar_day.html | 2 +- .../templates/databrowse/calendar_main.html | 2 +- .../templates/databrowse/calendar_month.html | 4 +- .../gis/db/backends/postgis/operations.py | 14 +- .../gis/db/backends/spatialite/base.py | 29 +-- .../gis/db/backends/spatialite/creation.py | 3 +- .../gis/db/backends/spatialite/operations.py | 21 +- django/contrib/gis/db/models/manager.py | 5 +- django/contrib/gis/db/models/sql/compiler.py | 4 +- .../contrib/gis/geos/prototypes/errcheck.py | 9 +- django/contrib/gis/tests/relatedapp/tests.py | 2 - django/contrib/localflavor/id/__init__.py | 0 django/contrib/localflavor/id/forms.py | 210 ++++++++++++++++++ django/contrib/localflavor/id/id_choices.py | 101 +++++++++ django/contrib/localflavor/ie/__init__.py | 0 django/contrib/localflavor/ie/forms.py | 13 ++ django/contrib/localflavor/ie/ie_counties.py | 40 ++++ django/contrib/localflavor/kw/__init__.py | 0 django/contrib/localflavor/kw/forms.py | 61 +++++ django/contrib/localflavor/pt/__init__.py | 0 django/contrib/localflavor/pt/forms.py | 47 ++++ django/contrib/localflavor/uk/forms.py | 2 +- django/contrib/localflavor/us/forms.py | 4 +- django/contrib/localflavor/us/models.py | 16 +- django/contrib/localflavor/uy/__init__.py | 0 django/contrib/localflavor/uy/forms.py | 59 +++++ django/contrib/localflavor/uy/util.py | 12 + .../contrib/localflavor/uy/uy_departaments.py | 24 ++ django/core/management/commands/test.py | 6 +- django/forms/widgets.py | 19 +- django/template/defaultfilters.py | 14 +- django/test/simple.py | 41 +++- django/utils/datastructures.py | 7 + django/utils/dateformat.py | 14 +- django/utils/encoding.py | 17 +- django/utils/formats.py | 56 +++-- django/utils/translation/trans_real.py | 3 + django/views/i18n.py | 25 ++- docs/ref/contrib/localflavor.txt | 103 ++++++++- docs/ref/django-admin.txt | 6 +- docs/ref/settings.txt | 2 +- docs/ref/templates/builtins.txt | 2 + docs/releases/1.1.2.txt | 15 +- docs/releases/1.2.txt | 20 +- docs/topics/auth.txt | 2 +- docs/topics/testing.txt | 21 +- tests/regressiontests/admin_views/tests.py | 11 +- tests/regressiontests/admin_widgets/models.py | 9 + tests/regressiontests/admin_widgets/tests.py | 3 +- tests/regressiontests/datastructures/tests.py | 7 +- tests/regressiontests/dateformat/tests.py | 4 + tests/regressiontests/forms/localflavor/id.py | 175 +++++++++++++++ tests/regressiontests/forms/localflavor/ie.py | 12 + tests/regressiontests/forms/localflavor/kw.py | 15 ++ tests/regressiontests/forms/localflavor/pt.py | 106 +++++++++ tests/regressiontests/forms/localflavor/uk.py | 11 + tests/regressiontests/forms/localflavor/uy.py | 46 ++++ tests/regressiontests/forms/tests.py | 10 + tests/regressiontests/forms/widgets.py | 36 +++ tests/regressiontests/i18n/forms.py | 4 + tests/regressiontests/i18n/models.py | 6 +- tests/regressiontests/i18n/tests.py | 67 +++++- tests/regressiontests/localflavor/forms.py | 7 - tests/regressiontests/localflavor/models.py | 8 - tests/regressiontests/localflavor/tests.py | 83 ------- tests/regressiontests/settings/__init__.py | 4 + .../views/locale/ru/LC_MESSAGES/djangojs.mo | Bin 0 -> 431 bytes .../views/locale/ru/LC_MESSAGES/djangojs.po | 20 ++ tests/regressiontests/views/tests/i18n.py | 6 +- tests/runtests.py | 2 +- 85 files changed, 1652 insertions(+), 356 deletions(-) create mode 100644 django/contrib/localflavor/id/__init__.py create mode 100644 django/contrib/localflavor/id/forms.py create mode 100644 django/contrib/localflavor/id/id_choices.py create mode 100644 django/contrib/localflavor/ie/__init__.py create mode 100644 django/contrib/localflavor/ie/forms.py create mode 100644 django/contrib/localflavor/ie/ie_counties.py create mode 100644 django/contrib/localflavor/kw/__init__.py create mode 100644 django/contrib/localflavor/kw/forms.py create mode 100644 django/contrib/localflavor/pt/__init__.py create mode 100644 django/contrib/localflavor/pt/forms.py create mode 100644 django/contrib/localflavor/uy/__init__.py create mode 100644 django/contrib/localflavor/uy/forms.py create mode 100644 django/contrib/localflavor/uy/util.py create mode 100644 django/contrib/localflavor/uy/uy_departaments.py create mode 100644 tests/regressiontests/forms/localflavor/id.py create mode 100644 tests/regressiontests/forms/localflavor/ie.py create mode 100644 tests/regressiontests/forms/localflavor/kw.py create mode 100644 tests/regressiontests/forms/localflavor/pt.py create mode 100644 tests/regressiontests/forms/localflavor/uy.py create mode 100644 tests/regressiontests/settings/__init__.py create mode 100644 tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.mo create mode 100644 tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po diff --git a/AUTHORS b/AUTHORS index e60cf61c41..6ad345a971 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,9 +28,11 @@ answer newbie questions, and generally made Django that much better: ajs alang@bright-green.com + Alcides Fonseca Andi Albrecht Marty Alchin Ahmad Alhashemi + Ahmad Al-Ibrahim Daniel Alves Barbosa de Oliveira Vaz AgarFu Dagur Páll Ammendrup @@ -188,6 +190,7 @@ answer newbie questions, and generally made Django that much better: Simon Greenhill Owen Griffiths Espen Grindhaug + Janos Guljas Thomas Güttler Horst Gutmann Scot Hacker @@ -195,6 +198,7 @@ answer newbie questions, and generally made Django that much better: hambaloney Brian Harring Brant Harris + Ronny Haryanto Hawkeye Joe Heck Joel Heenan @@ -384,6 +388,7 @@ answer newbie questions, and generally made Django that much better: Rozza Oliver Rutherfurd ryankanno + Gonzalo Saavedra Manuel Saelices Ivan Sagalaev (Maniac) Vinay Sajip @@ -482,6 +487,7 @@ answer newbie questions, and generally made Django that much better: ymasuda@ethercube.com Jesse Young Mykola Zamkovoi + zegor Gasper Zejn Jarek Zgoda Cheng Zhang diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index e970c12792..e3ba6602cc 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -84,6 +84,7 @@ LANGUAGES = ( ('sk', gettext_noop('Slovak')), ('sl', gettext_noop('Slovenian')), ('sr', gettext_noop('Serbian')), + ('sr-latn', gettext_noop('Serbian Latin')), ('sv', gettext_noop('Swedish')), ('ta', gettext_noop('Tamil')), ('te', gettext_noop('Telugu')), diff --git a/django/conf/locale/it/formats.py b/django/conf/locale/it/formats.py index fe86b5bd70..8471bc0392 100644 --- a/django/conf/locale/it/formats.py +++ b/django/conf/locale/it/formats.py @@ -2,17 +2,40 @@ # This file is distributed under the same license as the Django package. # -DATE_FORMAT = 'd F Y' -TIME_FORMAT = 'H.i.s' -# DATETIME_FORMAT = -YEAR_MONTH_FORMAT = 'F Y' -MONTH_DAY_FORMAT = 'j F' -SHORT_DATE_FORMAT = 'd/M/Y' -# SHORT_DATETIME_FORMAT = -# FIRST_DAY_OF_WEEK = -# DATE_INPUT_FORMATS = -# TIME_INPUT_FORMATS = -# DATETIME_INPUT_FORMATS = +DATE_FORMAT = 'd F Y' # 25 Ottobre 2006 +TIME_FORMAT = 'H:i:s' # 14:30:59 +DATETIME_FORMAT = 'w d F Y H:i:s' # Mercoledì 25 Ottobre 2006 14:30:59 +YEAR_MONTH_FORMAT = 'F Y' # Ottobre 2006 +MONTH_DAY_FORMAT = 'j/F' # 10/2006 +SHORT_DATE_FORMAT = 'd/M/Y' # 25/12/2009 +SHORT_DATETIME_FORMAT = 'd/M/Y H:i:s' # 25/10/2009 14:30:59 +FIRST_DAY_OF_WEEK = 1 # Lunedì +DATE_INPUT_FORMATS = ( + '%Y-%m-%d', '%Y/%m/%d', # '2008-10-25', '2008/10/25' + '%d-%m-%Y', '%d/%m/%Y', # '25-10-2006', '25/10/2006' + '%d-%m-%y', '%d/%m/%y', # '25-10-06', '25/10/06' +) +TIME_INPUT_FORMATS = ( + '%H:%M:%S', # '14:30:59' + '%H:%M', # '14:30' +) +DATETIME_INPUT_FORMATS = ( + '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' + '%Y-%m-%d %H:%M', # '2006-10-25 14:30' + '%Y-%m-%d', # '2006-10-25' + '%d-%m-%Y %H:%M:%S', # '25-10-2006 14:30:59' + '%d-%m-%Y %H:%M', # '25-10-2006 14:30' + '%d-%m-%Y', # '25-10-2006' + '%d-%m-%y %H:%M:%S', # '25-10-06 14:30:59' + '%d-%m-%y %H:%M', # '25-10-06 14:30' + '%d-%m-%y', # '25-10-06' + '%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59' + '%d/%m/%Y %H:%M', # '25/10/2006 14:30' + '%d/%m/%Y', # '25/10/2006' + '%d/%m/%y %H:%M:%S', # '25/10/06 14:30:59' + '%d/%m/%y %H:%M', # '25/10/06 14:30' + '%d/%m/%y', # '25/10/06' +) DECIMAL_SEPARATOR = ',' THOUSAND_SEPARATOR = '.' -# NUMBER_GROUPING = +NUMBER_GROUPING = 3 diff --git a/django/conf/locale/no/formats.py b/django/conf/locale/no/formats.py index 62ee886f11..0ebf629d9c 100644 --- a/django/conf/locale/no/formats.py +++ b/django/conf/locale/no/formats.py @@ -14,19 +14,19 @@ DATE_INPUT_FORMATS = ( '%j. %B %Y', '%j %B %Y', # '25. oktober 2006', '25 oktober 2006' ) TIME_INPUT_FORMATS = ( - '%H:%i:%S', # '14:30:59' - '%H:%i', # '14:30' + '%H:%M:%S', # '14:30:59' + '%H:%M', # '14:30' ) DATETIME_INPUT_FORMATS = ( - '%Y-%m-%d %H:%i:%S', # '2006-10-25 14:30:59' - '%Y-%m-%d %H:%i', # '2006-10-25 14:30' + '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' + '%Y-%m-%d %H:%M', # '2006-10-25 14:30' '%Y-%m-%d', # '2006-10-25' '%Y-%m-%j', # '2006-10-25' - '%j.%m.%Y %H:%i:%S', # '25.10.2006 14:30:59' - '%j.%m.%Y %H:%i', # '25.10.2006 14:30' + '%j.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59' + '%j.%m.%Y %H:%M', # '25.10.2006 14:30' '%j.%m.%Y', # '25.10.2006' - '%j.%m.%y %H:%i:%S', # '25.10.06 14:30:59' - '%j.%m.%y %H:%i', # '25.10.06 14:30' + '%j.%m.%y %H:%M:%S', # '25.10.06 14:30:59' + '%j.%m.%y %H:%M', # '25.10.06 14:30' '%j.%m.%y', # '25.10.06' ) DECIMAL_SEPARATOR = ',' diff --git a/django/conf/locale/pl/LC_MESSAGES/django.po b/django/conf/locale/pl/LC_MESSAGES/django.po index 0438e80433..8fb0f9fec8 100644 --- a/django/conf/locale/pl/LC_MESSAGES/django.po +++ b/django/conf/locale/pl/LC_MESSAGES/django.po @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-12-23 12:55+0100\n" +"POT-Creation-Date: 2009-12-30 08:54+0100\n" "PO-Revision-Date: 2008-02-25 15:53+0100\n" "Last-Translator: Jarek Zgoda \n" "MIME-Version: 1.0\n" @@ -223,7 +223,7 @@ msgstr "chiński tradycyjny" msgid "Successfully deleted %(count)d %(items)s." msgstr "Usunięto %(count)d %(items)s." -#: contrib/admin/actions.py:67 contrib/admin/options.py:1073 +#: contrib/admin/actions.py:67 contrib/admin/options.py:1075 msgid "Are you sure?" msgstr "Jesteś pewien?" @@ -314,94 +314,94 @@ msgstr "logi" msgid "None" msgstr "brak" -#: contrib/admin/options.py:540 +#: contrib/admin/options.py:554 #, python-format msgid "Changed %s." msgstr "Zmieniono %s" -#: contrib/admin/options.py:540 contrib/admin/options.py:550 +#: contrib/admin/options.py:554 contrib/admin/options.py:564 #: contrib/comments/templates/comments/preview.html:16 forms/models.py:385 #: forms/models.py:598 msgid "and" msgstr "i" -#: contrib/admin/options.py:545 +#: contrib/admin/options.py:559 #, python-format msgid "Added %(name)s \"%(object)s\"." msgstr "Dodano %(name)s \"%(object)s\"." -#: contrib/admin/options.py:549 +#: contrib/admin/options.py:563 #, python-format msgid "Changed %(list)s for %(name)s \"%(object)s\"." msgstr "Zmieniono %(list)s w %(name)s \"%(object)s\"." -#: contrib/admin/options.py:554 +#: contrib/admin/options.py:568 #, python-format msgid "Deleted %(name)s \"%(object)s\"." msgstr "Usunięto %(name)s \"%(object)s\"." -#: contrib/admin/options.py:558 +#: contrib/admin/options.py:572 msgid "No fields changed." msgstr "Żadne pole nie zmienione." -#: contrib/admin/options.py:620 contrib/auth/admin.py:68 +#: contrib/admin/options.py:634 contrib/auth/admin.py:68 #, python-format msgid "The %(name)s \"%(obj)s\" was added successfully." msgstr "%(name)s \"%(obj)s\" dodany pomyślnie." -#: contrib/admin/options.py:624 contrib/admin/options.py:657 +#: contrib/admin/options.py:638 contrib/admin/options.py:671 #: contrib/auth/admin.py:77 msgid "You may edit it again below." msgstr "Możesz ponownie edytować wpis poniżej." -#: contrib/admin/options.py:634 contrib/admin/options.py:667 +#: contrib/admin/options.py:648 contrib/admin/options.py:681 #, python-format msgid "You may add another %s below." msgstr "Możesz dodać nowy wpis %s poniżej." -#: contrib/admin/options.py:655 +#: contrib/admin/options.py:669 #, python-format msgid "The %(name)s \"%(obj)s\" was changed successfully." msgstr "%(name)s \"%(obj)s\" zostało pomyślnie zmienione." -#: contrib/admin/options.py:663 +#: contrib/admin/options.py:677 #, python-format msgid "" "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." msgstr "" "%(name)s \"%(obj)s\" dodane pomyślnie. Możesz edytować ponownie wpis poniżej." -#: contrib/admin/options.py:714 +#: contrib/admin/options.py:728 msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." msgstr "" "Wykonanie akcji wymaga wybrania obiektów. Żaden obiekt nie został zmieniony." -#: contrib/admin/options.py:728 +#: contrib/admin/options.py:742 msgid "No action selected." msgstr "Nie wybrano akcji." -#: contrib/admin/options.py:808 +#: contrib/admin/options.py:822 #, python-format msgid "Add %s" msgstr "Dodaj %s" -#: contrib/admin/options.py:840 contrib/admin/options.py:1051 +#: contrib/admin/options.py:848 contrib/admin/options.py:1053 #, python-format msgid "%(name)s object with primary key %(key)r does not exist." msgstr "Obiekt %(name)s o kluczu głównym %(key)r nie istnieje." -#: contrib/admin/options.py:905 +#: contrib/admin/options.py:913 #, python-format msgid "Change %s" msgstr "Zmień %s" -#: contrib/admin/options.py:950 +#: contrib/admin/options.py:958 msgid "Database error" msgstr "Błąd bazy danych" -#: contrib/admin/options.py:986 +#: contrib/admin/options.py:994 #, python-format msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." @@ -409,12 +409,12 @@ msgstr[0] "%(count)s %(name)s został pomyślnie zmieniony." msgstr[1] "%(count)s %(name)s zostały pomyślnie zmienione." msgstr[2] "%(count)s %(name)s zostało pomyślnie zmienionych." -#: contrib/admin/options.py:1066 +#: contrib/admin/options.py:1068 #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" usunięty pomyślnie." -#: contrib/admin/options.py:1103 +#: contrib/admin/options.py:1105 #, python-format msgid "Change history: %s" msgstr "Historia zmian: %s" @@ -546,9 +546,8 @@ msgid "" "There's been an error. It's been reported to the site administrators via e-" "mail and should be fixed shortly. Thanks for your patience." msgstr "" -"Wystąpił niespodziewany błąd. Raport został wysłany e-mailem " -"administratorowi strony i powinien zostać wkrótce naprawiony. Dziękujemy za " -"cierpliwość." +"Wystąpił niespodziewany błąd. Został on zgłoszony e-mailem administratorowi " +"strony i powinien zostać wkrótce naprawiony. Dziękujemy za cierpliwość." #: contrib/admin/templates/admin/actions.html:4 msgid "Run the selected action" diff --git a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js index e3ebd03fb2..0c6eff0fa9 100644 --- a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js +++ b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js @@ -44,7 +44,7 @@ var DateTimeShortcuts = { var shortcuts_span = document.createElement('span'); inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling); var now_link = document.createElement('a'); - now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());"); + now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + gettext('TIME_INPUT_FORMATS') + "'));"); now_link.appendChild(document.createTextNode(gettext('Now'))); var clock_link = document.createElement('a'); clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');'); @@ -80,10 +80,10 @@ var DateTimeShortcuts = { quickElement('h2', clock_box, gettext('Choose a time')); time_list = quickElement('ul', clock_box, ''); time_list.className = 'timelist'; - quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());") - quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '00:00:00');") - quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '06:00:00');") - quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '12:00:00');") + quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + gettext('TIME_INPUT_FORMATS') + "'));"); + quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,0,0,0,0).strftime('" + gettext('TIME_INPUT_FORMATS') + "'));"); + quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,6,0,0,0).strftime('" + gettext('TIME_INPUT_FORMATS') + "'));"); + quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,12,0,0,0).strftime('" + gettext('TIME_INPUT_FORMATS') + "'));"); cancel_p = quickElement('p', clock_box, ''); cancel_p.className = 'calendar-cancel'; @@ -195,20 +195,19 @@ var DateTimeShortcuts = { openCalendar: function(num) { var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num) var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num) - var inp = DateTimeShortcuts.calendarInputs[num]; + var inp = DateTimeShortcuts.calendarInputs[num]; - // Determine if the current value in the input has a valid date. - // If so, draw the calendar with that date's year and month. - if (inp.value) { - var date_parts = inp.value.split('-'); - var year = date_parts[0]; - var month = parseFloat(date_parts[1]); - if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) { - DateTimeShortcuts.calendars[num].drawDate(month, year); - } - } + // Determine if the current value in the input has a valid date. + // If so, draw the calendar with that date's year and month. + if (inp.value) { + var date_parts = inp.value.split('-'); + var year = date_parts[0]; + var month = parseFloat(date_parts[1]); + if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) { + DateTimeShortcuts.calendars[num].drawDate(month, year); + } + } - // Recalculate the clockbox position // is it left-to-right or right-to-left layout ? if (getStyle(document.body,'direction')!='rtl') { @@ -237,12 +236,19 @@ var DateTimeShortcuts = { DateTimeShortcuts.calendars[num].drawNextMonth(); }, handleCalendarCallback: function(num) { - return "function(y, m, d) { DateTimeShortcuts.calendarInputs["+num+"].value = y+'-'+(m<10?'0':'')+m+'-'+(d<10?'0':'')+d; document.getElementById(DateTimeShortcuts.calendarDivName1+"+num+").style.display='none';}"; + format = gettext('DATE_INPUT_FORMATS'); + // the format needs to be escaped a little + format = format.replace('\\', '\\\\'); + format = format.replace('\r', '\\r'); + format = format.replace('\n', '\\n'); + format = format.replace('\t', '\\t'); + format = format.replace("'", "\\'"); + return "function(y, m, d) { DateTimeShortcuts.calendarInputs["+num+"].value = new Date(y, m-1, d).strftime('"+format+"');document.getElementById(DateTimeShortcuts.calendarDivName1+"+num+").style.display='none';}"; }, handleCalendarQuickLink: function(num, offset) { var d = new Date(); d.setDate(d.getDate() + offset) - DateTimeShortcuts.calendarInputs[num].value = d.getISODate(); + DateTimeShortcuts.calendarInputs[num].value = d.strftime(gettext('DATE_INPUT_FORMATS')); DateTimeShortcuts.dismissCalendar(num); }, cancelEventPropagation: function(e) { diff --git a/django/contrib/admin/media/js/core.js b/django/contrib/admin/media/js/core.js index c8d0db6a8d..a7192c17f1 100644 --- a/django/contrib/admin/media/js/core.js +++ b/django/contrib/admin/media/js/core.js @@ -115,6 +115,10 @@ Date.prototype.getCorrectYear = function() { return (y < 38) ? y + 2000 : y + 1900; } +Date.prototype.getTwelveHours = function() { + return (this.getHours() <= 12) ? this.getHours() : 24 - this.getHours(); +} + Date.prototype.getTwoDigitMonth = function() { return (this.getMonth() < 9) ? '0' + (this.getMonth()+1) : (this.getMonth()+1); } @@ -123,6 +127,10 @@ Date.prototype.getTwoDigitDate = function() { return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate(); } +Date.prototype.getTwoDigitTwelveHour = function() { + return (this.getTwelveHours() < 10) ? '0' + this.getTwelveHours() : this.getTwelveHours(); +} + Date.prototype.getTwoDigitHour = function() { return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours(); } @@ -147,6 +155,37 @@ Date.prototype.getHourMinuteSecond = function() { return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond(); } +Date.prototype.strftime = function(format) { + var fields = { + c: this.toString(), + d: this.getTwoDigitDate(), + H: this.getTwoDigitHour(), + I: this.getTwoDigitTwelveHour(), + m: this.getTwoDigitMonth(), + M: this.getTwoDigitMinute(), + p: (this.getHours() >= 12) ? 'PM' : 'AM', + S: this.getTwoDigitSecond(), + w: '0' + this.getDay(), + x: this.toLocaleDateString(), + X: this.toLocaleTimeString(), + y: ('' + this.getFullYear()).substr(2, 4), + Y: '' + this.getFullYear(), + '%' : '%' + }; + var result = '', i = 0; + while (i < format.length) { + if (format[i] === '%') { + result = result + fields[format[i + 1]]; + ++i; + } + else { + result = result + format[i]; + } + ++i; + } + return result; +} + // ---------------------------------------------------------------------------- // String object extensions // ---------------------------------------------------------------------------- diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 3f79a81d56..5085d884a9 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -8,7 +8,7 @@ from django.contrib.admin import helpers from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict from django.contrib import messages from django.views.decorators.csrf import csrf_protect -from django.core.exceptions import PermissionDenied +from django.core.exceptions import PermissionDenied, ValidationError from django.db import models, transaction from django.db.models.fields import BLANK_CHOICE_DASH from django.http import Http404, HttpResponse, HttpResponseRedirect @@ -368,6 +368,20 @@ class ModelAdmin(BaseModelAdmin): from django.contrib.admin.views.main import ChangeList return ChangeList + def get_object(self, request, object_id): + """ + Returns an instance matching the primary key provided. ``None`` is + returned if no match is found (or the object_id failed validation + against the primary key field). + """ + queryset = self.queryset(request) + model = queryset.model + try: + object_id = model._meta.pk.to_python(object_id) + return queryset.get(pk=object_id) + except (model.DoesNotExist, ValidationError): + return None + def get_changelist_form(self, request, **kwargs): """ Returns a Form class for use in the Formset on the changelist page. @@ -829,13 +843,7 @@ class ModelAdmin(BaseModelAdmin): model = self.model opts = model._meta - try: - obj = self.queryset(request).get(pk=unquote(object_id)) - except model.DoesNotExist: - # Don't raise Http404 just yet, because we haven't checked - # permissions yet. We don't want an unauthenticated user to be able - # to determine whether a given object exists. - obj = None + obj = self.get_object(request, unquote(object_id)) if not self.has_change_permission(request, obj): raise PermissionDenied @@ -1040,13 +1048,7 @@ class ModelAdmin(BaseModelAdmin): opts = self.model._meta app_label = opts.app_label - try: - obj = self.queryset(request).get(pk=unquote(object_id)) - except self.model.DoesNotExist: - # Don't raise Http404 just yet, because we haven't checked - # permissions yet. We don't want an unauthenticated user to be able - # to determine whether a given object exists. - obj = None + obj = self.get_object(request, unquote(object_id)) if not self.has_delete_permission(request, obj): raise PermissionDenied diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 352277351e..8626ef0c23 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -247,8 +247,8 @@ def date_hierarchy(cl): return { 'show': True, 'choices': [{ - 'link': link({year_field: year.year}), - 'title': year.year + 'link': link({year_field: str(year.year)}), + 'title': str(year.year), } for year in years] } date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy) diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 8b2435ef33..4a9f49cb7c 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -252,8 +252,7 @@ def lookup_field(name, obj, model_admin=None): def label_for_field(name, model, model_admin): try: - model._meta.get_field_by_name(name)[0] - return name + return model._meta.get_field_by_name(name)[0].verbose_name except models.FieldDoesNotExist: if name == "__unicode__": return force_unicode(model._meta.verbose_name) diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 0392b642bb..120df94cf3 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -41,21 +41,21 @@ class FilteredSelectMultiple(forms.SelectMultiple): (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX)) return mark_safe(u''.join(output)) -class AdminDateWidget(forms.TextInput): +class AdminDateWidget(forms.DateTimeInput): class Media: js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js") - def __init__(self, attrs={}): - super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'}) + def __init__(self, attrs={}, format=None): + super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'}, format=format) -class AdminTimeWidget(forms.TextInput): +class AdminTimeWidget(forms.TimeInput): class Media: js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js") - def __init__(self, attrs={}): - super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'}) + def __init__(self, attrs={}, format=None): + super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'}, format=format) class AdminSplitDateTime(forms.SplitDateTimeWidget): """ diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 8148d8a992..ceab7baf12 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -218,22 +218,26 @@ class User(models.Model): permissions = set() for backend in auth.get_backends(): if hasattr(backend, "get_group_permissions"): - if obj is not None and backend.supports_object_permissions: - group_permissions = backend.get_group_permissions(self, obj) + if obj is not None: + if backend.supports_object_permissions: + permissions.update( + backend.get_group_permissions(self, obj) + ) else: - group_permissions = backend.get_group_permissions(self) - permissions.update(group_permissions) + permissions.update(backend.get_group_permissions(self)) return permissions def get_all_permissions(self, obj=None): permissions = set() for backend in auth.get_backends(): if hasattr(backend, "get_all_permissions"): - if obj is not None and backend.supports_object_permissions: - all_permissions = backend.get_all_permissions(self, obj) + if obj is not None: + if backend.supports_object_permissions: + permissions.update( + backend.get_all_permissions(self, obj) + ) else: - all_permissions = backend.get_all_permissions(self) - permissions.update(all_permissions) + permissions.update(backend.get_all_permissions(self)) return permissions def has_perm(self, perm, obj=None): @@ -255,9 +259,10 @@ class User(models.Model): # Otherwise we need to check the backends. for backend in auth.get_backends(): if hasattr(backend, "has_perm"): - if obj is not None and backend.supports_object_permissions: - if backend.has_perm(self, perm, obj): - return True + if obj is not None: + if (backend.supports_object_permissions and + backend.has_perm(self, perm, obj)): + return True else: if backend.has_perm(self, perm): return True diff --git a/django/contrib/auth/tests/auth_backends.py b/django/contrib/auth/tests/auth_backends.py index bf5611aef0..af15d0b03b 100644 --- a/django/contrib/auth/tests/auth_backends.py +++ b/django/contrib/auth/tests/auth_backends.py @@ -69,6 +69,21 @@ class BackendTest(TestCase): self.assertEqual(user.has_perm('test'), False) self.assertEqual(user.has_perms(['auth.test2', 'auth.test3']), False) + def test_has_no_object_perm(self): + """Regressiontest for #12462""" + user = User.objects.get(username='test') + content_type=ContentType.objects.get_for_model(Group) + perm = Permission.objects.create(name='test', content_type=content_type, codename='test') + user.user_permissions.add(perm) + user.save() + + self.assertEqual(user.has_perm('auth.test', 'object'), False) + self.assertEqual(user.get_all_permissions('object'), set([])) + self.assertEqual(user.has_perm('auth.test'), True) + self.assertEqual(user.get_all_permissions(), set(['auth.test'])) + + + class TestObj(object): pass diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index 369f825f0e..4fcdba9d33 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -158,7 +158,7 @@ class EasyInstanceField(object): if isinstance(self.field, models.DateTimeField): objs = capfirst(formats.date_format(self.raw_value, 'DATETIME_FORMAT')) elif isinstance(self.field, models.TimeField): - objs = capfirst(formats.date_format(self.raw_value, 'TIME_FORMAT')) + objs = capfirst(formats.time_format(self.raw_value, 'TIME_FORMAT')) else: objs = capfirst(formats.date_format(self.raw_value, 'DATE_FORMAT')) else: diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py index ac2f522148..9bbd02da26 100644 --- a/django/contrib/databrowse/plugins/calendars.py +++ b/django/contrib/databrowse/plugins/calendars.py @@ -37,7 +37,7 @@ class CalendarPlugin(DatabrowsePlugin): return [mark_safe(u'%s%s/%s/%s/%s/%s/' % ( easy_instance_field.model.url(), plugin_name, easy_instance_field.field.name, - d.year, + str(d.year), datetime_safe.new_date(d).strftime('%b').lower(), d.day))] diff --git a/django/contrib/databrowse/templates/databrowse/calendar_day.html b/django/contrib/databrowse/templates/databrowse/calendar_day.html index b0a221d5d3..c009a94c66 100644 --- a/django/contrib/databrowse/templates/databrowse/calendar_day.html +++ b/django/contrib/databrowse/templates/databrowse/calendar_day.html @@ -4,7 +4,7 @@ {% block content %} - +

{{ object_list.count }} {% if object_list.count|pluralize %}{{ model.verbose_name_plural }}{% else %}{{ model.verbose_name }}{% endif %} with {{ field.verbose_name }} on {{ day|date:"F j, Y" }}

diff --git a/django/contrib/databrowse/templates/databrowse/calendar_main.html b/django/contrib/databrowse/templates/databrowse/calendar_main.html index b22a44d321..7cb59042df 100644 --- a/django/contrib/databrowse/templates/databrowse/calendar_main.html +++ b/django/contrib/databrowse/templates/databrowse/calendar_main.html @@ -10,7 +10,7 @@ diff --git a/django/contrib/databrowse/templates/databrowse/calendar_month.html b/django/contrib/databrowse/templates/databrowse/calendar_month.html index f6a616911b..ad189f441c 100644 --- a/django/contrib/databrowse/templates/databrowse/calendar_month.html +++ b/django/contrib/databrowse/templates/databrowse/calendar_month.html @@ -4,9 +4,9 @@ {% block content %} - + -

{{ object_list.count }} {% if object_list.count|pluralize %}{{ model.verbose_name_plural }}{% else %}{{ model.verbose_name }}{% endif %} with {{ field.verbose_name }} on {{ day|date:"F Y" }}

+

{{ object_list.count }} {% if object_list.count|pluralize %}{{ model.verbose_name_plural }}{% else %}{{ model.verbose_name }}{% endif %} with {{ field.verbose_name }} on {{ month|date:"F Y" }}

    {% for object in object_list %} diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 98dab75270..030599dfff 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -8,8 +8,7 @@ from django.contrib.gis.db.backends.postgis.adapter import PostGISAdapter from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Distance from django.core.exceptions import ImproperlyConfigured -from django.db.backends.postgresql.operations import DatabaseOperations -from django.db.backends.postgresql_psycopg2.base import Database +from django.db.backends.postgresql_psycopg2.base import Database, DatabaseOperations #### Classes used in constructing PostGIS spatial SQL #### class PostGISOperator(SpatialOperation): @@ -404,11 +403,12 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): """ cursor = self.connection._cursor() try: - cursor.execute('SELECT %s()' % func) - row = cursor.fetchone() - except: - # Responsibility of callers to perform error handling. - raise + try: + cursor.execute('SELECT %s()' % func) + row = cursor.fetchone() + except: + # Responsibility of callers to perform error handling. + raise finally: cursor.close() return row[0] diff --git a/django/contrib/gis/db/backends/spatialite/base.py b/django/contrib/gis/db/backends/spatialite/base.py index 0b6332dd0c..9dde4ec0b2 100644 --- a/django/contrib/gis/db/backends/spatialite/base.py +++ b/django/contrib/gis/db/backends/spatialite/base.py @@ -9,7 +9,6 @@ from django.contrib.gis.db.backends.spatialite.client import SpatiaLiteClient from django.contrib.gis.db.backends.spatialite.creation import SpatiaLiteCreation from django.contrib.gis.db.backends.spatialite.operations import SpatiaLiteOperations - class DatabaseWrapper(SqliteDatabaseWrapper): def __init__(self, *args, **kwargs): # Before we get too far, make sure pysqlite 2.5+ is installed. @@ -36,6 +35,7 @@ class DatabaseWrapper(SqliteDatabaseWrapper): def _cursor(self): if self.connection is None: + ## The following is the same as in django.db.backends.sqlite3.base ## settings_dict = self.settings_dict if not settings_dict['NAME']: from django.core.exceptions import ImproperlyConfigured @@ -50,7 +50,11 @@ class DatabaseWrapper(SqliteDatabaseWrapper): self.connection.create_function("django_extract", 2, _sqlite_extract) self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc) self.connection.create_function("regexp", 2, _sqlite_regexp) + connection_created.send(sender=self.__class__) + ## From here on, customized for GeoDjango ## + + # Enabling extension loading on the SQLite connection. try: self.connection.enable_load_extension(True) except AttributeError: @@ -59,15 +63,14 @@ class DatabaseWrapper(SqliteDatabaseWrapper): 'the loading of extensions to use SpatiaLite.' ) - connection_created.send(sender=self.__class__) - return self.connection.cursor(factory=SQLiteCursorWrapper) - - def load_spatialite(self): - """ - Loads the SpatiaLite library. - """ - try: - self._cursor().execute("SELECT load_extension(%s)", (self.spatialite_lib,)) - except Exception, msg: - raise ImproperlyConfigured('Unable to load the SpatiaLite extension ' - '"%s" because: %s' % (self.spatialite_lib, msg)) + # Loading the SpatiaLite library extension on the connection, and returning + # the created cursor. + cur = self.connection.cursor(factory=SQLiteCursorWrapper) + try: + cur.execute("SELECT load_extension(%s)", (self.spatialite_lib,)) + except Exception, msg: + raise ImproperlyConfigured('Unable to load the SpatiaLite library extension ' + '"%s" because: %s' % (self.spatialite_lib, msg)) + return cur + else: + return self.connection.cursor(factory=SQLiteCursorWrapper) diff --git a/django/contrib/gis/db/backends/spatialite/creation.py b/django/contrib/gis/db/backends/spatialite/creation.py index 1bec8360df..cbe4a29585 100644 --- a/django/contrib/gis/db/backends/spatialite/creation.py +++ b/django/contrib/gis/db/backends/spatialite/creation.py @@ -24,8 +24,7 @@ class SpatiaLiteCreation(DatabaseCreation): self.connection.settings_dict["NAME"] = test_database_name can_rollback = self._rollback_works() self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback - # Need to load the SpatiaLite library and initializatin SQL before running `syncdb`. - self.connection.load_spatialite() + # Need to load the SpatiaLite initialization SQL before running `syncdb`. self.load_spatialite_sql() call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias) diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 7b4e4364ad..444a212c13 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -51,8 +51,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): name = 'spatialite' spatialite = True version_regex = re.compile(r'^(?P\d)\.(?P\d)\.(?P\d+)') - valid_aggregates = dict([(k, None) for k in - ('Extent', 'Union')]) + valid_aggregates = dict([(k, None) for k in ('Extent', 'Union')]) Adapter = SpatiaLiteAdapter @@ -112,10 +111,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): super(DatabaseOperations, self).__init__() self.connection = connection - # Load the spatialite library (must be done before getting the - # SpatiaLite version). - self.connection.load_spatialite() - + # Determine the version of the SpatiaLite library. try: vtup = self.spatialite_version_tuple() version = vtup[1:] @@ -211,11 +207,12 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): """ cursor = self.connection._cursor() try: - cursor.execute('SELECT %s()' % func) - row = cursor.fetchone() - except: - # TODO: raise helpful exception here. - raise + try: + cursor.execute('SELECT %s()' % func) + row = cursor.fetchone() + except: + # Responsibility of caller to perform error handling. + raise finally: cursor.close() return row[0] @@ -268,7 +265,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): """ Returns the SpatiaLite-specific SQL for the given lookup value [a tuple of (alias, column, db_type)], lookup type, lookup - value, and the model field. + value, the model field, and the quoting function. """ alias, col, db_type = lvalue diff --git a/django/contrib/gis/db/models/manager.py b/django/contrib/gis/db/models/manager.py index b39d652d0d..041bc0dcd5 100644 --- a/django/contrib/gis/db/models/manager.py +++ b/django/contrib/gis/db/models/manager.py @@ -10,7 +10,10 @@ class GeoManager(Manager): use_for_related_fields = True def get_query_set(self): - return GeoQuerySet(model=self.model) + qs = GeoQuerySet(self.model) + if self._db is not None: + qs = qs.using(self._db) + return qs def area(self, *args, **kwargs): return self.get_query_set().area(*args, **kwargs) diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index 1a91f8eeba..a94093fec3 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -191,8 +191,8 @@ class GeoSQLCompiler(compiler.SQLCompiler): if self.connection.ops.oracle or getattr(self.query, 'geo_values', False): # We resolve the rest of the columns if we're on Oracle or if # the `geo_values` attribute is defined. - for value, field in izip(row[index_start:], fields): - values.append(self.query.convert_values(value, field, self.connection)) + for value, field in map(None, row[index_start:], fields): + values.append(self.query.convert_values(value, field, connection=self.connection)) else: values.extend(row[index_start:]) return tuple(values) diff --git a/django/contrib/gis/geos/prototypes/errcheck.py b/django/contrib/gis/geos/prototypes/errcheck.py index 0d5e9e24fc..a00b93be6e 100644 --- a/django/contrib/gis/geos/prototypes/errcheck.py +++ b/django/contrib/gis/geos/prototypes/errcheck.py @@ -1,6 +1,7 @@ """ Error checking functions for GEOS ctypes prototype functions. """ +import os from ctypes import c_void_p, string_at, CDLL from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.libgeos import lgeos, GEOS_VERSION @@ -15,8 +16,12 @@ if GEOS_VERSION >= (3, 1, 1): free.restype = None else: # Getting the `free` routine from the C library of the platform. - # The C library is obtained by passing None into `CDLL`. - libc = CDLL(None) + if os.name == 'nt': + # On NT, use the MS C library. + libc = CDLL('msvcrt') + else: + # On POSIX platforms C library is obtained by passing None into `CDLL`. + libc = CDLL(None) free = libc.free ### ctypes error checking routines ### diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py index 8ebe871e6d..184b65b9c7 100644 --- a/django/contrib/gis/tests/relatedapp/tests.py +++ b/django/contrib/gis/tests/relatedapp/tests.py @@ -19,7 +19,6 @@ class RelatedGeoModelTest(unittest.TestCase): loc = Location.objects.create(point=Point(lon, lat)) c = City.objects.create(name=name, state=state, location=loc) - @no_oracle # TODO: Fix select_related() problems w/Oracle and pagination. def test02_select_related(self): "Testing `select_related` on geographic models (see #7126)." qs1 = City.objects.all() @@ -34,7 +33,6 @@ class RelatedGeoModelTest(unittest.TestCase): self.assertEqual(Point(lon, lat), c.location.point) @no_mysql - @no_oracle # Pagination problem is implicated in this test as well. def test03_transform_related(self): "Testing the `transform` GeoQuerySet method on related geographic models." # All the transformations are to state plane coordinate systems using diff --git a/django/contrib/localflavor/id/__init__.py b/django/contrib/localflavor/id/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/id/forms.py b/django/contrib/localflavor/id/forms.py new file mode 100644 index 0000000000..0d68fa32d5 --- /dev/null +++ b/django/contrib/localflavor/id/forms.py @@ -0,0 +1,210 @@ +""" +ID-specific Form helpers +""" + +import re +import time + +from django.forms import ValidationError +from django.forms.fields import Field, Select, EMPTY_VALUES +from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import smart_unicode + +postcode_re = re.compile(r'^[1-9]\d{4}$') +phone_re = re.compile(r'^(\+62|0)[2-9]\d{7,10}$') +plate_re = re.compile(r'^(?P[A-Z]{1,2}) ' + \ + r'(?P\d{1,5})( (?P([A-Z]{1,3}|[1-9][0-9]{,2})))?$') +nik_re = re.compile(r'^\d{16}$') + + +class IDPostCodeField(Field): + """ + An Indonesian post code field. + + http://id.wikipedia.org/wiki/Kode_pos + """ + default_error_messages = { + 'invalid': _('Enter a valid post code'), + } + + def clean(self, value): + super(IDPostCodeField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + + value = value.strip() + if not postcode_re.search(value): + raise ValidationError(self.error_messages['invalid']) + + if int(value) < 10110: + raise ValidationError(self.error_messages['invalid']) + + # 1xxx0 + if value[0] == '1' and value[4] != '0': + raise ValidationError(self.error_messages['invalid']) + + return u'%s' % (value, ) + + +class IDProvinceSelect(Select): + """ + A Select widget that uses a list of provinces of Indonesia as its + choices. + """ + + def __init__(self, attrs=None): + from id_choices import PROVINCE_CHOICES + super(IDProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES) + + +class IDPhoneNumberField(Field): + """ + An Indonesian telephone number field. + + http://id.wikipedia.org/wiki/Daftar_kode_telepon_di_Indonesia + """ + default_error_messages = { + 'invalid': _('Enter a valid phone number'), + } + + def clean(self, value): + super(IDPhoneNumberField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + + phone_number = re.sub(r'[\-\s\(\)]', '', smart_unicode(value)) + + if phone_re.search(phone_number): + return smart_unicode(value) + + raise ValidationError(self.error_messages['invalid']) + + +class IDLicensePlatePrefixSelect(Select): + """ + A Select widget that uses a list of vehicle license plate prefix code + of Indonesia as its choices. + + http://id.wikipedia.org/wiki/Tanda_Nomor_Kendaraan_Bermotor + """ + + def __init__(self, attrs=None): + from id_choices import LICENSE_PLATE_PREFIX_CHOICES + super(IDLicensePlatePrefixSelect, self).__init__(attrs, + choices=LICENSE_PLATE_PREFIX_CHOICES) + + +class IDLicensePlateField(Field): + """ + An Indonesian vehicle license plate field. + + http://id.wikipedia.org/wiki/Tanda_Nomor_Kendaraan_Bermotor + + Plus: "B 12345 12" + """ + default_error_messages = { + 'invalid': _('Enter a valid vehicle license plate number'), + } + + def clean(self, value): + super(IDLicensePlateField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + + plate_number = re.sub(r'\s+', ' ', + smart_unicode(value.strip())).upper() + + matches = plate_re.search(plate_number) + if matches is None: + raise ValidationError(self.error_messages['invalid']) + + # Make sure prefix is in the list of known codes. + from id_choices import LICENSE_PLATE_PREFIX_CHOICES + prefix = matches.group('prefix') + if prefix not in [choice[0] for choice in LICENSE_PLATE_PREFIX_CHOICES]: + raise ValidationError(self.error_messages['invalid']) + + # Only Jakarta (prefix B) can have 3 letter suffix. + suffix = matches.group('suffix') + if suffix is not None and len(suffix) == 3 and prefix != 'B': + raise ValidationError(self.error_messages['invalid']) + + # RI plates don't have suffix. + if prefix == 'RI' and suffix is not None and suffix != '': + raise ValidationError(self.error_messages['invalid']) + + # Number can't be zero. + number = matches.group('number') + if number == '0': + raise ValidationError(self.error_messages['invalid']) + + # CD, CC and B 12345 12 + if len(number) == 5 or prefix in ('CD', 'CC'): + # suffix must be numeric and non-empty + if re.match(r'^\d+$', suffix) is None: + raise ValidationError(self.error_messages['invalid']) + + # Known codes range is 12-124 + if prefix in ('CD', 'CC') and not (12 <= int(number) <= 124): + raise ValidationError(self.error_messages['invalid']) + if len(number) == 5 and not (12 <= int(suffix) <= 124): + raise ValidationError(self.error_messages['invalid']) + else: + # suffix must be non-numeric + if suffix is not None and re.match(r'^[A-Z]{,3}$', suffix) is None: + raise ValidationError(self.error_messages['invalid']) + + return plate_number + + +class IDNationalIdentityNumberField(Field): + """ + An Indonesian national identity number (NIK/KTP#) field. + + http://id.wikipedia.org/wiki/Nomor_Induk_Kependudukan + + xx.xxxx.ddmmyy.xxxx - 16 digits (excl. dots) + """ + default_error_messages = { + 'invalid': _('Enter a valid NIK/KTP number'), + } + + def clean(self, value): + super(IDNationalIdentityNumberField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + + value = re.sub(r'[\s.]', '', smart_unicode(value)) + + if not nik_re.search(value): + raise ValidationError(self.error_messages['invalid']) + + if int(value) == 0: + raise ValidationError(self.error_messages['invalid']) + + def valid_nik_date(year, month, day): + try: + t1 = (int(year), int(month), int(day), 0, 0, 0, 0, 0, -1) + d = time.mktime(t1) + t2 = time.localtime(d) + if t1[:3] != t2[:3]: + return False + else: + return True + except (OverflowError, ValueError): + return False + + year = int(value[10:12]) + month = int(value[8:10]) + day = int(value[6:8]) + current_year = time.localtime().tm_year + if year < int(str(current_year)[-2:]): + if not valid_nik_date(2000 + int(year), month, day): + raise ValidationError(self.error_messages['invalid']) + elif not valid_nik_date(1900 + int(year), month, day): + raise ValidationError(self.error_messages['invalid']) + + if value[:6] == '000000' or value[12:] == '0000': + raise ValidationError(self.error_messages['invalid']) + + return '%s.%s.%s.%s' % (value[:2], value[2:6], value[6:12], value[12:]) diff --git a/django/contrib/localflavor/id/id_choices.py b/django/contrib/localflavor/id/id_choices.py new file mode 100644 index 0000000000..ed1ea017b9 --- /dev/null +++ b/django/contrib/localflavor/id/id_choices.py @@ -0,0 +1,101 @@ +from django.utils.translation import ugettext_lazy as _ + +# Reference: http://id.wikipedia.org/wiki/Daftar_provinsi_Indonesia + +# Indonesia does not have an official Province code standard. +# I decided to use unambiguous and consistent (some are common) 3-letter codes. + +PROVINCE_CHOICES = ( + ('BLI', _('Bali')), + ('BTN', _('Banten')), + ('BKL', _('Bengkulu')), + ('DIY', _('Yogyakarta')), + ('JKT', _('Jakarta')), + ('GOR', _('Gorontalo')), + ('JMB', _('Jambi')), + ('JBR', _('Jawa Barat')), + ('JTG', _('Jawa Tengah')), + ('JTM', _('Jawa Timur')), + ('KBR', _('Kalimantan Barat')), + ('KSL', _('Kalimantan Selatan')), + ('KTG', _('Kalimantan Tengah')), + ('KTM', _('Kalimantan Timur')), + ('BBL', _('Kepulauan Bangka-Belitung')), + ('KRI', _('Kepulauan Riau')), + ('LPG', _('Lampung')), + ('MLK', _('Maluku')), + ('MUT', _('Maluku Utara')), + ('NAD', _('Nanggroe Aceh Darussalam')), + ('NTB', _('Nusa Tenggara Barat')), + ('NTT', _('Nusa Tenggara Timur')), + ('PPA', _('Papua')), + ('PPB', _('Papua Barat')), + ('RIU', _('Riau')), + ('SLB', _('Sulawesi Barat')), + ('SLS', _('Sulawesi Selatan')), + ('SLT', _('Sulawesi Tengah')), + ('SLR', _('Sulawesi Tenggara')), + ('SLU', _('Sulawesi Utara')), + ('SMB', _('Sumatera Barat')), + ('SMS', _('Sumatera Selatan')), + ('SMU', _('Sumatera Utara')), +) + +LICENSE_PLATE_PREFIX_CHOICES = ( + ('A', _('Banten')), + ('AA', _('Magelang')), + ('AB', _('Yogyakarta')), + ('AD', _('Surakarta - Solo')), + ('AE', _('Madiun')), + ('AG', _('Kediri')), + ('B', _('Jakarta')), + ('BA', _('Sumatera Barat')), + ('BB', _('Tapanuli')), + ('BD', _('Bengkulu')), + ('BE', _('Lampung')), + ('BG', _('Sumatera Selatan')), + ('BH', _('Jambi')), + ('BK', _('Sumatera Utara')), + ('BL', _('Nanggroe Aceh Darussalam')), + ('BM', _('Riau')), + ('BN', _('Kepulauan Bangka Belitung')), + ('BP', _('Kepulauan Riau')), + ('CC', _('Corps Consulate')), + ('CD', _('Corps Diplomatic')), + ('D', _('Bandung')), + ('DA', _('Kalimantan Selatan')), + ('DB', _('Sulawesi Utara Daratan')), + ('DC', _('Sulawesi Barat')), + ('DD', _('Sulawesi Selatan')), + ('DE', _('Maluku')), + ('DG', _('Maluku Utara')), + ('DH', _('NTT - Timor')), + ('DK', _('Bali')), + ('DL', _('Sulawesi Utara Kepulauan')), + ('DM', _('Gorontalo')), + ('DN', _('Sulawesi Tengah')), + ('DR', _('NTB - Lombok')), + ('DS', _('Papua dan Papua Barat')), + ('DT', _('Sulawesi Tenggara')), + ('E', _('Cirebon')), + ('EA', _('NTB - Sumbawa')), + ('EB', _('NTT - Flores')), + ('ED', _('NTT - Sumba')), + ('F', _('Bogor')), + ('G', _('Pekalongan')), + ('H', _('Semarang')), + ('K', _('Pati')), + ('KB', _('Kalimantan Barat')), + ('KH', _('Kalimantan Tengah')), + ('KT', _('Kalimantan Timur')), + ('L', _('Surabaya')), + ('M', _('Madura')), + ('N', _('Malang')), + ('P', _('Jember')), + ('R', _('Banyumas')), + ('RI', _('Federal Government')), + ('S', _('Bojonegoro')), + ('T', _('Purwakarta')), + ('W', _('Sidoarjo')), + ('Z', _('Garut')), +) diff --git a/django/contrib/localflavor/ie/__init__.py b/django/contrib/localflavor/ie/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/ie/forms.py b/django/contrib/localflavor/ie/forms.py new file mode 100644 index 0000000000..2cfd2f2bcc --- /dev/null +++ b/django/contrib/localflavor/ie/forms.py @@ -0,0 +1,13 @@ +""" +UK-specific Form helpers +""" + +from django.forms.fields import Select + +class IECountySelect(Select): + """ + A Select widget that uses a list of Irish Counties as its choices. + """ + def __init__(self, attrs=None): + from ie_counties import IE_COUNTY_CHOICES + super(IECountySelect, self).__init__(attrs, choices=IE_COUNTY_CHOICES) diff --git a/django/contrib/localflavor/ie/ie_counties.py b/django/contrib/localflavor/ie/ie_counties.py new file mode 100644 index 0000000000..c8991e01ae --- /dev/null +++ b/django/contrib/localflavor/ie/ie_counties.py @@ -0,0 +1,40 @@ +""" +Sources: + Irish Counties: http://en.wikipedia.org/wiki/Counties_of_Ireland +""" +from django.utils.translation import ugettext_lazy as _ + +IE_COUNTY_CHOICES = ( + ('antrim', _('Antrim')), + ('armagh', _('Armagh')), + ('carlow', _('Carlow')), + ('cavan', _('Cavan')), + ('clare', _('Clare')), + ('cork', _('Cork')), + ('derry', _('Derry')), + ('donegal', _('Donegal')), + ('down', _('Down')), + ('dublin', _('Dublin')), + ('fermanagh', _('Fermanagh')), + ('galway', _('Galway')), + ('kerry', _('Kerry')), + ('kildare', _('Kildare')), + ('kilkenny', _('Kilkenny')), + ('laois', _('Laois')), + ('leitrim', _('Leitrim')), + ('limerick', _('Limerick')), + ('longford', _('Longford')), + ('louth', _('Louth')), + ('mayo', _('Mayo')), + ('meath', _('Meath')), + ('monaghan', _('Monaghan')), + ('offaly', _('Offaly')), + ('roscommon', _('Roscommon')), + ('sligo', _('Sligo')), + ('tipperary', _('Tipperary')), + ('tyrone', _('Tyrone')), + ('waterford', _('Waterford')), + ('westmeath', _('Westmeath')), + ('wexford', _('Wexford')), + ('wicklow', _('Wicklow')), +) diff --git a/django/contrib/localflavor/kw/__init__.py b/django/contrib/localflavor/kw/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/kw/forms.py b/django/contrib/localflavor/kw/forms.py new file mode 100644 index 0000000000..32d6cb990a --- /dev/null +++ b/django/contrib/localflavor/kw/forms.py @@ -0,0 +1,61 @@ +""" +Kuwait-specific Form helpers +""" +import re +from datetime import date +from django.forms import ValidationError +from django.forms.fields import Field, RegexField, EMPTY_VALUES +from django.utils.translation import gettext as _ + +id_re = re.compile(r'^(?P\d{1})(?P\d\d)(?P\d\d)(?P
    \d\d)(?P\d{4})(?P\d{1})') + +class KWCivilIDNumberField(Field): + """ + Kuwaiti Civil ID numbers are 12 digits, second to seventh digits + represents the person's birthdate. + + Checks the following rules to determine the validty of the number: + * The number consist of 12 digits. + * The birthdate of the person is a valid date. + * The calculated checksum equals to the last digit of the Civil ID. + """ + default_error_messages = { + 'invalid': _('Enter a valid Kuwaiti Civil ID number'), + } + + def has_valid_checksum(self, value): + weight = (2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2) + calculated_checksum = 0 + for i in range(11): + calculated_checksum += int(value[i]) * weight[i] + + remainder = calculated_checksum % 11 + checkdigit = 11 - remainder + if checkdigit != int(value[11]): + return False + return True + + def clean(self, value): + super(KWCivilIDNumberField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + + if not re.match(r'^\d{12}$', value): + raise ValidationError(self.error_messages['invalid']) + + match = re.match(id_re, value) + + if not match: + raise ValidationError(self.error_messages['invalid']) + + gd = match.groupdict() + + try: + d = date(int(gd['yy']), int(gd['mm']), int(gd['dd'])) + except ValueError: + raise ValidationError(self.error_messages['invalid']) + + if not self.has_valid_checksum(value): + raise ValidationError(self.error_messages['invalid']) + + return value diff --git a/django/contrib/localflavor/pt/__init__.py b/django/contrib/localflavor/pt/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/pt/forms.py b/django/contrib/localflavor/pt/forms.py new file mode 100644 index 0000000000..86833fc85f --- /dev/null +++ b/django/contrib/localflavor/pt/forms.py @@ -0,0 +1,47 @@ +""" +PT-specific Form helpers +""" + +from django.forms import ValidationError +from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES +from django.utils.encoding import smart_unicode +from django.utils.translation import ugettext_lazy as _ +import re + +phone_digits_re = re.compile(r'^(\d{9}|(00|\+)\d*)$') + + +class PTZipCodeField(RegexField): + default_error_messages = { + 'invalid': _('Enter a zip code in the format XXXX-XXX.'), + } + + def __init__(self, *args, **kwargs): + super(PTZipCodeField, self).__init__(r'^(\d{4}-\d{3}|\d{7})$', + max_length=None, min_length=None, *args, **kwargs) + + def clean(self,value): + cleaned = super(PTZipCodeField, self).clean(value) + if len(cleaned) == 7: + return u'%s-%s' % (cleaned[:4],cleaned[4:]) + else: + return cleaned + +class PTPhoneNumberField(Field): + """ + Validate local Portuguese phone number (including international ones) + It should have 9 digits (may include spaces) or start by 00 or + (international) + """ + default_error_messages = { + 'invalid': _('Phone numbers must have 9 digits, or start by + or 00.'), + } + + def clean(self, value): + super(PTPhoneNumberField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + value = re.sub('(\.|\s)', '', smart_unicode(value)) + m = phone_digits_re.search(value) + if m: + return u'%s' % value + raise ValidationError(self.error_messages['invalid']) diff --git a/django/contrib/localflavor/uk/forms.py b/django/contrib/localflavor/uk/forms.py index 7df736444a..aafe9734db 100644 --- a/django/contrib/localflavor/uk/forms.py +++ b/django/contrib/localflavor/uk/forms.py @@ -33,7 +33,7 @@ class UKPostcodeField(CharField): # 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']) + raise ValidationError(self.error_messages['invalid']) return postcode class UKCountySelect(Select): diff --git a/django/contrib/localflavor/us/forms.py b/django/contrib/localflavor/us/forms.py index 7879e323f6..dc6daaf165 100644 --- a/django/contrib/localflavor/us/forms.py +++ b/django/contrib/localflavor/us/forms.py @@ -4,7 +4,7 @@ USA-specific Form helpers from django.core.validators import EMPTY_VALUES from django.forms import ValidationError -from django.forms.fields import Field, RegexField, Select +from django.forms.fields import Field, RegexField, Select, CharField from django.utils.encoding import smart_unicode from django.utils.translation import ugettext_lazy as _ import re @@ -21,7 +21,7 @@ class USZipCodeField(RegexField): super(USZipCodeField, self).__init__(r'^\d{5}(?:-\d{4})?$', max_length=None, min_length=None, *args, **kwargs) -class USPhoneNumberField(Field): +class USPhoneNumberField(CharField): default_error_messages = { 'invalid': u'Phone numbers must be in XXX-XXX-XXXX format.', } diff --git a/django/contrib/localflavor/us/models.py b/django/contrib/localflavor/us/models.py index 4cb6e6851e..1f78e4504a 100644 --- a/django/contrib/localflavor/us/models.py +++ b/django/contrib/localflavor/us/models.py @@ -1,6 +1,6 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ -from django.db.models.fields import Field, CharField +from django.db.models.fields import CharField from django.contrib.localflavor.us.us_states import STATE_CHOICES class USStateField(CharField): @@ -12,22 +12,16 @@ class USStateField(CharField): kwargs['max_length'] = 2 super(USStateField, self).__init__(*args, **kwargs) -class PhoneNumberField(Field): +class PhoneNumberField(CharField): description = _("Phone number") - def get_internal_type(self): - return "PhoneNumberField" - - def db_type(self, connection): - if connection.settings_dict['ENGINE'] == 'django.db.backends.oracle': - return 'VARCHAR2(20)' - else: - return 'varchar(20)' + def __init__(self, *args, **kwargs): + kwargs['max_length'] = 20 + super(PhoneNumberField, self).__init__(*args, **kwargs) def formfield(self, **kwargs): from django.contrib.localflavor.us.forms import USPhoneNumberField defaults = {'form_class': USPhoneNumberField} defaults.update(kwargs) return super(PhoneNumberField, self).formfield(**defaults) - diff --git a/django/contrib/localflavor/uy/__init__.py b/django/contrib/localflavor/uy/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/uy/forms.py b/django/contrib/localflavor/uy/forms.py new file mode 100644 index 0000000000..5b47ba1e24 --- /dev/null +++ b/django/contrib/localflavor/uy/forms.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +""" +UY-specific form helpers. +""" +import re + +from django.forms.fields import Select, RegexField, EMPTY_VALUES +from django.forms import ValidationError +from django.utils.translation import ugettext_lazy as _ +from django.contrib.localflavor.uy.util import get_validation_digit + + +class UYDepartamentSelect(Select): + """ + A Select widget that uses a list of Uruguayan departaments as its choices. + """ + def __init__(self, attrs=None): + from uy_departaments import DEPARTAMENT_CHOICES + super(UYDepartamentSelect, self).__init__(attrs, choices=DEPARTAMENT_CHOICES) + + +class UYCIField(RegexField): + """ + A field that validates Uruguayan 'Cedula de identidad' (CI) numbers. + """ + default_error_messages = { + 'invalid': _("Enter a valid CI number in X.XXX.XXX-X," + "XXXXXXX-X or XXXXXXXX format."), + 'invalid_validation_digit': _("Enter a valid CI number."), + } + + def __init__(self, *args, **kwargs): + super(UYCIField, self).__init__(r'(?P(\d{6,7}|(\d\.)?\d{3}\.\d{3}))-?(?P\d)', + *args, **kwargs) + + def clean(self, value): + """ + Validates format and validation digit. + + The official format is [X.]XXX.XXX-X but usually dots and/or slash are + omitted so, when validating, those characters are ignored if found in + the correct place. The three typically used formats are supported: + [X]XXXXXXX, [X]XXXXXX-X and [X.]XXX.XXX-X. + """ + + value = super(UYCIField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + match = self.regex.match(value) + if not match: + raise ValidationError(self.error_messages['invalid']) + + number = int(match.group('num').replace('.', '')) + validation_digit = int(match.group('val')) + + if not validation_digit == get_validation_digit(number): + raise ValidationError(self.error_messages['invalid_validation_digit']) + + return value diff --git a/django/contrib/localflavor/uy/util.py b/django/contrib/localflavor/uy/util.py new file mode 100644 index 0000000000..0c1a8f84be --- /dev/null +++ b/django/contrib/localflavor/uy/util.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +def get_validation_digit(number): + """ Calculates the validation digit for the given number. """ + sum = 0 + dvs = [4, 3, 6, 7, 8, 9, 2] + number = str(number) + + for i in range(0, len(number)): + sum = (int(number[-1 - i]) * dvs[i] + sum) % 10 + + return (10-sum) % 10 diff --git a/django/contrib/localflavor/uy/uy_departaments.py b/django/contrib/localflavor/uy/uy_departaments.py new file mode 100644 index 0000000000..97795f8e82 --- /dev/null +++ b/django/contrib/localflavor/uy/uy_departaments.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +"""A list of Urguayan departaments as `choices` in a formfield.""" + +DEPARTAMENT_CHOICES = ( + ('G', u'Artigas'), + ('A', u'Canelones'), + ('E', u'Cerro Largo'), + ('L', u'Colonia'), + ('Q', u'Durazno'), + ('N', u'Flores'), + ('O', u'Florida'), + ('P', u'Lavalleja'), + ('B', u'Maldonado'), + ('S', u'Montevideo'), + ('I', u'Paysandú'), + ('J', u'Río Negro'), + ('F', u'Rivera'), + ('C', u'Rocha'), + ('H', u'Salto'), + ('M', u'San José'), + ('K', u'Soriano'), + ('R', u'Tacuarembó'), + ('D', u'Treinta y Tres'), +) diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py index 4fd6ba0c8d..46204a7508 100644 --- a/django/core/management/commands/test.py +++ b/django/core/management/commands/test.py @@ -17,7 +17,7 @@ class Command(BaseCommand): def handle(self, *test_labels, **options): from django.conf import settings from django.test.utils import get_runner - + verbosity = int(options.get('verbosity', 1)) interactive = options.get('interactive', True) failfast = options.get('failfast', False) @@ -25,10 +25,10 @@ class Command(BaseCommand): # Some custom test runners won't accept the failfast flag, so let's make sure they accept it before passing it to them if 'failfast' in test_runner.func_code.co_varnames: - failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, + failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, failfast=failfast) else: failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive) if failures: - sys.exit(failures) + sys.exit(bool(failures)) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index aabd1b017d..c16c239de1 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -10,8 +10,7 @@ from django.utils.html import escape, conditional_escape from django.utils.translation import ugettext from django.utils.encoding import StrAndUnicode, force_unicode from django.utils.safestring import mark_safe -from django.utils.formats import localize -from django.utils import datetime_safe +from django.utils import datetime_safe, formats from datetime import time from util import flatatt from urlparse import urljoin @@ -209,7 +208,7 @@ class Input(Widget): final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) if value != '': # Only add the 'value' attribute if a value is non-empty. - final_attrs['value'] = force_unicode(localize(value, is_input=True)) + final_attrs['value'] = force_unicode(formats.localize_input(value)) return mark_safe(u'' % flatatt(final_attrs)) class TextInput(Input): @@ -284,7 +283,7 @@ class Textarea(Widget): class DateInput(Input): input_type = 'text' - format = '%Y-%m-%d' # '2006-10-25' + format = None def __init__(self, attrs=None, format=None): super(DateInput, self).__init__(attrs) @@ -295,8 +294,7 @@ class DateInput(Input): if value is None: return '' elif hasattr(value, 'strftime'): - value = datetime_safe.new_date(value) - return value.strftime(self.format) + return formats.localize_input(value, self.format) return value def render(self, name, value, attrs=None): @@ -308,7 +306,7 @@ class DateInput(Input): class DateTimeInput(Input): input_type = 'text' - format = '%Y-%m-%d %H:%M:%S' # '2006-10-25 14:30:59' + format = None def __init__(self, attrs=None, format=None): super(DateTimeInput, self).__init__(attrs) @@ -319,8 +317,7 @@ class DateTimeInput(Input): if value is None: return '' elif hasattr(value, 'strftime'): - value = datetime_safe.new_datetime(value) - return value.strftime(self.format) + return formats.localize_input(value, self.format) return value def render(self, name, value, attrs=None): @@ -332,7 +329,7 @@ class DateTimeInput(Input): class TimeInput(Input): input_type = 'text' - format = '%H:%M:%S' # '14:30:59' + format = None def __init__(self, attrs=None, format=None): super(TimeInput, self).__init__(attrs) @@ -343,7 +340,7 @@ class TimeInput(Input): if value is None: return '' elif hasattr(value, 'strftime'): - return value.strftime(self.format) + return formats.localize_input(value, self.format) return value def render(self, name, value, attrs=None): diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 26b6b5ec4e..0ba16bc9a5 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -15,10 +15,10 @@ except ImportError: from django.template import Variable, Library from django.conf import settings +from django.utils import formats from django.utils.translation import ugettext, ungettext from django.utils.encoding import force_unicode, iri_to_uri from django.utils.safestring import mark_safe, SafeData -from django.utils.formats import date_format, number_format register = Library() @@ -167,14 +167,14 @@ def floatformat(text, arg=-1): return input_val if not m and p < 0: - return mark_safe(number_format(u'%d' % (int(d)), 0)) + return mark_safe(formats.number_format(u'%d' % (int(d)), 0)) if p == 0: exp = Decimal(1) else: exp = Decimal('1.0') / (Decimal(10) ** abs(p)) try: - return mark_safe(number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p))) + return mark_safe(formats.number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p))) except InvalidOperation: return input_val floatformat.is_safe = True @@ -686,7 +686,7 @@ def date(value, arg=None): if arg is None: arg = settings.DATE_FORMAT try: - return date_format(value, arg) + return formats.date_format(value, arg) except AttributeError: try: return format(value, arg) @@ -696,16 +696,16 @@ date.is_safe = False def time(value, arg=None): """Formats a time according to the given format.""" - from django.utils.dateformat import time_format + from django.utils import dateformat if value in (None, u''): return u'' if arg is None: arg = settings.TIME_FORMAT try: - return date_format(value, arg) + return formats.time_format(value, arg) except AttributeError: try: - return time_format(value, arg) + return dateformat.time_format(value, arg) except AttributeError: return '' time.is_safe = False diff --git a/django/test/simple.py b/django/test/simple.py index 092ef4bde2..b01ecad7f4 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -1,4 +1,7 @@ +import sys +import signal import unittest + from django.conf import settings from django.db.models import get_app, get_apps from django.test import _doctest as doctest @@ -11,22 +14,50 @@ TEST_MODULE = 'tests' doctestOutputChecker = OutputChecker() class DjangoTestRunner(unittest.TextTestRunner): - + def __init__(self, verbosity=0, failfast=False, **kwargs): super(DjangoTestRunner, self).__init__(verbosity=verbosity, **kwargs) self.failfast = failfast - + self._keyboard_interrupt_intercepted = False + + def run(self, *args, **kwargs): + """ + Runs the test suite after registering a custom signal handler + that triggers a graceful exit when Ctrl-C is pressed. + """ + self._default_keyboard_interrupt_handler = signal.signal(signal.SIGINT, + self._keyboard_interrupt_handler) + try: + result = super(DjangoTestRunner, self).run(*args, **kwargs) + finally: + signal.signal(signal.SIGINT, self._default_keyboard_interrupt_handler) + return result + + def _keyboard_interrupt_handler(self, signal_number, stack_frame): + """ + Handles Ctrl-C by setting a flag that will stop the test run when + the currently running test completes. + """ + self._keyboard_interrupt_intercepted = True + sys.stderr.write(" ") + # Set the interrupt handler back to the default handler, so that + # another Ctrl-C press will trigger immediate exit. + signal.signal(signal.SIGINT, self._default_keyboard_interrupt_handler) + def _makeResult(self): result = super(DjangoTestRunner, self)._makeResult() failfast = self.failfast - + def stoptest_override(func): def stoptest(test): - if failfast and not result.wasSuccessful(): + # If we were set to failfast and the unit test failed, + # or if the user has typed Ctrl-C, report and quit + if (failfast and not result.wasSuccessful()) or \ + self._keyboard_interrupt_intercepted: result.stop() func(test) return stoptest - + setattr(result, 'stopTest', stoptest_override(result.stopTest)) return result diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 06cf6c6b75..adcc3546d0 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -1,3 +1,5 @@ +from types import GeneratorType + from django.utils.copycompat import deepcopy @@ -65,6 +67,11 @@ class SortedDict(dict): def __init__(self, data=None): if data is None: data = {} + elif isinstance(data, GeneratorType): + # Unfortunately we need to be able to read a generator twice. Once + # to get the data into self with our super().__init__ call and a + # second time to setup keyOrder correctly + data = list(data) super(SortedDict, self).__init__(data) if isinstance(data, dict): self.keyOrder = data.keys() diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index b2ea71228c..13cc2c7a81 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -19,7 +19,7 @@ from django.utils.tzinfo import LocalTimezone from django.utils.translation import ugettext as _ from django.utils.encoding import force_unicode -re_formatchars = re.compile(r'(? 2: + return language[:p].lower()+'_'+language[p+1].upper()+language[p+2:].lower() return language[:p].lower()+'_'+language[p+1:].upper() else: return language.lower() diff --git a/django/views/i18n.py b/django/views/i18n.py index ddd75203a7..6a1ee514cd 100644 --- a/django/views/i18n.py +++ b/django/views/i18n.py @@ -6,6 +6,7 @@ from django.conf import settings from django.utils import importlib from django.utils.translation import check_for_language, activate, to_locale, get_language from django.utils.text import javascript_quote +from django.utils.encoding import smart_unicode from django.utils.formats import get_format_modules def set_language(request): @@ -36,15 +37,17 @@ def set_language(request): def get_formats(): """ - Returns an iterator over all formats in formats file + Returns all formats strings required for i18n to work """ - FORMAT_SETTINGS = ('DATE_FORMAT', 'DATETIME_FORMAT', 'TIME_FORMAT', + FORMAT_SETTINGS = ( + 'DATE_FORMAT', 'DATETIME_FORMAT', 'TIME_FORMAT', 'YEAR_MONTH_FORMAT', 'MONTH_DAY_FORMAT', 'SHORT_DATE_FORMAT', 'SHORT_DATETIME_FORMAT', 'FIRST_DAY_OF_WEEK', 'DECIMAL_SEPARATOR', - 'THOUSAND_SEPARATOR', 'NUMBER_GROUPING') - + 'THOUSAND_SEPARATOR', 'NUMBER_GROUPING', + 'DATE_INPUT_FORMATS', 'TIME_INPUT_FORMATS', 'DATETIME_INPUT_FORMATS' + ) result = {} - for module in [settings] + get_format_modules(): + for module in [settings] + get_format_modules(reverse=True): for attr in FORMAT_SETTINGS: try: result[attr] = getattr(module, attr) @@ -140,7 +143,7 @@ def javascript_catalog(request, domain='djangojs', packages=None): activate(request.GET['language']) if packages is None: packages = ['django.conf'] - if type(packages) in (str, unicode): + if isinstance(packages, basestring): packages = packages.split('+') packages = [p for p in packages if p == 'django.conf' or p in settings.INSTALLED_APPS] default_locale = to_locale(settings.LANGUAGE_CODE) @@ -194,9 +197,9 @@ def javascript_catalog(request, domain='djangojs', packages=None): for k, v in t.items(): if k == '': continue - if type(k) in (str, unicode): + if isinstance(k, basestring): csrc.append("catalog['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(v))) - elif type(k) == tuple: + elif isinstance(k, tuple): if k[0] not in pdict: pdict[k[0]] = k[1] else: @@ -208,7 +211,11 @@ def javascript_catalog(request, domain='djangojs', packages=None): for k, v in pdict.items(): src.append("catalog['%s'] = [%s];\n" % (javascript_quote(k), ','.join(["''"]*(v+1)))) for k, v in get_formats().items(): - src.append("catalog['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(unicode(v)))) + if isinstance(v, (basestring, int)): + src.append("catalog['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(smart_unicode(v)))) + elif isinstance(v, (tuple, list)): + v = [javascript_quote(smart_unicode(value)) for value in v] + src.append("catalog['%s'] = ['%s'];\n" % (javascript_quote(k), "', '".join(v))) src.extend(csrc) src.append(LibFoot) src.append(InterPolate) diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index 660b7c623b..1c58e2d5e8 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -50,13 +50,17 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are: * Germany_ * Iceland_ * India_ + * Indonesia_ + * Ireland_ * Italy_ * Japan_ + * Kuwait_ * Mexico_ * `The Netherlands`_ * Norway_ * Peru_ * Poland_ + * Portugal_ * Romania_ * Slovakia_ * `South Africa`_ @@ -65,6 +69,7 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are: * Switzerland_ * `United Kingdom`_ * `United States of America`_ + * Uruguay_ The ``django.contrib.localflavor`` package also includes a ``generic`` subpackage, containing useful code that is not specific to one particular country or culture. @@ -92,12 +97,16 @@ Here's an example of how to use them:: .. _The Netherlands: `The Netherlands (nl)`_ .. _Iceland: `Iceland (is\_)`_ .. _India: `India (in\_)`_ +.. _Indonesia: `Indonesia (id)`_ +.. _Ireland: `Ireland (ie)`_ .. _Italy: `Italy (it)`_ .. _Japan: `Japan (jp)`_ +.. _Kuwait: `Kuwait (kw)`_ .. _Mexico: `Mexico (mx)`_ .. _Norway: `Norway (no)`_ .. _Peru: `Peru (pe)`_ .. _Poland: `Poland (pl)`_ +.. _Portugal: `Portugal (pt)`_ .. _Romania: `Romania (ro)`_ .. _Slovakia: `Slovakia (sk)`_ .. _South Africa: `South Africa (za)`_ @@ -106,6 +115,7 @@ Here's an example of how to use them:: .. _Switzerland: `Switzerland (ch)`_ .. _United Kingdom: `United Kingdom (uk)`_ .. _United States of America: `United States of America (us)`_ +.. _Uruguay: `Uruguay (uy)`_ Adding flavors ============== @@ -369,6 +379,46 @@ India (``in_``) A ``Select`` widget that uses a list of Indian states/territories as its choices. +Ireland (``ie``) +================ + +.. class:: ie.forms.IECountySelect + + A ``Select`` widget that uses a list of Irish Counties as its choices. + +Indonesia (``id``) +================== + +.. class:: id.forms.IDPostCodeField + + A form field that validates input as an Indonesian post code field. + +.. class:: id.forms.IDProvinceSelect + + A ``Select`` widget that uses a list of Indonesian provinces as its choices. + +.. class:: id.forms.IDPhoneNumberField + + A form field that validates input as an Indonesian telephone number. + +.. class:: id.forms.IDLicensePlatePrefixSelect + + A ``Select`` widget that uses a list of Indonesian license plate + prefix code as its choices. + +.. class:: id.forms.IDLicensePlateField + + A form field that validates input as an Indonesian vehicle license plate. + +.. class:: id.forms.IDNationalIdentityNumberField + + A form field that validates input as an Indonesian national identity + number (`NIK`_/KTP). The output will be in the format of + 'XX.XXXX.DDMMYY.XXXX'. Dots or spaces can be used in the input to break + down the numbers. + +.. _NIK: http://en.wikipedia.org/wiki/Indonesian_identity_card + Italy (``it``) ============== @@ -408,6 +458,18 @@ Japan (``jp``) A ``Select`` widget that uses a list of Japanese prefectures as its choices. +Kuwait (``kw``) +=============== + +.. class:: kw.forms.KWCivilIDNumberField + + A form field that validates input as a Kuwaiti Civil ID number. A valid + Civil ID number must obey the following rules: + + * The number consist of 12 digits. + * The birthdate of the person is a valid date. + * The calculated checksum equals to the last digit of the Civil ID. + Mexico (``mx``) =============== @@ -438,31 +500,31 @@ Norway (``no``) Peru (``pe``) ============= -.. class:: pt.forms.PEDNIField +.. class:: pe.forms.PEDNIField A form field that validates input as a DNI (Peruvian national identity) number. -.. class:: pt.forms.PERUCField +.. class:: pe.forms.PERUCField A form field that validates input as an RUC (Registro Unico de Contribuyentes) number. Valid RUC numbers have 11 digits. -.. class:: pt.forms.PEDepartmentSelect +.. class:: pe.forms.PEDepartmentSelect A ``Select`` widget that uses a list of Peruvian Departments as its choices. Poland (``pl``) =============== -.. class:: pl.forms.PLNationalIdentificationNumberField +.. class:: pl.forms.PLPESELField A form field that validates input as a Polish national identification number (PESEL_). .. _PESEL: http://en.wikipedia.org/wiki/PESEL -.. class:: pl.forms.PLNationalBusinessRegisterField +.. class:: pl.forms.PLREGONField A form field that validates input as a Polish National Official Business Register Number (REGON_), having either seven or nine digits. The checksum @@ -476,22 +538,35 @@ Poland (``pl``) A form field that validates input as a Polish postal code. The valid format is XX-XXX, where X is a digit. -.. class:: pl.forms.PLTaxNumberField +.. class:: pl.forms.PLNIPField A form field that validates input as a Polish Tax Number (NIP). Valid formats are XXX-XXX-XX-XX or XX-XX-XXX-XXX. The checksum algorithm used for NIPs is documented at http://wipos.p.lodz.pl/zylla/ut/nip-rego.html. -.. class:: pl.forms.PLAdministrativeUnitSelect +.. class:: pl.forms.PLCountySelect A ``Select`` widget that uses a list of Polish administrative units as its choices. -.. class:: pl.forms.PLVoivodeshipSelect +.. class:: pl.forms.PLProvinceSelect A ``Select`` widget that uses a list of Polish voivodeships (administrative provinces) as its choices. +Portugal (``pt``) +================= + +.. class:: pt.forms.PTZipCodeField + + A form field that validates input as a Portuguese zip code. + +.. class:: pt.forms.PTPhoneNumberField + + A form field that validates input as a Portuguese phone number. + Valid numbers have 9 digits (may include spaces) or start by 00 + or + (international). + Romania (``ro``) ================ @@ -738,3 +813,15 @@ United States of America (``us``) A model field that forms represent as a ``forms.USStateField`` field and stores the two-letter U.S. state abbreviation in the database. + +Uruguay (``uy``) +================ + +.. class:: uy.forms.UYCIField + + A field that validates Uruguayan 'Cedula de identidad' (CI) numbers. + +.. class:: uy.forms.UYDepartamentSelect + + A ``Select`` widget that uses a list of Uruguayan departaments as its + choices. diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 2cd879423c..88715ebdc7 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -812,12 +812,10 @@ test Runs tests for all installed models. See :ref:`topics-testing` for more information. ---failfast -~~~~~~~~~~ - .. versionadded:: 1.2 +.. django-admin-option:: --failfast -Use the ``--failfast`` option to stop running tests and report the failure +Use the :djadminopt:`--failfast` option to stop running tests and report the failure immediately after a test fails. testserver diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 8cbc9ccc8f..daedc87d21 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -219,7 +219,7 @@ SQLite. This can be configured using the following:: DATABASES = { 'default': { - 'BACKEND': 'django.db.backends.sqlite3', + 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'mydatabase' } } diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index b7afa15f7f..51ef03c886 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -637,6 +637,7 @@ Available format strings: A ``'AM'`` or ``'PM'``. ``'AM'`` b Month, textual, 3 letters, lowercase. ``'jan'`` B Not implemented. + c ISO 8601 Format. ``2008-01-02 10:30:00.000123`` d Day of the month, 2 digits with ``'01'`` to ``'31'`` leading zeros. D Day of the week, textual, 3 letters. ``'Fri'`` @@ -673,6 +674,7 @@ Available format strings: month, 2 characters. t Number of days in the given month. ``28`` to ``31`` T Time zone of this machine. ``'EST'``, ``'MDT'`` + u Microseconds. ``0`` to ``999999`` U Seconds since the Unix Epoch (January 1 1970 00:00:00 UTC). w Day of the week, digits without ``'0'`` (Sunday) to ``'6'`` (Saturday) diff --git a/docs/releases/1.1.2.txt b/docs/releases/1.1.2.txt index 64bbdd5dde..4c887ad4a5 100644 --- a/docs/releases/1.1.2.txt +++ b/docs/releases/1.1.2.txt @@ -19,8 +19,21 @@ development or deployment currently using or targeting Django 1.1. For full details on the new features, backwards incompatibilities, and deprecated features in the 1.1 branch, see the :ref:`releases-1.1`. +Backwards-incompatible changes in 1.1.2 +======================================= + +Test runner exit status code +---------------------------- + +The exit status code of the test runners (``tests/runtests.py`` and ``python +manage.py test``) no longer represents the number of failed tests, since a +failure of 256 or more tests resulted in a wrong exit status code. The exit +status code for the test runner is now 0 for success (no failing tests) and 1 +for any number of test failures. If needed, the number of test failures can be +found at the end of the test runner's output. + One new feature ---------------- +=============== Ordinarily, a point release would not include new features, but in the case of Django 1.1.2, we have made an exception to this rule. Django diff --git a/docs/releases/1.2.txt b/docs/releases/1.2.txt index 8810963eaa..a549bd0cce 100644 --- a/docs/releases/1.2.txt +++ b/docs/releases/1.2.txt @@ -232,6 +232,16 @@ party packages, or that you wrote yourself, you should ensure that the information, see :ref:`template tag thread safety considerations`. +Test runner exit status code +---------------------------- + +The exit status code of the test runners (``tests/runtests.py`` and ``python +manage.py test``) no longer represents the number of failed tests, since a +failure of 256 or more tests resulted in a wrong exit status code. The exit +status code for the test runner is now 0 for success (no failing tests) and 1 +for any number of test failures. If needed, the number of test failures can be +found at the end of the test runner's output. + .. _deprecated-features-1.2: Features deprecated in 1.2 @@ -471,10 +481,12 @@ Models can now use a 64 bit :class:`~django.db.models.BigIntegerField` type. Fast Failure for Tests ---------------------- -The ``test`` subcommand of ``django-admin.py``, and the ``runtests.py`` script -used to run Django's own test suite, support a new ``--failfast`` option. -When specified, this option causes the test runner to exit after -encountering a failure instead of continuing with the test run. +The :djadmin:`test` subcommand of ``django-admin.py``, and the ``runtests.py`` +script used to run Django's own test suite, support a new ``--failfast`` option. +When specified, this option causes the test runner to exit after encountering +a failure instead of continuing with the test run. In addition, the handling +of ``Ctrl-C`` during a test run has been improved to trigger a graceful exit +from the test run that reports details of the tests run before the interruption. Improved localization --------------------- diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 8aae4f5e73..9874135b02 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -34,7 +34,7 @@ Authentication support is bundled as a Django application in 1. Put ``'django.contrib.auth'`` and ``'django.contrib.contenttypes'`` in your :setting:`INSTALLED_APPS` setting. - (The :class:`~django.contrib.auth.models.Permisson` model in + (The :class:`~django.contrib.auth.models.Permission` model in :mod:`django.contrib.auth` depends on :mod:`django.contrib.contenttypes`.) 2. Run the command ``manage.py syncdb``. diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 9ef6cceae8..a309294d7c 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -242,8 +242,8 @@ in different circumstances. Running tests ============= -Once you've written tests, run them using your project's ``manage.py`` -utility:: +Once you've written tests, run them using the :djadmin:`test` subcommand of +your project's ``manage.py`` utility:: $ ./manage.py test @@ -274,6 +274,23 @@ a test case, add the name of the test method to the label:: $ ./manage.py test animals.AnimalTestCase.testFluffyAnimals +.. versionadded:: 1.2 + You can now trigger a graceful exit from a test run by pressing ``Ctrl-C``. + +If you press ``Ctrl-C`` while the tests are running, the test runner will +wait for the currently running test to complete and then exit gracefully. +During a graceful exit the test runner will output details of any test +failures, report on how many tests were run and how many errors and failures +were encountered, and destroy any test databases as usual. Thus pressing +``Ctrl-C`` can be very useful if you forget to pass the :djadminopt:`--failfast` +option, notice that some tests are unexpectedly failing, and want to get details +on the failures without waiting for the full test run to complete. + +If you do not want to wait for the currently running test to finish, you +can press ``Ctrl-C`` a second time and the test run will halt immediately, +but not gracefully. No details of the tests run before the interruption will +be reported, and any test databases created by the run will not be destroyed. + The test database ----------------- diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 320b632537..df43701586 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -63,11 +63,20 @@ class AdminViewBasicTest(TestCase): def testBasicEditGet(self): """ - A smoke test to ensureGET on the change_view works. + A smoke test to ensure GET on the change_view works. """ response = self.client.get('/test_admin/%s/admin_views/section/1/' % self.urlbit) self.failUnlessEqual(response.status_code, 200) + def testBasicEditGetStringPK(self): + """ + A smoke test to ensure GET on the change_view works (returns an HTTP + 404 error, see #11191) when passing a string as the PK argument for a + model with an integer PK field. + """ + response = self.client.get('/test_admin/%s/admin_views/section/abc/' % self.urlbit) + self.failUnlessEqual(response.status_code, 404) + def testBasicAddPost(self): """ A smoke test to ensure POST on add_view works. diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py index 0c81ed3119..b9fe67c182 100644 --- a/tests/regressiontests/admin_widgets/models.py +++ b/tests/regressiontests/admin_widgets/models.py @@ -77,6 +77,9 @@ __test__ = {'WIDGETS_TESTS': """ >>> from django.contrib.admin.widgets import FilteredSelectMultiple, AdminSplitDateTime >>> from django.contrib.admin.widgets import AdminFileWidget, ForeignKeyRawIdWidget, ManyToManyRawIdWidget >>> from django.contrib.admin.widgets import RelatedFieldWidgetWrapper +>>> from django.utils.translation import activate, deactivate +>>> from django.conf import settings + Calling conditional_escape on the output of widget.render will simulate what happens in the template. This is easier than setting up a template and context @@ -94,6 +97,12 @@ HTML escaped. >>> w = AdminSplitDateTime() >>> print conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30)))

    Date:
    Time:

    +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> print conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))) +

    Datum:
    Zeit:

    +>>> deactivate() +>>> settings.USE_L10N = False >>> band = Band.objects.create(pk=1, name='Linkin Park') >>> album = band.album_set.create(name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg') diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py index 85651542bb..5601b49193 100644 --- a/tests/regressiontests/admin_widgets/tests.py +++ b/tests/regressiontests/admin_widgets/tests.py @@ -3,6 +3,7 @@ from django.contrib import admin from django.contrib.admin import widgets from unittest import TestCase from django.test import TestCase as DjangoTestCase +from django.db.models import DateField import models class AdminFormfieldForDBFieldTests(TestCase): @@ -89,7 +90,7 @@ class AdminFormfieldForDBFieldTests(TestCase): def testFormfieldOverrides(self): self.assertFormfield(models.Event, 'start_date', forms.TextInput, - formfield_overrides={'widget': forms.TextInput}) + formfield_overrides={DateField: {'widget': forms.TextInput}}) def testFieldWithChoices(self): self.assertFormfield(models.Member, 'gender', forms.Select) diff --git a/tests/regressiontests/datastructures/tests.py b/tests/regressiontests/datastructures/tests.py index e658f3302e..b7885e65a4 100644 --- a/tests/regressiontests/datastructures/tests.py +++ b/tests/regressiontests/datastructures/tests.py @@ -60,9 +60,9 @@ MultiValueDictKeyError: "Key 'lastname' not found in >> d.setlist('lastname', ['Holovaty', 'Willison']) >>> d.getlist('lastname') ['Holovaty', 'Willison'] ->>> d.values() +>>> d.values() ['Developer', 'Simon', 'Willison'] ->>> list(d.itervalues()) +>>> list(d.itervalues()) ['Developer', 'Simon', 'Willison'] ### SortedDict ################################################################# @@ -95,6 +95,9 @@ True >>> d.pop('one', 'missing') 'missing' +>>> SortedDict((i, i) for i in xrange(3)) +{0: 0, 1: 1, 2: 2} + We don't know which item will be popped in popitem(), so we'll just check that the number of keys has decreased. >>> l = len(d) diff --git a/tests/regressiontests/dateformat/tests.py b/tests/regressiontests/dateformat/tests.py index bbde45b903..42c582cd0f 100644 --- a/tests/regressiontests/dateformat/tests.py +++ b/tests/regressiontests/dateformat/tests.py @@ -39,8 +39,10 @@ class DateFormatTests(TestCase): def test_date_formats(self): my_birthday = datetime.datetime(1979, 7, 8, 22, 00) + timestamp = datetime.datetime(2008, 5, 19, 11, 45, 23, 123456) self.assertEquals(dateformat.format(my_birthday, 'A'), u'PM') + self.assertEquals(dateformat.format(timestamp, 'c'), u'2008-05-19 11:45:23.123456') self.assertEquals(dateformat.format(my_birthday, 'd'), u'08') self.assertEquals(dateformat.format(my_birthday, 'j'), u'8') self.assertEquals(dateformat.format(my_birthday, 'l'), u'Sunday') @@ -79,12 +81,14 @@ class DateFormatTests(TestCase): my_birthday = datetime.datetime(1979, 7, 8, 22, 00) summertime = datetime.datetime(2005, 10, 30, 1, 00) wintertime = datetime.datetime(2005, 10, 30, 4, 00) + timestamp = datetime.datetime(2008, 5, 19, 11, 45, 23, 123456) if self.tz_tests: self.assertEquals(dateformat.format(my_birthday, 'O'), u'+0100') self.assertEquals(dateformat.format(my_birthday, 'r'), u'Sun, 8 Jul 1979 22:00:00 +0100') self.assertEquals(dateformat.format(my_birthday, 'T'), u'CET') self.assertEquals(dateformat.format(my_birthday, 'U'), u'300315600') + self.assertEquals(dateformat.format(timestamp, 'u'), u'123456') self.assertEquals(dateformat.format(my_birthday, 'Z'), u'3600') self.assertEquals(dateformat.format(summertime, 'I'), u'1') self.assertEquals(dateformat.format(summertime, 'O'), u'+0200') diff --git a/tests/regressiontests/forms/localflavor/id.py b/tests/regressiontests/forms/localflavor/id.py new file mode 100644 index 0000000000..9098b9d6c0 --- /dev/null +++ b/tests/regressiontests/forms/localflavor/id.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +# Tests for the contrib/localflavor/ ID form fields. + +tests = r""" + +# IDPhoneNumberField ######################################################## + +>>> from django.contrib.localflavor.id.forms import IDPhoneNumberField +>>> f = IDPhoneNumberField(required=False) +>>> f.clean('') +u'' +>>> f.clean('0812-3456789') +u'0812-3456789' +>>> f.clean('081234567890') +u'081234567890' +>>> f.clean('021 345 6789') +u'021 345 6789' +>>> f.clean('0213456789') +u'0213456789' +>>> f.clean('0123456789') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid phone number'] +>>> f.clean('+62-21-3456789') +u'+62-21-3456789' +>>> f.clean('+62-021-3456789') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid phone number'] +>>> f.clean('(021) 345 6789') +u'(021) 345 6789' +>>> f.clean('+62-021-3456789') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid phone number'] +>>> f.clean('+62-0812-3456789') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid phone number'] +>>> f.clean('0812345678901') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid phone number'] +>>> f.clean('foo') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid phone number'] + +# IDPostCodeField ############################################################ + +>>> from django.contrib.localflavor.id.forms import IDPostCodeField +>>> f = IDPostCodeField(required=False) +>>> f.clean('') +u'' +>>> f.clean('12340') +u'12340' +>>> f.clean('25412') +u'25412' +>>> f.clean(' 12340 ') +u'12340' +>>> f.clean('12 3 4 0') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid post code'] +>>> f.clean('12345') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid post code'] +>>> f.clean('10100') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid post code'] +>>> f.clean('123456') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid post code'] +>>> f.clean('foo') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid post code'] + +# IDNationalIdentityNumberField ######################################################### + +>>> from django.contrib.localflavor.id.forms import IDNationalIdentityNumberField +>>> f = IDNationalIdentityNumberField(required=False) +>>> f.clean('') +u'' +>>> f.clean(' 12.3456.010178 3456 ') +u'12.3456.010178.3456' +>>> f.clean('1234560101783456') +u'12.3456.010178.3456' +>>> f.clean('12.3456.010101.3456') +u'12.3456.010101.3456' +>>> f.clean('12.3456.310278.3456') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid NIK/KTP number'] +>>> f.clean('00.0000.010101.0000') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid NIK/KTP number'] +>>> f.clean('1234567890123456') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid NIK/KTP number'] +>>> f.clean('foo') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid NIK/KTP number'] + +# IDProvinceSelect ########################################################## + +>>> from django.contrib.localflavor.id.forms import IDProvinceSelect +>>> s = IDProvinceSelect() +>>> s.render('provinces', 'LPG') +u'' + +# IDLicensePlatePrefixelect ######################################################################## + +>>> from django.contrib.localflavor.id.forms import IDLicensePlatePrefixSelect +>>> s = IDLicensePlatePrefixSelect() +>>> s.render('codes', 'BE') +u'' + +# IDLicensePlateField ####################################################################### + +>>> from django.contrib.localflavor.id.forms import IDLicensePlateField +>>> f = IDLicensePlateField(required=False) +>>> f.clean('') +u'' +>>> f.clean(' b 1234 ab ') +u'B 1234 AB' +>>> f.clean('B 1234 ABC') +u'B 1234 ABC' +>>> f.clean('A 12') +u'A 12' +>>> f.clean('DK 12345 12') +u'DK 12345 12' +>>> f.clean('RI 10') +u'RI 10' +>>> f.clean('CD 12 12') +u'CD 12 12' +>>> f.clean('CD 10 12') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid vehicle license plate number'] +>>> f.clean('CD 1234 12') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid vehicle license plate number'] +>>> f.clean('RI 10 AB') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid vehicle license plate number'] +>>> f.clean('B 12345 01') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid vehicle license plate number'] +>>> f.clean('N 1234 12') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid vehicle license plate number'] +>>> f.clean('A 12 XYZ') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid vehicle license plate number'] +>>> f.clean('Q 1234 AB') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid vehicle license plate number'] +>>> f.clean('foo') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid vehicle license plate number'] +""" \ No newline at end of file diff --git a/tests/regressiontests/forms/localflavor/ie.py b/tests/regressiontests/forms/localflavor/ie.py new file mode 100644 index 0000000000..540281c19e --- /dev/null +++ b/tests/regressiontests/forms/localflavor/ie.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Tests for the contrib/localflavor/ie form fields. + +tests = r""" +# IECountySelect ######################################################### + +>>> from django.contrib.localflavor.ie.forms import IECountySelect +>>> f = IECountySelect() +>>> f.render('counties', 'dublin') +u'' + +""" diff --git a/tests/regressiontests/forms/localflavor/kw.py b/tests/regressiontests/forms/localflavor/kw.py new file mode 100644 index 0000000000..265c1f00ae --- /dev/null +++ b/tests/regressiontests/forms/localflavor/kw.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Tests for the contrib/localflavor/ KW form fields. + +tests = r""" +# KWCivilIDNumberField ######################################################## + +>>> from django.contrib.localflavor.kw.forms import KWCivilIDNumberField +>>> f = KWCivilIDNumberField() +>>> f.clean('282040701483') +'282040701483' +>>> f.clean('289332013455') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Kuwaiti Civil ID number'] +""" diff --git a/tests/regressiontests/forms/localflavor/pt.py b/tests/regressiontests/forms/localflavor/pt.py new file mode 100644 index 0000000000..56d4d54c02 --- /dev/null +++ b/tests/regressiontests/forms/localflavor/pt.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# Tests for the contrib/localflavor/ PT form fields. + +tests = r""" +# PTZipCodeField ############################################################# + +PTZipCodeField validates that the data is a valid PT zipcode. +>>> from django.contrib.localflavor.pt.forms import PTZipCodeField +>>> f = PTZipCodeField() +>>> f.clean('3030-034') +u'3030-034' +>>> f.clean('1003456') +u'1003-456' +>>> f.clean('2A200') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXX-XXX.'] +>>> f.clean('980001') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXX-XXX.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = PTZipCodeField(required=False) +>>> f.clean('3030-034') +u'3030-034' +>>> f.clean('1003456') +u'1003-456' +>>> f.clean('2A200') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXX-XXX.'] +>>> f.clean('980001') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXX-XXX.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# PTPhoneNumberField ########################################################## + +PTPhoneNumberField validates that the data is a valid Portuguese phone number. +It's normalized to XXXXXXXXX format or +X(X) for international numbers. Dots are valid too. +>>> from django.contrib.localflavor.pt.forms import PTPhoneNumberField +>>> f = PTPhoneNumberField() +>>> f.clean('917845189') +u'917845189' +>>> f.clean('91 784 5189') +u'917845189' +>>> f.clean('91 784 5189') +u'917845189' +>>> f.clean('+351 91 111') +u'+35191111' +>>> f.clean('00351873') +u'00351873' +>>> f.clean('91 784 51 8') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must have 9 digits, or start by + or 00.'] +>>> f.clean('091 456 987 1') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must have 9 digits, or start by + or 00.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = PTPhoneNumberField(required=False) +>>> f.clean('917845189') +u'917845189' +>>> f.clean('91 784 5189') +u'917845189' +>>> f.clean('91 784 5189') +u'917845189' +>>> f.clean('+351 91 111') +u'+35191111' +>>> f.clean('00351873') +u'00351873' +>>> f.clean('91 784 51 8') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must have 9 digits, or start by + or 00.'] +>>> f.clean('091 456 987 1') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must have 9 digits, or start by + or 00.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +""" diff --git a/tests/regressiontests/forms/localflavor/uk.py b/tests/regressiontests/forms/localflavor/uk.py index 258c22e5a9..cd6c8edf84 100644 --- a/tests/regressiontests/forms/localflavor/uk.py +++ b/tests/regressiontests/forms/localflavor/uk.py @@ -58,4 +58,15 @@ u'BT32 4PX' u'' >>> f.clean('') u'' +>>> class MyUKPostcodeField(UKPostcodeField): +... default_error_messages = { +... 'invalid': 'Enter a bloody postcode!', +... } +... +>>> +>>> f = MyUKPostcodeField(required=False) +>>> f.clean('1NV 4L1D') +Traceback (most recent call last): +... +ValidationError: [u'Enter a bloody postcode!'] """ diff --git a/tests/regressiontests/forms/localflavor/uy.py b/tests/regressiontests/forms/localflavor/uy.py new file mode 100644 index 0000000000..057ab193f5 --- /dev/null +++ b/tests/regressiontests/forms/localflavor/uy.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Tests for the contrib/localflavor/ UY form fields. + +tests = r""" +# UYDepartamentSelect ######################################################### + +>>> from django.contrib.localflavor.uy.forms import UYDepartamentSelect +>>> f = UYDepartamentSelect() +>>> f.render('departamentos', 'S') +u'' + +# UYCIField ################################################################### + +>>> from django.contrib.localflavor.uy.util import get_validation_digit +>>> get_validation_digit(409805) == 3 +True +>>> get_validation_digit(1005411) == 2 +True + +>>> from django.contrib.localflavor.uy.forms import UYCIField +>>> f = UYCIField() +>>> f.clean('4098053') +u'4098053' +>>> f.clean('409805-3') +u'409805-3' +>>> f.clean('409.805-3') +u'409.805-3' +>>> f.clean('10054112') +u'10054112' +>>> f.clean('1005411-2') +u'1005411-2' +>>> f.clean('1.005.411-2') +u'1.005.411-2' +>>> f.clean('foo') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid CI number in X.XXX.XXX-X,XXXXXXX-X or XXXXXXXX format.'] +>>> f.clean('409805-2') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid CI number.'] +>>> f.clean('1.005.411-5') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid CI number.'] +""" diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index b6b7752f5a..e9ae948dfd 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -15,16 +15,21 @@ from localflavor.es import tests as localflavor_es_tests from localflavor.fi import tests as localflavor_fi_tests from localflavor.fr import tests as localflavor_fr_tests from localflavor.generic import tests as localflavor_generic_tests +from localflavor.id import tests as localflavor_id_tests +from localflavor.ie import tests as localflavor_ie_tests from localflavor.is_ import tests as localflavor_is_tests from localflavor.it import tests as localflavor_it_tests from localflavor.jp import tests as localflavor_jp_tests +from localflavor.kw import tests as localflavor_kw_tests from localflavor.nl import tests as localflavor_nl_tests from localflavor.pl import tests as localflavor_pl_tests +from localflavor.pt import tests as localflavor_pt_tests from localflavor.ro import tests as localflavor_ro_tests from localflavor.se import tests as localflavor_se_tests from localflavor.sk import tests as localflavor_sk_tests from localflavor.uk import tests as localflavor_uk_tests from localflavor.us import tests as localflavor_us_tests +from localflavor.uy import tests as localflavor_uy_tests from localflavor.za import tests as localflavor_za_tests from regressions import tests as regression_tests from util import tests as util_tests @@ -52,16 +57,21 @@ __test__ = { 'localflavor_fi_tests': localflavor_fi_tests, 'localflavor_fr_tests': localflavor_fr_tests, 'localflavor_generic_tests': localflavor_generic_tests, + 'localflavor_id_tests': localflavor_id_tests, + 'localflavor_ie_tests': localflavor_ie_tests, 'localflavor_is_tests': localflavor_is_tests, 'localflavor_it_tests': localflavor_it_tests, 'localflavor_jp_tests': localflavor_jp_tests, + 'localflavor_kw_tests': localflavor_kw_tests, 'localflavor_nl_tests': localflavor_nl_tests, 'localflavor_pl_tests': localflavor_pl_tests, + 'localflavor_pt_tests': localflavor_pt_tests, 'localflavor_ro_tests': localflavor_ro_tests, 'localflavor_se_tests': localflavor_se_tests, 'localflavor_sk_tests': localflavor_sk_tests, 'localflavor_uk_tests': localflavor_uk_tests, 'localflavor_us_tests': localflavor_us_tests, + 'localflavor_uy_tests': localflavor_uy_tests, 'localflavor_za_tests': localflavor_za_tests, 'regression_tests': regression_tests, 'formset_tests': formset_tests, diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py index 84ae61f527..6ea9298d4c 100644 --- a/tests/regressiontests/forms/widgets.py +++ b/tests/regressiontests/forms/widgets.py @@ -10,6 +10,8 @@ tests = r""" ... from decimal import Decimal ... except ImportError: ... from django.utils._decimal import Decimal +>>> from django.utils.translation import activate, deactivate +>>> from django.conf import settings ########### # Widgets # @@ -1082,6 +1084,13 @@ True False >>> w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'06/05/2008', u'12:41']) True +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> w._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), [u'06.05.2008', u'12:41']) +True +>>> deactivate() +>>> settings.USE_L10N = False + # DateTimeInput ############################################################### @@ -1099,6 +1108,12 @@ u'' u'' >>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51)) u'' +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> w.render('date', d) +u'' +>>> deactivate() +>>> settings.USE_L10N = False Use 'format' to change the way a value is displayed. >>> w = DateTimeInput(format='%d/%m/%Y %H:%M') @@ -1107,6 +1122,7 @@ u'' >>> w._has_changed(d, '17/09/2007 12:51') False + # DateInput ################################################################### >>> w = DateInput() @@ -1125,6 +1141,13 @@ We should be able to initialize from a unicode value. >>> w.render('date', u'2007-09-17') u'' +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> w.render('date', d) +u'' +>>> deactivate() +>>> settings.USE_L10N = False + Use 'format' to change the way a value is displayed. >>> w = DateInput(format='%d/%m/%Y') >>> w.render('date', d) @@ -1153,6 +1176,13 @@ We should be able to initialize from a unicode value. >>> w.render('time', u'13:12:11') u'' +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> w.render('date', d) +u'' +>>> deactivate() +>>> settings.USE_L10N = False + Use 'format' to change the way a value is displayed. >>> w = TimeInput(format='%H:%M') >>> w.render('time', t) @@ -1176,6 +1206,12 @@ u'' >>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51)) u'' +>>> activate('de-at') +>>> settings.USE_L10N = True +>>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51)) +u'' +>>> deactivate() +>>> settings.USE_L10N = False """ diff --git a/tests/regressiontests/i18n/forms.py b/tests/regressiontests/i18n/forms.py index b8244f6ca7..8df066c6fd 100644 --- a/tests/regressiontests/i18n/forms.py +++ b/tests/regressiontests/i18n/forms.py @@ -1,5 +1,6 @@ from django import template, forms from django.forms.extras import SelectDateWidget +from models import Company class I18nForm(forms.Form): decimal_field = forms.DecimalField() @@ -11,3 +12,6 @@ class I18nForm(forms.Form): class SelectDateForm(forms.Form): date_field = forms.DateField(widget=SelectDateWidget) +class CompanyForm(forms.ModelForm): + class Meta: + model = Company diff --git a/tests/regressiontests/i18n/models.py b/tests/regressiontests/i18n/models.py index e1eefb8477..56f158578b 100644 --- a/tests/regressiontests/i18n/models.py +++ b/tests/regressiontests/i18n/models.py @@ -1,12 +1,16 @@ +from datetime import datetime from django.db import models from django.utils.translation import ugettext_lazy as _ class TestModel(models.Model): text = models.CharField(max_length=10, default=_('Anything')) +class Company(models.Model): + name = models.CharField(max_length=50) + date_added = models.DateTimeField(default=datetime(1799,1,31,23,59,59,0)) + __test__ = {'API_TESTS': ''' >>> tm = TestModel() >>> tm.save() ''' } - diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 42dfe91659..2df08a6648 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -1,15 +1,17 @@ +# -*- encoding: utf-8 -*- import sys import decimal import datetime from django.template import Template, Context from django.conf import settings -from django.utils.formats import get_format, date_format, number_format, localize +from django.utils.formats import get_format, date_format, time_format, number_format, localize, localize_input from django.utils.numberformat import format from django.test import TestCase, client -from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy +from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, to_locale + +from forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm -from forms import I18nForm, SelectDateForm, SelectDateWidget class TranslationTests(TestCase): @@ -80,6 +82,23 @@ class TranslationTests(TestCase): finally: deactivate() + def test_to_locale(self): + """ + Tests the to_locale function and the special case of Serbian Latin + (refs #12230 and r11299) + """ + self.assertEqual(to_locale('en-us'), 'en_US') + self.assertEqual(to_locale('sr-lat'), 'sr_Lat') + + def test_to_language(self): + """ + Test the to_language function + """ + from django.utils.translation.trans_real import to_language + self.assertEqual(to_language('en_US'), 'en-us') + self.assertEqual(to_language('sr_Lat'), 'sr-lat') + + class FormattingTests(TestCase): def setUp(self): @@ -90,8 +109,10 @@ class FormattingTests(TestCase): self.f = 99999.999 self.d = datetime.date(2009, 12, 31) self.dt = datetime.datetime(2009, 12, 31, 20, 50) + self.t = datetime.time(10, 15, 48) self.ctxt = Context({ 'n': self.n, + 't': self.t, 'd': self.d, 'dt': self.dt, 'f': self.f @@ -102,10 +123,10 @@ class FormattingTests(TestCase): settings.USE_I18N = self._use_i18n settings.USE_L10N = self._use_l10n settings.USE_THOUSAND_SEPARATOR = self._use_thousand_separator - + def test_locale_independent(self): """ - Localization of dates and numbers + Localization of numbers """ settings.USE_L10N = True settings.USE_THOUSAND_SEPARATOR = False @@ -118,6 +139,10 @@ class FormattingTests(TestCase): self.assertEqual(u'-66666.6', format(-66666.666, decimal_sep='.', decimal_pos=1)) self.assertEqual(u'-66666.0', format(int('-66666'), decimal_sep='.', decimal_pos=1)) + # date filter + self.assertEqual(u'31.12.2009 в 20:50', Template('{{ dt|date:"d.m.Y в H:i" }}').render(self.ctxt)) + self.assertEqual(u'⌚ 10:15', Template('{{ t|time:"⌚ H:i" }}').render(self.ctxt)) + def test_l10n_disabled(self): """ Catalan locale with format i18n disabled translations will be used, @@ -129,6 +154,7 @@ class FormattingTests(TestCase): self.assertEqual('N j, Y', get_format('DATE_FORMAT')) self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK')) self.assertEqual('.', get_format('DECIMAL_SEPARATOR')) + self.assertEqual(u'10:15 a.m.', time_format(self.t)) self.assertEqual(u'des. 31, 2009', date_format(self.d)) self.assertEqual(u'desembre 2009', date_format(self.d, 'YEAR_MONTH_FORMAT')) self.assertEqual(u'12/31/2009 8:50 p.m.', date_format(self.dt, 'SHORT_DATETIME_FORMAT')) @@ -143,6 +169,7 @@ class FormattingTests(TestCase): self.assertEqual(u'2009-12-31 20:50:00', Template('{{ dt }}').render(self.ctxt)) self.assertEqual(u'66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) self.assertEqual(u'100000.0', Template('{{ f|floatformat }}').render(self.ctxt)) + self.assertEqual(u'10:15 a.m.', Template('{{ t|time:"TIME_FORMAT" }}').render(self.ctxt)) self.assertEqual(u'12/31/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) self.assertEqual(u'12/31/2009 8:50 p.m.', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt)) @@ -168,7 +195,7 @@ class FormattingTests(TestCase): self.assertEqual(datetime.date(2009, 12, 31), form2.cleaned_data['date_field']) self.assertEqual( u'\n\n', - SelectDateWidget().render('mydate', datetime.date(2009, 12, 31)) + SelectDateWidget(years=range(2009, 2019)).render('mydate', datetime.date(2009, 12, 31)) ) finally: deactivate() @@ -183,6 +210,7 @@ class FormattingTests(TestCase): self.assertEqual('j \de F \de Y', get_format('DATE_FORMAT')) self.assertEqual(1, get_format('FIRST_DAY_OF_WEEK')) self.assertEqual(',', get_format('DECIMAL_SEPARATOR')) + self.assertEqual(u'10:15:48', time_format(self.t)) self.assertEqual(u'31 de desembre de 2009', date_format(self.d)) self.assertEqual(u'desembre del 2009', date_format(self.d, 'YEAR_MONTH_FORMAT')) self.assertEqual(u'31/12/2009 20:50', date_format(self.dt, 'SHORT_DATETIME_FORMAT')) @@ -209,6 +237,7 @@ class FormattingTests(TestCase): self.assertEqual(u'31 de desembre de 2009 a les 20:50', Template('{{ dt }}').render(self.ctxt)) self.assertEqual(u'66666,67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) self.assertEqual(u'100000,0', Template('{{ f|floatformat }}').render(self.ctxt)) + self.assertEqual(u'10:15:48', Template('{{ t|time:"TIME_FORMAT" }}').render(self.ctxt)) self.assertEqual(u'31/12/2009', Template('{{ d|date:"SHORT_DATE_FORMAT" }}').render(self.ctxt)) self.assertEqual(u'31/12/2009 20:50', Template('{{ dt|date:"SHORT_DATETIME_FORMAT" }}').render(self.ctxt)) @@ -235,7 +264,7 @@ class FormattingTests(TestCase): self.assertEqual(datetime.date(2009, 12, 31), form4.cleaned_data['date_field']) self.assertEqual( u'\n\n', - SelectDateWidget().render('mydate', datetime.date(2009, 12, 31)) + SelectDateWidget(years=range(2009, 2019)).render('mydate', datetime.date(2009, 12, 31)) ) finally: deactivate() @@ -300,7 +329,7 @@ class FormattingTests(TestCase): self.assertEqual(datetime.date(2009, 12, 31), form6.cleaned_data['date_field']) self.assertEqual( u'\n\n', - SelectDateWidget().render('mydate', datetime.date(2009, 12, 31)) + SelectDateWidget(years=range(2009, 2019)).render('mydate', datetime.date(2009, 12, 31)) ) finally: deactivate() @@ -323,6 +352,28 @@ class FormattingTests(TestCase): finally: deactivate() + def test_localized_input(self): + """ + Tests if form input is correctly localized + """ + settings.USE_L10N = True + activate('de-at') + try: + form6 = CompanyForm({ + 'name': u'acme', + 'date_added': datetime.datetime(2009, 12, 31, 6, 0, 0), + }) + form6.save() + self.assertEqual(True, form6.is_valid()) + self.assertEqual( + form6.as_ul(), + u'
  • \n
  • ' + ) + self.assertEqual(localize_input(datetime.datetime(2009, 12, 31, 6, 0, 0)), '31.12.2009 06:00:00') + self.assertEqual(datetime.datetime(2009, 12, 31, 6, 0, 0), form6.cleaned_data['date_added']) + finally: + deactivate() + class MiscTests(TestCase): def test_parse_spec_http_header(self): diff --git a/tests/regressiontests/localflavor/forms.py b/tests/regressiontests/localflavor/forms.py index 49635b02fb..2dd1da6dd0 100644 --- a/tests/regressiontests/localflavor/forms.py +++ b/tests/regressiontests/localflavor/forms.py @@ -1,13 +1,6 @@ from django.forms import ModelForm from models import Place -class PlaceForm(ModelForm): - """docstring for PlaceForm""" - class Meta: - model = Place -from django.forms import ModelForm -from models import Place - class PlaceForm(ModelForm): """docstring for PlaceForm""" class Meta: diff --git a/tests/regressiontests/localflavor/models.py b/tests/regressiontests/localflavor/models.py index 079c7bd982..f74a5051d4 100644 --- a/tests/regressiontests/localflavor/models.py +++ b/tests/regressiontests/localflavor/models.py @@ -1,14 +1,6 @@ from django.db import models from django.contrib.localflavor.us.models import USStateField -class Place(models.Model): - state = USStateField(blank=True) - state_req = USStateField() - state_default = USStateField(default="CA", blank=True) - name = models.CharField(max_length=20) -from django.db import models -from django.contrib.localflavor.us.models import USStateField - class Place(models.Model): state = USStateField(blank=True) state_req = USStateField() diff --git a/tests/regressiontests/localflavor/tests.py b/tests/regressiontests/localflavor/tests.py index 61e0e5b5e6..0ea3c52568 100644 --- a/tests/regressiontests/localflavor/tests.py +++ b/tests/regressiontests/localflavor/tests.py @@ -2,89 +2,6 @@ from django.test import TestCase from models import Place from forms import PlaceForm -class USLocalflavorTests(TestCase): - def setUp(self): - self.form = PlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'}) - - def test_get_display_methods(self): - """Test that the get_*_display() methods are added to the model instances.""" - place = self.form.save() - self.assertEqual(place.get_state_display(), 'Georgia') - self.assertEqual(place.get_state_req_display(), 'North Carolina') - - def test_required(self): - """Test that required USStateFields throw appropriate errors.""" - form = PlaceForm({'state':'GA', 'name':'Place in GA'}) - self.assertFalse(form.is_valid()) - self.assertEqual(form.errors['state_req'], [u'This field is required.']) - - def test_field_blank_option(self): - """Test that the empty option is there.""" - state_select_html = """\ -""" - self.assertEqual(str(self.form['state']), state_select_html) -from django.test import TestCase -from models import Place -from forms import PlaceForm - class USLocalflavorTests(TestCase): def setUp(self): self.form = PlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'}) diff --git a/tests/regressiontests/settings/__init__.py b/tests/regressiontests/settings/__init__.py new file mode 100644 index 0000000000..76e845e16b --- /dev/null +++ b/tests/regressiontests/settings/__init__.py @@ -0,0 +1,4 @@ +# Settings file automatically generated by regressiontests.admin_scripts test case +DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'SUPPORTS_TRANSACTIONS': True, 'NAME': ':memory:', 'TEST_CHARSET': None, 'TIME_ZONE': 'America/Chicago', 'TEST_COLLATION': None, 'PORT': '', 'HOST': '', 'USER': '', 'TEST_NAME': None, 'PASSWORD': '', 'OPTIONS': {}}, 'other': {'ENGINE': 'django.db.backends.sqlite3', 'SUPPORTS_TRANSACTIONS': True, 'NAME': 'other_db14006', 'TEST_CHARSET': None, 'TIME_ZONE': 'America/Chicago', 'TEST_COLLATION': None, 'PORT': '', 'HOST': '', 'USER': '', 'TEST_NAME': 'other_db14006', 'PASSWORD': '', 'OPTIONS': {}}} +ROOT_URLCONF = 'urls' +INSTALLED_APPS = ['django.contrib.auth', 'django.contrib.contenttypes', 'admin_scripts'] diff --git a/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.mo b/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..148cba533bfe58b81654c876767e3053988250f6 GIT binary patch literal 431 zcmYL^y-ve06omtVB?AK!1B3VCk|GKvl&Ya|OGN%i(@L2 zT?MA+RPF+w=x&lL9IvtoX`=4Hb&+p*1T~3gId0ps?TGaIEwJ)zkz^FdG00qsEWclhW$|8o79ZL#wlsI9 literal 0 HcmV?d00001 diff --git a/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po b/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000000..a0ff0152ed --- /dev/null +++ b/tests/regressiontests/views/locale/ru/LC_MESSAGES/djangojs.po @@ -0,0 +1,20 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-09-15 16:45+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "this is to be translated" +msgstr "перевод" \ No newline at end of file diff --git a/tests/regressiontests/views/tests/i18n.py b/tests/regressiontests/views/tests/i18n.py index ebe97ab2dc..b0fa3e3f47 100644 --- a/tests/regressiontests/views/tests/i18n.py +++ b/tests/regressiontests/views/tests/i18n.py @@ -4,6 +4,7 @@ import gettext from django.conf import settings from django.test import TestCase from django.utils.translation import activate +from django.utils.text import javascript_quote from regressiontests.views.urls import locale_dir @@ -20,11 +21,12 @@ class I18NTests(TestCase): def test_jsi18n(self): """The javascript_catalog can be deployed with language settings""" - for lang_code in ['es', 'fr', 'en']: + for lang_code in ['es', 'fr', 'en', 'ru']: activate(lang_code) catalog = gettext.translation('djangojs', locale_dir, [lang_code]) trans_txt = catalog.ugettext('this is to be translated') response = self.client.get('/views/jsi18n/') # in response content must to be a line like that: # catalog['this is to be translated'] = 'same_that_trans_txt' - self.assertContains(response, trans_txt, 1) + # javascript_quote is used to be able to check unicode strings + self.assertContains(response, javascript_quote(trans_txt), 1) diff --git a/tests/runtests.py b/tests/runtests.py index 1cd95b9fef..f2ea5f1271 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -162,7 +162,7 @@ def django_tests(verbosity, interactive, failfast, test_labels): failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, failfast=failfast, extra_tests=extra_tests) if failures: - sys.exit(failures) + sys.exit(bool(failures)) # Restore the old settings. settings.INSTALLED_APPS = old_installed_apps