diff options
author | Boulder Sprinters <boulder-sprinters@djangoproject.com> | 2007-05-01 15:33:10 +0000 |
---|---|---|
committer | Boulder Sprinters <boulder-sprinters@djangoproject.com> | 2007-05-01 15:33:10 +0000 |
commit | 90740c93e6aae5f1e676dc09777c4ba88cee5633 (patch) | |
tree | d84c18459ef2485f593aba6a1f24b3cbac9a0d98 | |
parent | 5a7802586d117a75f91df04c032fd249808a877d (diff) | |
download | django-90740c93e6aae5f1e676dc09777c4ba88cee5633.tar.gz |
boulder-oracle-sprint: Merged to [5134]
git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@5135 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r-- | AUTHORS | 2 | ||||
-rw-r--r-- | django/contrib/localflavor/ch/__init__.py | 0 | ||||
-rw-r--r-- | django/contrib/localflavor/ch/ch_states.py | 31 | ||||
-rw-r--r-- | django/contrib/localflavor/ch/forms.py | 109 | ||||
-rw-r--r-- | django/core/management.py | 1 | ||||
-rw-r--r-- | django/db/models/fields/__init__.py | 6 | ||||
-rw-r--r-- | django/db/models/query.py | 13 | ||||
-rw-r--r-- | docs/settings.txt | 45 | ||||
-rw-r--r-- | tests/modeltests/custom_columns/models.py | 2 | ||||
-rw-r--r-- | tests/modeltests/lookup/models.py | 4 | ||||
-rw-r--r-- | tests/modeltests/many_to_one/models.py | 4 | ||||
-rw-r--r-- | tests/modeltests/reverse_lookup/models.py | 2 | ||||
-rw-r--r-- | tests/regressiontests/forms/localflavor.py | 54 | ||||
-rw-r--r-- | tests/regressiontests/null_queries/models.py | 2 |
14 files changed, 231 insertions, 44 deletions
@@ -181,6 +181,7 @@ answer newbie questions, and generally made Django that much better: Luke Plant <http://lukeplant.me.uk/> plisk Daniel Poelzleithner <http://poelzi.org/> + polpak@yahoo.com J. Rademaker Michael Radziej <mir@noris.de> ramiro @@ -224,6 +225,7 @@ answer newbie questions, and generally made Django that much better: wam-djangobug@wamber.net Dan Watson <http://theidioteque.net/> Chris Wesseling <Chris.Wesseling@cwi.nl> + charly.wilhelm@gmail.com Rachel Willmer <http://www.willmer.com/kb/> Gary Wilson <gary.wilson@gmail.com> wojtek diff --git a/django/contrib/localflavor/ch/__init__.py b/django/contrib/localflavor/ch/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/django/contrib/localflavor/ch/__init__.py diff --git a/django/contrib/localflavor/ch/ch_states.py b/django/contrib/localflavor/ch/ch_states.py new file mode 100644 index 0000000000..e9bbcc6268 --- /dev/null +++ b/django/contrib/localflavor/ch/ch_states.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -* +from django.utils.translation import gettext_lazy as _ + +STATE_CHOICES = ( + ('AG', _('Aargau')), + ('AI', _('Appenzell Innerrhoden')), + ('AR', _('Appenzell Ausserrhoden')), + ('BS', _('Basel-Stadt')), + ('BL', _('Basel-Land')), + ('BE', _('Berne')), + ('FR', _('Fribourg')), + ('GE', _('Geneva')), + ('GL', _('Glarus')), + ('GR', _('Graubuenden')), + ('JU', _('Jura')), + ('LU', _('Lucerne')), + ('NE', _('Neuchatel')), + ('NW', _('Nidwalden')), + ('OW', _('Obwalden')), + ('SH', _('Schaffhausen')), + ('SZ', _('Schwyz')), + ('SO', _('Solothurn')), + ('SG', _('St. Gallen')), + ('TG', _('Thurgau')), + ('TI', _('Ticino')), + ('UR', _('Uri')), + ('VS', _('Valais')), + ('VD', _('Vaud')), + ('ZG', _('Zug')), + ('ZH', _('Zurich')) +) diff --git a/django/contrib/localflavor/ch/forms.py b/django/contrib/localflavor/ch/forms.py new file mode 100644 index 0000000000..51e52dc0e9 --- /dev/null +++ b/django/contrib/localflavor/ch/forms.py @@ -0,0 +1,109 @@ +""" +Swiss-specific Form helpers +""" + +from django.newforms import ValidationError +from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES +from django.utils.encoding import smart_unicode +from django.utils.translation import gettext +import re + +id_re = re.compile(r"^(?P<idnumber>\w{8})(?P<pos9>(\d{1}|<))(?P<checksum>\d{1})$") +phone_digits_re = re.compile(r'^0([1-9]{1})\d{8}$') + +class CHZipCodeField(RegexField): + def __init__(self, *args, **kwargs): + super(CHZipCodeField, self).__init__(r'^\d{4}$', + max_length=None, min_length=None, + error_message=gettext('Enter a zip code in the format XXXX.'), + *args, **kwargs) + +class CHPhoneNumberField(Field): + """ + Validate local Swiss phone number (not international ones) + The correct format is '0XX XXX XX XX'. + '0XX.XXX.XX.XX' and '0XXXXXXXXX' validate but are corrected to + '0XX XXX XX XX'. + """ + def clean(self, value): + super(CHPhoneNumberField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + value = re.sub('(\.|\s|/|-)', '', smart_unicode(value)) + m = phone_digits_re.search(value) + if m: + return u'%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10]) + raise ValidationError('Phone numbers must be in 0XX XXX XX XX format.') + +class CHStateSelect(Select): + """ + A Select widget that uses a list of CH states as its choices. + """ + def __init__(self, attrs=None): + from ch_states import STATE_CHOICES # relative import + super(CHStateSelect, self).__init__(attrs, choices=STATE_CHOICES) + +class CHIdentityCardNumberField(Field): + """ + A Swiss identity card number. + + Checks the following rules to determine whether the number is valid: + + * Conforms to the X1234567<0 or 1234567890 format. + * Included checksums match calculated checksums + + Algorithm is documented at http://adi.kousz.ch/artikel/IDCHE.htm + """ + def has_valid_checksum(self, number): + given_number, given_checksum = number[:-1], number[-1] + new_number = given_number + calculated_checksum = 0 + fragment = "" + parameter = 7 + + first = str(number[:1]) + if first.isalpha(): + num = ord(first.upper()) - 65 + if num < 0 or num > 8: + return False + new_number = str(num) + new_number[1:] + new_number = new_number[:8] + '0' + + if not new_number.isdigit(): + return False + + for i in range(len(new_number)): + fragment = int(new_number[i])*parameter + calculated_checksum += fragment + + if parameter == 1: + parameter = 7 + elif parameter == 3: + parameter = 1 + elif parameter ==7: + parameter = 3 + + return str(calculated_checksum)[-1] == given_checksum + + def clean(self, value): + super(CHIdentityCardNumberField, self).clean(value) + error_msg = gettext('Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.') + if value in EMPTY_VALUES: + return u'' + + match = re.match(id_re, value) + if not match: + raise ValidationError(error_msg) + + idnumber, pos9, checksum = match.groupdict()['idnumber'], match.groupdict()['pos9'], match.groupdict()['checksum'] + + if idnumber == '00000000' or \ + idnumber == 'A0000000': + raise ValidationError(error_msg) + + all_digits = "%s%s%s" % (idnumber, pos9, checksum) + if not self.has_valid_checksum(all_digits): + raise ValidationError(error_msg) + + return u'%s%s%s' % (idnumber, pos9, checksum) + diff --git a/django/core/management.py b/django/core/management.py index 4a08b1e54d..883cbe1ca7 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -598,6 +598,7 @@ def syncdb(verbosity=1, interactive=True): # Install custom SQL for the app (but only if this # is a model we've just created) for app in models.get_apps(): + app_name = app.__name__.split('.')[-2] for model in models.get_models(app): if model in created_models: custom_sql = get_custom_sql_for_model(model) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index b78a3e9a70..f9aed8b98e 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -893,6 +893,12 @@ class USStateField(Field): def get_manipulator_field_objs(self): return [oldforms.USStateField] + def formfield(self, **kwargs): + from django.contrib.localflavor.us.forms import USStateSelect + defaults = {'widget': USStateSelect} + defaults.update(kwargs) + return super(USStateField, self).formfield(**defaults) + class XMLField(TextField): def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): self.schema_path = schema_path diff --git a/django/db/models/query.py b/django/db/models/query.py index 142fee04c2..93dcdd1776 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -903,6 +903,13 @@ def find_field(name, field_list, related_query): return None return matches[0] +def field_choices(field_list, related_query): + if related_query: + choices = [f.field.related_query_name() for f in field_list] + else: + choices = [f.name for f in field_list] + return choices + def lookup_inner(path, lookup_type, value, opts, table, column): qn = backend.quote_name joins, where, params = SortedDict(), [], [] @@ -987,7 +994,11 @@ def lookup_inner(path, lookup_type, value, opts, table, column): except FieldFound: # Match found, loop has been shortcut. pass else: # No match found. - raise TypeError, "Cannot resolve keyword '%s' into field" % name + choices = field_choices(current_opts.many_to_many, False) + \ + field_choices(current_opts.get_all_related_many_to_many_objects(), True) + \ + field_choices(current_opts.get_all_related_objects(), True) + \ + field_choices(current_opts.fields, False) + raise TypeError, "Cannot resolve keyword '%s' into field, choices are: %s" % (name, ", ".join(choices)) # Check whether an intermediate join is required between current_table # and new_table. diff --git a/docs/settings.txt b/docs/settings.txt index 8cc400dfde..aae9a1da04 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -500,44 +500,17 @@ in standard language format. For example, U.S. English is ``"en-us"``. See the LANGUAGES --------- -Default: A tuple of all available languages. Currently, this is:: +Default: A tuple of all available languages. This list is continually growing +and including a copy here would inevitably become rapidly out of date. You can +see the current list of translated languages by looking in +``django/conf/global_settings.py`` (or view the `online source`_). - LANGUAGES = ( - ('ar', _('Arabic')), - ('bn', _('Bengali')), - ('cs', _('Czech')), - ('cy', _('Welsh')), - ('da', _('Danish')), - ('de', _('German')), - ('el', _('Greek')), - ('en', _('English')), - ('es', _('Spanish')), - ('es_AR', _('Argentinean Spanish')), - ('fr', _('French')), - ('gl', _('Galician')), - ('hu', _('Hungarian')), - ('he', _('Hebrew')), - ('is', _('Icelandic')), - ('it', _('Italian')), - ('ja', _('Japanese')), - ('nl', _('Dutch')), - ('no', _('Norwegian')), - ('pt-br', _('Brazilian')), - ('ro', _('Romanian')), - ('ru', _('Russian')), - ('sk', _('Slovak')), - ('sl', _('Slovenian')), - ('sr', _('Serbian')), - ('sv', _('Swedish')), - ('ta', _('Tamil')), - ('uk', _('Ukrainian')), - ('zh-cn', _('Simplified Chinese')), - ('zh-tw', _('Traditional Chinese')), - ) +.. _online source: http://code.djangoproject.com/browser/django/trunk/django/conf/global_settings.py -A tuple of two-tuples in the format (language code, language name). This -specifies which languages are available for language selection. See the -`internationalization docs`_ for details. +The list is a tuple of two-tuples in the format (language code, language +name) -- for example, ``('ja', 'Japanese')``. This specifies which languages +are available for language selection. See the `internationalization docs`_ for +details. Generally, the default value should suffice. Only set this setting if you want to restrict language selection to a subset of the Django-provided languages. diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py index c09ca05557..b2b7261c89 100644 --- a/tests/modeltests/custom_columns/models.py +++ b/tests/modeltests/custom_columns/models.py @@ -71,7 +71,7 @@ __test__ = {'API_TESTS':""" >>> Author.objects.filter(firstname__exact='John') Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'firstname' into field +TypeError: Cannot resolve keyword 'firstname' into field, choices are: article, id, first_name, last_name >>> a = Author.objects.get(last_name__exact='Smith') >>> a.first_name diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index 106c97d3b4..c28f0e015f 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -223,11 +223,11 @@ DoesNotExist: Article matching query does not exist. >>> Article.objects.filter(pub_date_year='2005').count() Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'pub_date_year' into field +TypeError: Cannot resolve keyword 'pub_date_year' into field, choices are: id, headline, pub_date >>> Article.objects.filter(headline__starts='Article') Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'headline__starts' into field +TypeError: Cannot resolve keyword 'headline__starts' into field, choices are: id, headline, pub_date """} diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index 82eb3257d0..3ed449d598 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -174,13 +174,13 @@ False >>> Article.objects.filter(reporter_id__exact=1) Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'reporter_id' into field +TypeError: Cannot resolve keyword 'reporter_id' into field, choices are: id, headline, pub_date, reporter # You need to specify a comparison clause >>> Article.objects.filter(reporter_id=1) Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'reporter_id' into field +TypeError: Cannot resolve keyword 'reporter_id' into field, choices are: id, headline, pub_date, reporter # You can also instantiate an Article by passing # the Reporter's ID instead of a Reporter object. diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py index 7e6712676f..d30269c5c6 100644 --- a/tests/modeltests/reverse_lookup/models.py +++ b/tests/modeltests/reverse_lookup/models.py @@ -55,5 +55,5 @@ __test__ = {'API_TESTS':""" >>> Poll.objects.get(choice__name__exact="This is the answer") Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'choice' into field +TypeError: Cannot resolve keyword 'choice' into field, choices are: poll_choice, related_choice, id, question, creator """} diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py index f725fb38b7..ede89de2a0 100644 --- a/tests/regressiontests/forms/localflavor.py +++ b/tests/regressiontests/forms/localflavor.py @@ -1011,6 +1011,60 @@ Traceback (most recent call last): ... ValidationError: [u'Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.'] +# CHZipCodeField ############################################################ + +>>> from django.contrib.localflavor.ch.forms import CHZipCodeField +>>> f = CHZipCodeField() +>>> f.clean('800x') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXX.'] +>>> f.clean('80 00') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXX.'] +>>> f.clean('8000') +u'8000' + +# CHPhoneNumberField ######################################################## + +>>> from django.contrib.localflavor.ch.forms import CHPhoneNumberField +>>> f = CHPhoneNumberField() +>>> f.clean('01234567890') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0XX XXX XX XX format.'] +>>> f.clean('1234567890') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0XX XXX XX XX format.'] +>>> f.clean('0123456789') +u'012 345 67 89' + +# CHIdentityCardNumberField ################################################# + +>>> from django.contrib.localflavor.ch.forms import CHIdentityCardNumberField +>>> f = CHIdentityCardNumberField() +>>> f.clean('C1234567<0') +u'C1234567<0' +>>> f.clean('C1234567<1') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.'] +>>> f.clean('2123456700') +u'2123456700' +>>> f.clean('2123456701') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.'] + +# CHStateSelect ############################################################# + +>>> from django.contrib.localflavor.ch.forms import CHStateSelect +>>> w = CHStateSelect() +>>> w.render('state', 'AG') +u'<select name="state">\n<option value="AG" selected="selected">Aargau</option>\n<option value="AI">Appenzell Innerrhoden</option>\n<option value="AR">Appenzell Ausserrhoden</option>\n<option value="BS">Basel-Stadt</option>\n<option value="BL">Basel-Land</option>\n<option value="BE">Berne</option>\n<option value="FR">Fribourg</option>\n<option value="GE">Geneva</option>\n<option value="GL">Glarus</option>\n<option value="GR">Graubuenden</option>\n<option value="JU">Jura</option>\n<option value="LU">Lucerne</option>\n<option value="NE">Neuchatel</option>\n<option value="NW">Nidwalden</option>\n<option value="OW">Obwalden</option>\n<option value="SH">Schaffhausen</option>\n<option value="SZ">Schwyz</option>\n<option value="SO">Solothurn</option>\n<option value="SG">St. Gallen</option>\n<option value="TG">Thurgau</option>\n<option value="TI">Ticino</option>\n<option value="UR">Uri</option>\n<option value="VS">Valais</option>\n<option value="VD">Vaud</option>\n<option value="ZG">Zug</option>\n<option value="ZH">Zurich</option>\n</select>' + ## AUPostCodeField ########################################################## A field that accepts a four digit Australian post code. diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py index 09024f18c2..4396ab4005 100644 --- a/tests/regressiontests/null_queries/models.py +++ b/tests/regressiontests/null_queries/models.py @@ -32,7 +32,7 @@ __test__ = {'API_TESTS':""" >>> Choice.objects.filter(foo__exact=None) Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'foo' into field +TypeError: Cannot resolve keyword 'foo' into field, choices are: id, poll, choice # Can't use None on anything other than __exact >>> Choice.objects.filter(id__gt=None) |