diff --git a/AUTHORS b/AUTHORS
index bd7ef7770d..1c33bb9a7b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -150,6 +150,7 @@ answer newbie questions, and generally made Django that much better:
Manuzhai
Petar Marić
Nuno Mariz
+ marijn@metronomo.cl
mark@junklight.com
Yasushi Masuda
mattycakes@gmail.com
@@ -167,6 +168,7 @@ answer newbie questions, and generally made Django that much better:
Sam Newman
Neal Norwitz
oggie rob
+ onaiort@gmail.com
Jay Parlar
pavithran s
Barry Pederson
diff --git a/django/bin/make-messages.py b/django/bin/make-messages.py
index 34fb68dcfe..bf9e7a1962 100755
--- a/django/bin/make-messages.py
+++ b/django/bin/make-messages.py
@@ -81,7 +81,7 @@ def make_messages():
src = pythonize_re.sub('\n#', src)
open(os.path.join(dirpath, '%s.py' % file), "wb").write(src)
thefile = '%s.py' % file
- cmd = 'xgettext %s -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy --from-code UTF-8 -o - "%s"' % (
+ cmd = 'xgettext %s -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % (
os.path.exists(potfile) and '--omit-header' or '', domain, os.path.join(dirpath, thefile))
(stdin, stdout, stderr) = os.popen3(cmd, 'b')
msgs = stdout.read()
@@ -103,7 +103,7 @@ def make_messages():
open(os.path.join(dirpath, '%s.py' % file), "wb").write(templatize(src))
thefile = '%s.py' % file
if verbose: sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
- cmd = 'xgettext %s -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy --from-code UTF-8 -o - "%s"' % (
+ cmd = 'xgettext %s -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % (
os.path.exists(potfile) and '--omit-header' or '', domain, os.path.join(dirpath, thefile))
(stdin, stdout, stderr) = os.popen3(cmd, 'b')
msgs = stdout.read()
diff --git a/django/bin/profiling/gather_profile_stats.py b/django/bin/profiling/gather_profile_stats.py
index 852f16229d..c0844930e9 100644
--- a/django/bin/profiling/gather_profile_stats.py
+++ b/django/bin/profiling/gather_profile_stats.py
@@ -22,7 +22,7 @@ def gather_stats(p):
else:
continue
print "Processing %s" % f
- if profiles.has_key(path):
+ if path in profiles:
profiles[path].add(prof)
else:
profiles[path] = prof
diff --git a/django/conf/urls/defaults.py b/django/conf/urls/defaults.py
index 49bc176ef3..13a74331d6 100644
--- a/django/conf/urls/defaults.py
+++ b/django/conf/urls/defaults.py
@@ -11,9 +11,10 @@ def patterns(prefix, *args):
pattern_list = []
for t in args:
if isinstance(t, (list, tuple)):
- pattern_list.append(url(prefix=prefix, *t))
- else:
- pattern_list.append(t)
+ t = url(prefix=prefix, *t)
+ elif isinstance(t, RegexURLPattern):
+ t.add_prefix(prefix)
+ pattern_list.append(t)
return pattern_list
def url(regex, view, kwargs=None, name=None, prefix=''):
diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py
index e708b876bd..f9cad005d5 100644
--- a/django/contrib/admin/templatetags/admin_modify.py
+++ b/django/contrib/admin/templatetags/admin_modify.py
@@ -74,7 +74,7 @@ class FieldWidgetNode(template.Node):
self.bound_field_var = bound_field_var
def get_nodelist(cls, klass):
- if not cls.nodelists.has_key(klass):
+ if klass not in cls.nodelists:
try:
field_class_name = klass.__name__
template_name = "widget/%s.html" % class_name_to_underscored(field_class_name)
diff --git a/django/contrib/admin/views/auth.py b/django/contrib/admin/views/auth.py
index bea1f8533c..c6ad0c3a95 100644
--- a/django/contrib/admin/views/auth.py
+++ b/django/contrib/admin/views/auth.py
@@ -17,7 +17,7 @@ def user_add_stage(request):
if not errors:
new_user = manipulator.save(new_data)
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user}
- if request.POST.has_key("_addanother"):
+ if "_addanother" in request.POST:
request.user.message_set.create(message=msg)
return HttpResponseRedirect(request.path)
else:
@@ -29,7 +29,7 @@ def user_add_stage(request):
return render_to_response('admin/auth/user/add_form.html', {
'title': _('Add user'),
'form': form,
- 'is_popup': request.REQUEST.has_key('_popup'),
+ 'is_popup': '_popup' in request.REQUEST,
'add': True,
'change': False,
'has_delete_permission': False,
@@ -63,7 +63,7 @@ def user_change_password(request, id):
return render_to_response('admin/auth/user/change_password.html', {
'title': _('Change password: %s') % escape(user.username),
'form': form,
- 'is_popup': request.REQUEST.has_key('_popup'),
+ 'is_popup': '_popup' in request.REQUEST,
'add': True,
'change': False,
'has_delete_permission': False,
diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py
index bdd8257b2e..5389ca4dff 100644
--- a/django/contrib/admin/views/decorators.py
+++ b/django/contrib/admin/views/decorators.py
@@ -12,7 +12,7 @@ LOGIN_FORM_KEY = 'this_is_the_login_form'
def _display_login_form(request, error_message=''):
request.session.set_test_cookie()
- if request.POST and request.POST.has_key('post_data'):
+ if request.POST and 'post_data' in request.POST:
# User has failed login BUT has previously saved post data.
post_data = request.POST['post_data']
elif request.POST:
@@ -48,7 +48,7 @@ def staff_member_required(view_func):
def _checklogin(request, *args, **kwargs):
if request.user.is_authenticated() and request.user.is_staff:
# The user is valid. Continue to the admin page.
- if request.POST.has_key('post_data'):
+ if 'post_data' in request.POST:
# User must have re-authenticated through a different window
# or tab.
request.POST = _decode_post_data(request.POST['post_data'])
@@ -57,7 +57,7 @@ def staff_member_required(view_func):
assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
# If this isn't already the login page, display it.
- if not request.POST.has_key(LOGIN_FORM_KEY):
+ if LOGIN_FORM_KEY not in request.POST:
if request.POST:
message = _("Please log in again, because your session has expired. Don't worry: Your submission has been saved.")
else:
@@ -90,9 +90,9 @@ def staff_member_required(view_func):
if user.is_active and user.is_staff:
login(request, user)
# TODO: set last_login with an event.
- if request.POST.has_key('post_data'):
+ if 'post_data' in request.POST:
post_data = _decode_post_data(request.POST['post_data'])
- if post_data and not post_data.has_key(LOGIN_FORM_KEY):
+ if post_data and LOGIN_FORM_KEY not in post_data:
# overwrite request.POST with the saved post_data, and continue
request.POST = post_data
request.user = user
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 0e962adf18..5edc1fc19d 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -257,17 +257,17 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': opts.verbose_name, 'obj': new_object}
# Here, we distinguish between different save types by checking for
# the presence of keys in request.POST.
- if request.POST.has_key("_continue"):
+ if "_continue" in request.POST:
request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
- if request.POST.has_key("_popup"):
+ if "_popup" in request.POST:
post_url_continue += "?_popup=1"
return HttpResponseRedirect(post_url_continue % pk_value)
- if request.POST.has_key("_popup"):
+ if "_popup" in request.POST:
if type(pk_value) is str: # Quote if string, so JavaScript doesn't think it's a variable.
pk_value = '"%s"' % pk_value.replace('"', '\\"')
return HttpResponse('' % \
(pk_value, str(new_object).replace('"', '\\"')))
- elif request.POST.has_key("_addanother"):
+ elif "_addanother" in request.POST:
request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
return HttpResponseRedirect(request.path)
else:
@@ -288,7 +288,7 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po
c = template.RequestContext(request, {
'title': _('Add %s') % opts.verbose_name,
'form': form,
- 'is_popup': request.REQUEST.has_key('_popup'),
+ 'is_popup': '_popup' in request.REQUEST,
'show_delete': show_delete,
})
@@ -308,7 +308,7 @@ def change_stage(request, app_label, model_name, object_id):
if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
raise PermissionDenied
- if request.POST and request.POST.has_key("_saveasnew"):
+ if request.POST and "_saveasnew" in request.POST:
return add_stage(request, app_label, model_name, form_url='../../add/')
try:
@@ -343,16 +343,16 @@ def change_stage(request, app_label, model_name, object_id):
LogEntry.objects.log_action(request.user.id, ContentType.objects.get_for_model(model).id, pk_value, str(new_object), CHANGE, change_message)
msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': opts.verbose_name, 'obj': new_object}
- if request.POST.has_key("_continue"):
+ if "_continue" in request.POST:
request.user.message_set.create(message=msg + ' ' + _("You may edit it again below."))
- if request.REQUEST.has_key('_popup'):
+ if '_popup' in request.REQUEST:
return HttpResponseRedirect(request.path + "?_popup=1")
else:
return HttpResponseRedirect(request.path)
- elif request.POST.has_key("_saveasnew"):
+ elif "_saveasnew" in request.POST:
request.user.message_set.create(message=_('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': opts.verbose_name, 'obj': new_object})
return HttpResponseRedirect("../%s/" % pk_value)
- elif request.POST.has_key("_addanother"):
+ elif "_addanother" in request.POST:
request.user.message_set.create(message=msg + ' ' + (_("You may add another %s below.") % opts.verbose_name))
return HttpResponseRedirect("../add/")
else:
@@ -392,7 +392,7 @@ def change_stage(request, app_label, model_name, object_id):
'form': form,
'object_id': object_id,
'original': manipulator.original_object,
- 'is_popup': request.REQUEST.has_key('_popup'),
+ 'is_popup': '_popup' in request.REQUEST,
})
return render_change_form(model, manipulator, c, change=True)
change_stage = staff_member_required(never_cache(change_stage))
@@ -558,12 +558,12 @@ class ChangeList(object):
self.page_num = int(request.GET.get(PAGE_VAR, 0))
except ValueError:
self.page_num = 0
- self.show_all = request.GET.has_key(ALL_VAR)
- self.is_popup = request.GET.has_key(IS_POPUP_VAR)
+ self.show_all = ALL_VAR in request.GET
+ self.is_popup = IS_POPUP_VAR in request.GET
self.params = dict(request.GET.items())
- if self.params.has_key(PAGE_VAR):
+ if PAGE_VAR in self.params:
del self.params[PAGE_VAR]
- if self.params.has_key(ERROR_FLAG):
+ if ERROR_FLAG in self.params:
del self.params[ERROR_FLAG]
self.order_field, self.order_type = self.get_ordering()
@@ -594,7 +594,7 @@ class ChangeList(object):
if k.startswith(r):
del p[k]
for k, v in new_params.items():
- if p.has_key(k) and v is None:
+ if k in p and v is None:
del p[k]
elif v is not None:
p[k] = v
@@ -656,7 +656,7 @@ class ChangeList(object):
order_field, order_type = ordering[0][1:], 'desc'
else:
order_field, order_type = ordering[0], 'asc'
- if params.has_key(ORDER_VAR):
+ if ORDER_VAR in params:
try:
field_name = lookup_opts.admin.list_display[int(params[ORDER_VAR])]
try:
@@ -674,7 +674,7 @@ class ChangeList(object):
order_field = f.name
except (IndexError, ValueError):
pass # Invalid ordering specified. Just use the default.
- if params.has_key(ORDER_TYPE_VAR) and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
+ if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
order_type = params[ORDER_TYPE_VAR]
return order_field, order_type
@@ -682,7 +682,7 @@ class ChangeList(object):
qs = self.manager.get_query_set()
lookup_params = self.params.copy() # a dictionary of the query string
for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
- if lookup_params.has_key(i):
+ if i in lookup_params:
del lookup_params[i]
# Apply lookup parameters from the query string.
diff --git a/django/contrib/comments/templatetags/comments.py b/django/contrib/comments/templatetags/comments.py
index 80d4bf24ab..5c02c16f95 100644
--- a/django/contrib/comments/templatetags/comments.py
+++ b/django/contrib/comments/templatetags/comments.py
@@ -116,7 +116,7 @@ class CommentListNode(template.Node):
comment_list = get_list_function(**kwargs).order_by(self.ordering + 'submit_date').select_related()
if not self.free:
- if context.has_key('user') and context['user'].is_authenticated():
+ if 'user' in context and context['user'].is_authenticated():
user_id = context['user'].id
context['user_can_moderate_comments'] = Comment.objects.user_is_moderator(context['user'])
else:
diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py
index 12330afe41..73a9b2c480 100644
--- a/django/contrib/comments/views/comments.py
+++ b/django/contrib/comments/views/comments.py
@@ -217,10 +217,10 @@ def post_comment(request):
errors = manipulator.get_validation_errors(new_data)
# If user gave correct username/password and wasn't already logged in, log them in
# so they don't have to enter a username/password again.
- if manipulator.get_user() and not manipulator.get_user().is_authenticated() and new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']):
+ if manipulator.get_user() and not manipulator.get_user().is_authenticated() and 'password' in new_data and manipulator.get_user().check_password(new_data['password']):
from django.contrib.auth import login
login(request, manipulator.get_user())
- if errors or request.POST.has_key('preview'):
+ if errors or 'preview' in request.POST:
class CommentFormWrapper(oldforms.FormWrapper):
def __init__(self, manipulator, new_data, errors, rating_choices):
oldforms.FormWrapper.__init__(self, manipulator, new_data, errors)
@@ -244,7 +244,7 @@ def post_comment(request):
'rating_range': rating_range,
'rating_choices': rating_choices,
}, context_instance=RequestContext(request))
- elif request.POST.has_key('post'):
+ elif 'post' in request.POST:
# If the IP is banned, mail the admins, do NOT save the comment, and
# serve up the "Thanks for posting" page as if the comment WAS posted.
if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
@@ -298,7 +298,7 @@ def post_free_comment(request):
new_data['is_public'] = IS_PUBLIC in option_list
manipulator = PublicFreeCommentManipulator()
errors = manipulator.get_validation_errors(new_data)
- if errors or request.POST.has_key('preview'):
+ if errors or 'preview' in request.POST:
comment = errors and '' or manipulator.get_comment(new_data)
return render_to_response('comments/free_preview.html', {
'comment': comment,
@@ -307,7 +307,7 @@ def post_free_comment(request):
'target': target,
'hash': security_hash,
}, context_instance=RequestContext(request))
- elif request.POST.has_key('post'):
+ elif 'post' in request.POST:
# If the IP is banned, mail the admins, do NOT save the comment, and
# serve up the "Thanks for posting" page as if the comment WAS posted.
if request.META['REMOTE_ADDR'] in settings.BANNED_IPS:
@@ -330,7 +330,7 @@ def comment_was_posted(request):
The object the comment was posted on
"""
obj = None
- if request.GET.has_key('c'):
+ if 'c' in request.GET:
content_type_id, object_id = request.GET['c'].split(':')
try:
content_type = ContentType.objects.get(pk=content_type_id)
diff --git a/django/contrib/localflavor/br/forms.py b/django/contrib/localflavor/br/forms.py
index 29ad4df53d..3487787643 100644
--- a/django/contrib/localflavor/br/forms.py
+++ b/django/contrib/localflavor/br/forms.py
@@ -4,7 +4,7 @@ BR-specific Form helpers
"""
from django.newforms import ValidationError
-from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
+from django.newforms.fields import Field, RegexField, CharField, Select, EMPTY_VALUES
from django.utils.encoding import smart_unicode
from django.utils.translation import gettext
import re
@@ -15,7 +15,7 @@ class BRZipCodeField(RegexField):
def __init__(self, *args, **kwargs):
super(BRZipCodeField, self).__init__(r'^\d{5}-\d{3}$',
max_length=None, min_length=None,
- error_message=gettext(u'Enter a zip code in the format XXXXX-XXX.'),
+ error_message=gettext('Enter a zip code in the format XXXXX-XXX.'),
*args, **kwargs)
class BRPhoneNumberField(Field):
@@ -31,9 +31,89 @@ class BRPhoneNumberField(Field):
class BRStateSelect(Select):
"""
- A Select widget that uses a list of brazilian states/territories
+ A Select widget that uses a list of Brazilian states/territories
as its choices.
"""
def __init__(self, attrs=None):
from br_states import STATE_CHOICES # relative import
super(BRStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
+
+
+def DV_maker(v):
+ if v >= 2:
+ return 11 - v
+ return 0
+
+class BRCPFField(CharField):
+ """
+ This field validate a CPF number or a CPF string. A CPF number is
+ compounded by XXX.XXX.XXX-VD. The two last digits are check digits.
+
+ More information:
+ http://en.wikipedia.org/wiki/Cadastro_de_Pessoas_F%C3%ADsicas
+ """
+ def __init__(self, *args, **kwargs):
+ super(BRCPFField, self).__init__(max_length=14, min_length=11, *args, **kwargs)
+
+ def clean(self, value):
+ """
+ Value can be either a string in the format XXX.XXX.XXX-XX or an
+ 11-digit number.
+ """
+ value = super(BRCPFField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ orig_value = value[:]
+ if not value.isdigit():
+ value = re.sub("[-\.]", "", value)
+ try:
+ int(value)
+ except ValueError:
+ raise ValidationError(gettext("This field requires only numbers."))
+ if len(value) != 11:
+ raise ValidationError(gettext("This field requires at most 11 digits or 14 characters."))
+ orig_dv = value[-2:]
+
+ new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(10, 1, -1))])
+ new_1dv = DV_maker(new_1dv % 11)
+ value = value[:-2] + str(new_1dv) + value[-1]
+ new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(range(11, 1, -1))])
+ new_2dv = DV_maker(new_2dv % 11)
+ value = value[:-1] + str(new_2dv)
+ if value[-2:] != orig_dv:
+ raise ValidationError(gettext("Invalid CPF number."))
+
+ return orig_value
+
+class BRCNPJField(Field):
+ def clean(self, value):
+ """
+ Value can be either a string in the format XX.XXX.XXX/XXXX-XX or a
+ group of 14 characters.
+ """
+ value = super(BRCNPJField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ orig_value = value[:]
+ if not value.isdigit():
+ value = re.sub("[-/\.]", "", value)
+ try:
+ int(value)
+ except ValueError:
+ raise ValidationError("This field requires only numbers.")
+ if len(value) != 14:
+ raise ValidationError(
+ gettext("This field requires at least 14 digits"))
+ orig_dv = value[-2:]
+
+ new_1dv = sum([i * int(value[idx]) for idx, i in enumerate(range(5, 1, -1) + range(9, 1, -1))])
+ new_1dv = DV_maker(new_1dv % 11)
+ value = value[:-2] + str(new_1dv) + value[-1]
+ new_2dv = sum([i * int(value[idx]) for idx, i in enumerate(range(6, 1, -1) + range(9, 1, -1))])
+ new_2dv = DV_maker(new_2dv % 11)
+ value = value[:-1] + str(new_2dv)
+ if value[-2:] != orig_dv:
+ raise ValidationError(gettext("Invalid CNPJ number."))
+
+ return orig_value
+
diff --git a/django/contrib/localflavor/cl/__init__.py b/django/contrib/localflavor/cl/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/contrib/localflavor/cl/forms.py b/django/contrib/localflavor/cl/forms.py
new file mode 100644
index 0000000000..87c8093407
--- /dev/null
+++ b/django/contrib/localflavor/cl/forms.py
@@ -0,0 +1,78 @@
+"""
+Chile specific form helpers.
+"""
+
+from django.newforms import ValidationError
+from django.newforms.fields import RegexField, EMPTY_VALUES
+from django.utils.translation import gettext
+
+class CLRutField(RegexField):
+ """
+ Chilean "Rol Unico Tributario" (RUT) field. This is the Chilean national
+ identification number.
+
+ Samples for testing are available from
+ https://palena.sii.cl/cvc/dte/ee_empresas_emisoras.html
+ """
+ def __init__(self, *args, **kwargs):
+ if 'strict' in kwargs:
+ del kwargs['strict']
+ super(CLRutField, self).__init__(r'^(\d{1,2}\.)?\d{3}\.\d{3}-[\dkK]$',
+ error_message=gettext('Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'),
+ *args, **kwargs)
+ else:
+ # In non-strict mode, accept RUTs that validate but do not exist in
+ # the real world.
+ super(CLRutField, self).__init__(r'^[\d\.]{1,11}-?[\dkK]$', error_message=gettext(u'Enter valid a Chilean RUT'), *args, **kwargs)
+
+ def clean(self, value):
+ """
+ Check and clean the Chilean RUT.
+ """
+ super(CLRutField, self).clean(value)
+ if value in EMPTY_VALUES:
+ return u''
+ rut, verificador = self._canonify(value)
+ if self._algorithm(rut) == verificador:
+ return self._format(rut, verificador)
+ else:
+ raise ValidationError(u'The Chilean RUT is not valid.')
+
+ def _algorithm(self, rut):
+ """
+ Takes RUT in pure canonical form, calculates the verifier digit.
+ """
+ suma = 0
+ multi = 2
+ for r in rut[::-1]:
+ suma += int(r) * multi
+ multi += 1
+ if multi == 8:
+ multi = 2
+ return '0123456789K0'[11 - suma % 11]
+
+ def _canonify(self, rut):
+ """
+ Turns the RUT into one normalized format. Returns a (rut, verifier)
+ tuple.
+ """
+ rut = str(rut).replace(' ', '').replace('.', '').replace('-', '')
+ return rut[:-1], rut[-1]
+
+ def _format(self, code, verifier=None):
+ """
+ Formats the RUT from canonical form to the common string representation.
+ If verifier=None, then the last digit in 'code' is the verifier.
+ """
+ if verifier is None:
+ verifier = code[-1]
+ code = code[:-1]
+ while len(code) > 3 and '.' not in code[:3]:
+ pos = code.find('.')
+ if pos == -1:
+ new_dot = -3
+ else:
+ new_dot = pos - 3
+ code = code[:new_dot] + '.' + code[new_dot:]
+ return '%s-%s' % (code, verifier)
+
diff --git a/django/contrib/localflavor/is_/__init__.py b/django/contrib/localflavor/is_/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/contrib/localflavor/is_/forms.py b/django/contrib/localflavor/is_/forms.py
new file mode 100644
index 0000000000..d052acf579
--- /dev/null
+++ b/django/contrib/localflavor/is_/forms.py
@@ -0,0 +1,77 @@
+"""
+Iceland specific form helpers.
+"""
+
+from django.newforms import ValidationError
+from django.newforms.fields import RegexField, EMPTY_VALUES
+from django.newforms.widgets import Select
+from django.utils.translation import gettext
+
+class ISIdNumberField(RegexField):
+ """
+ Icelandic identification number (kennitala). This is a number every citizen
+ of Iceland has.
+ """
+ def __init__(self, *args, **kwargs):
+ error_msg = gettext(u'Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.')
+ kwargs['min_length'],kwargs['max_length'] = 10,11
+ super(ISIdNumberField, self).__init__(r'^\d{6}(-| )?\d{4}$', error_message=error_msg, *args, **kwargs)
+
+ def clean(self, value):
+ value = super(ISIdNumberField, self).clean(value)
+
+ if value in EMPTY_VALUES:
+ return u''
+
+ value = self._canonify(value)
+ if self._validate(value):
+ return self._format(value)
+ else:
+ raise ValidationError(gettext(u'The Icelandic identification number is not valid.'))
+
+ def _canonify(self, value):
+ """
+ Returns the value as only digits.
+ """
+ return value.replace('-', '').replace(' ', '')
+
+ def _validate(self, value):
+ """
+ Takes in the value in canonical form and checks the verifier digit. The
+ method is modulo 11.
+ """
+ check = [3, 2, 7, 6, 5, 4, 3, 2, 1, 0]
+ return sum(int(value[i]) * check[i] for i in range(10)) % 11 == 0
+
+ def _format(self, value):
+ """
+ Takes in the value in canonical form and returns it in the common
+ display format.
+ """
+ return value[:6]+'-'+value[6:]
+
+class ISPhoneNumberField(RegexField):
+ """
+ Icelandic phone number. Seven digits with an optional hyphen or space after
+ the first three digits.
+ """
+ def __init__(self, *args, **kwargs):
+ kwargs['min_length'], kwargs['max_length'] = 7,8
+ super(ISPhoneNumberField, self).__init__(r'^\d{3}(-| )?\d{4}$', *args, **kwargs)
+
+ def clean(self, value):
+ value = super(ISPhoneNumberField, self).clean(value)
+
+ if value in EMPTY_VALUES:
+ return u''
+
+ return value.replace('-', '').replace(' ', '')
+
+class ISPostalCodeSelect(Select):
+ """
+ A Select widget that uses a list of Icelandic postal codes as its choices.
+ """
+ def __init__(self, attrs=None):
+ from is_postalcodes import IS_POSTALCODES
+ super(ISPostalCodeSelect, self).__init__(attrs, choices=IS_POSTALCODES)
+
diff --git a/django/contrib/localflavor/is_/is_postalcodes.py b/django/contrib/localflavor/is_/is_postalcodes.py
new file mode 100644
index 0000000000..4feca9c013
--- /dev/null
+++ b/django/contrib/localflavor/is_/is_postalcodes.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+
+IS_POSTALCODES = (
+ ('101', u'101 Reykjavík'),
+ ('103', u'103 Reykjavík'),
+ ('104', u'104 Reykjavík'),
+ ('105', u'105 Reykjavík'),
+ ('107', u'107 Reykjavík'),
+ ('108', u'108 Reykjavík'),
+ ('109', u'109 Reykjavík'),
+ ('110', u'110 Reykjavík'),
+ ('111', u'111 Reykjavík'),
+ ('112', u'112 Reykjavík'),
+ ('113', u'113 Reykjavík'),
+ ('116', u'116 Kjalarnes'),
+ ('121', u'121 Reykjavík'),
+ ('123', u'123 Reykjavík'),
+ ('124', u'124 Reykjavík'),
+ ('125', u'125 Reykjavík'),
+ ('127', u'127 Reykjavík'),
+ ('128', u'128 Reykjavík'),
+ ('129', u'129 Reykjavík'),
+ ('130', u'130 Reykjavík'),
+ ('132', u'132 Reykjavík'),
+ ('150', u'150 Reykjavík'),
+ ('155', u'155 Reykjavík'),
+ ('170', u'170 Seltjarnarnes'),
+ ('172', u'172 Seltjarnarnes'),
+ ('190', u'190 Vogar'),
+ ('200', u'200 Kópavogur'),
+ ('201', u'201 Kópavogur'),
+ ('202', u'202 Kópavogur'),
+ ('203', u'203 Kópavogur'),
+ ('210', u'210 Garðabær'),
+ ('212', u'212 Garðabær'),
+ ('220', u'220 Hafnarfjörður'),
+ ('221', u'221 Hafnarfjörður'),
+ ('222', u'222 Hafnarfjörður'),
+ ('225', u'225 Álftanes'),
+ ('230', u'230 Reykjanesbær'),
+ ('232', u'232 Reykjanesbær'),
+ ('233', u'233 Reykjanesbær'),
+ ('235', u'235 Keflavíkurflugvöllur'),
+ ('240', u'240 Grindavík'),
+ ('245', u'245 Sandgerði'),
+ ('250', u'250 Garður'),
+ ('260', u'260 Reykjanesbær'),
+ ('270', u'270 Mosfellsbær'),
+ ('300', u'300 Akranes'),
+ ('301', u'301 Akranes'),
+ ('302', u'302 Akranes'),
+ ('310', u'310 Borgarnes'),
+ ('311', u'311 Borgarnes'),
+ ('320', u'320 Reykholt í Borgarfirði'),
+ ('340', u'340 Stykkishólmur'),
+ ('345', u'345 Flatey á Breiðafirði'),
+ ('350', u'350 Grundarfjörður'),
+ ('355', u'355 Ólafsvík'),
+ ('356', u'356 Snæfellsbær'),
+ ('360', u'360 Hellissandur'),
+ ('370', u'370 Búðardalur'),
+ ('371', u'371 Búðardalur'),
+ ('380', u'380 Reykhólahreppur'),
+ ('400', u'400 Ísafjörður'),
+ ('401', u'401 Ísafjörður'),
+ ('410', u'410 Hnífsdalur'),
+ ('415', u'415 Bolungarvík'),
+ ('420', u'420 Súðavík'),
+ ('425', u'425 Flateyri'),
+ ('430', u'430 Suðureyri'),
+ ('450', u'450 Patreksfjörður'),
+ ('451', u'451 Patreksfjörður'),
+ ('460', u'460 Tálknafjörður'),
+ ('465', u'465 Bíldudalur'),
+ ('470', u'470 Þingeyri'),
+ ('471', u'471 Þingeyri'),
+ ('500', u'500 Staður'),
+ ('510', u'510 Hólmavík'),
+ ('512', u'512 Hólmavík'),
+ ('520', u'520 Drangsnes'),
+ ('522', u'522 Kjörvogur'),
+ ('523', u'523 Bær'),
+ ('524', u'524 Norðurfjörður'),
+ ('530', u'530 Hvammstangi'),
+ ('531', u'531 Hvammstangi'),
+ ('540', u'540 Blönduós'),
+ ('541', u'541 Blönduós'),
+ ('545', u'545 Skagaströnd'),
+ ('550', u'550 Sauðárkrókur'),
+ ('551', u'551 Sauðárkrókur'),
+ ('560', u'560 Varmahlíð'),
+ ('565', u'565 Hofsós'),
+ ('566', u'566 Hofsós'),
+ ('570', u'570 Fljót'),
+ ('580', u'580 Siglufjörður'),
+ ('600', u'600 Akureyri'),
+ ('601', u'601 Akureyri'),
+ ('602', u'602 Akureyri'),
+ ('603', u'603 Akureyri'),
+ ('610', u'610 Grenivík'),
+ ('611', u'611 Grímsey'),
+ ('620', u'620 Dalvík'),
+ ('621', u'621 Dalvík'),
+ ('625', u'625 Ólafsfjörður'),
+ ('630', u'630 Hrísey'),
+ ('640', u'640 Húsavík'),
+ ('641', u'641 Húsavík'),
+ ('645', u'645 Fosshóll'),
+ ('650', u'650 Laugar'),
+ ('660', u'660 Mývatn'),
+ ('670', u'670 Kópasker'),
+ ('671', u'671 Kópasker'),
+ ('675', u'675 Raufarhöfn'),
+ ('680', u'680 Þórshöfn'),
+ ('681', u'681 Þórshöfn'),
+ ('685', u'685 Bakkafjörður'),
+ ('690', u'690 Vopnafjörður'),
+ ('700', u'700 Egilsstaðir'),
+ ('701', u'701 Egilsstaðir'),
+ ('710', u'710 Seyðisfjörður'),
+ ('715', u'715 Mjóifjörður'),
+ ('720', u'720 Borgarfjörður eystri'),
+ ('730', u'730 Reyðarfjörður'),
+ ('735', u'735 Eskifjörður'),
+ ('740', u'740 Neskaupstaður'),
+ ('750', u'750 Fáskrúðsfjörður'),
+ ('755', u'755 Stöðvarfjörður'),
+ ('760', u'760 Breiðdalsvík'),
+ ('765', u'765 Djúpivogur'),
+ ('780', u'780 Höfn í Hornafirði'),
+ ('781', u'781 Höfn í Hornafirði'),
+ ('785', u'785 Öræfi'),
+ ('800', u'800 Selfoss'),
+ ('801', u'801 Selfoss'),
+ ('802', u'802 Selfoss'),
+ ('810', u'810 Hveragerði'),
+ ('815', u'815 Þorlákshöfn'),
+ ('820', u'820 Eyrarbakki'),
+ ('825', u'825 Stokkseyri'),
+ ('840', u'840 Laugarvatn'),
+ ('845', u'845 Flúðir'),
+ ('850', u'850 Hella'),
+ ('851', u'851 Hella'),
+ ('860', u'860 Hvolsvöllur'),
+ ('861', u'861 Hvolsvöllur'),
+ ('870', u'870 Vík'),
+ ('871', u'871 Vík'),
+ ('880', u'880 Kirkjubæjarklaustur'),
+ ('900', u'900 Vestmannaeyjar'),
+ ('902', u'902 Vestmannaeyjar')
+)
diff --git a/django/contrib/sitemaps/views.py b/django/contrib/sitemaps/views.py
index 576e3d0bb8..d615c8e661 100644
--- a/django/contrib/sitemaps/views.py
+++ b/django/contrib/sitemaps/views.py
@@ -16,7 +16,7 @@ def index(request, sitemaps):
def sitemap(request, sitemaps, section=None):
maps, urls = [], []
if section is not None:
- if not sitemaps.has_key(section):
+ if section not in sitemaps:
raise Http404("No sitemap available for section: %r" % section)
maps.append(sitemaps[section])
else:
diff --git a/django/core/cache/backends/simple.py b/django/core/cache/backends/simple.py
index 175944a75a..3fcad8c7ad 100644
--- a/django/core/cache/backends/simple.py
+++ b/django/core/cache/backends/simple.py
@@ -52,7 +52,7 @@ class CacheClass(BaseCache):
pass
def has_key(self, key):
- return self._cache.has_key(key)
+ return key in self._cache
def _cull(self):
if self._cull_frequency == 0:
diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py
index 5fc41a048b..6370cab47c 100644
--- a/django/core/handlers/modpython.py
+++ b/django/core/handlers/modpython.py
@@ -42,11 +42,11 @@ class ModPythonRequest(http.HttpRequest):
def is_secure(self):
# Note: modpython 3.2.10+ has req.is_https(), but we need to support previous versions
- return self._req.subprocess_env.has_key('HTTPS') and self._req.subprocess_env['HTTPS'] == 'on'
+ return 'HTTPS' in self._req.subprocess_env and self._req.subprocess_env['HTTPS'] == 'on'
def _load_post_and_files(self):
"Populates self._post and self._files"
- if self._req.headers_in.has_key('content-type') and self._req.headers_in['content-type'].startswith('multipart'):
+ if 'content-type' in self._req.headers_in and self._req.headers_in['content-type'].startswith('multipart'):
self._post, self._files = http.parse_file_upload(self._req.headers_in, self.raw_post_data)
else:
self._post, self._files = http.QueryDict(self.raw_post_data), datastructures.MultiValueDict()
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index 71cfecd9a0..4320b69627 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -103,7 +103,7 @@ class WSGIRequest(http.HttpRequest):
return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
def is_secure(self):
- return self.environ.has_key('HTTPS') and self.environ['HTTPS'] == 'on'
+ return 'HTTPS' in self.environ and self.environ['HTTPS'] == 'on'
def _load_post_and_files(self):
# Populates self._post and self._files
diff --git a/django/core/management.py b/django/core/management.py
index 8e86e6b6ba..d696794fc6 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -356,7 +356,7 @@ def get_sql_delete(app):
# Drop the table now
output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
style.SQL_TABLE(backend.quote_name(model._meta.db_table))))
- if backend.supports_constraints and references_to_delete.has_key(model):
+ if backend.supports_constraints and model in references_to_delete:
for rel_class, f in references_to_delete[model]:
table = rel_class._meta.db_table
col = f.column
@@ -902,7 +902,7 @@ def inspectdb():
att_name += '_field'
comment_notes.append('Field renamed because it was a Python reserved word.')
- if relations.has_key(i):
+ if i in relations:
rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
field_type = 'ForeignKey(%s' % rel_to
if att_name.endswith('_id'):
@@ -1609,7 +1609,7 @@ def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None):
action = args[0]
except IndexError:
parser.print_usage_and_exit()
- if not action_mapping.has_key(action):
+ if action not in action_mapping:
print_error("Your action, %r, was invalid." % action, argv[0])
# Switch to English, because django-admin.py creates database content
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index a16b8b675a..80a0bf6a91 100644
--- a/django/core/servers/basehttp.py
+++ b/django/core/servers/basehttp.py
@@ -208,15 +208,15 @@ def guess_scheme(environ):
else:
return 'http'
-_hoppish = {
+_hop_headers = {
'connection':1, 'keep-alive':1, 'proxy-authenticate':1,
'proxy-authorization':1, 'te':1, 'trailers':1, 'transfer-encoding':1,
'upgrade':1
-}.has_key
+}
def is_hop_by_hop(header_name):
"""Return true if 'header_name' is an HTTP/1.1 "Hop-by-Hop" header"""
- return _hoppish(header_name.lower())
+ return header_name.lower() in _hop_headers
class ServerHandler(object):
"""Manage the invocation of a WSGI application"""
@@ -334,7 +334,7 @@ class ServerHandler(object):
Subclasses can extend this to add other defaults.
"""
- if not self.headers.has_key('Content-Length'):
+ if 'Content-Length' not in self.headers:
self.set_content_length()
def start_response(self, status, headers,exc_info=None):
@@ -368,11 +368,11 @@ class ServerHandler(object):
if self.origin_server:
if self.client_is_modern():
self._write('HTTP/%s %s\r\n' % (self.http_version,self.status))
- if not self.headers.has_key('Date'):
+ if 'Date' not in self.headers:
self._write(
'Date: %s\r\n' % time.asctime(time.gmtime(time.time()))
)
- if self.server_software and not self.headers.has_key('Server'):
+ if self.server_software and 'Server' not in self.headers:
self._write('Server: %s\r\n' % self.server_software)
else:
self._write('Status: %s\r\n' % self.status)
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index c4cbccabcf..38b3263da1 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -102,6 +102,14 @@ class RegexURLPattern(object):
self.default_args = default_args or {}
self.name = name
+ def add_prefix(self, prefix):
+ """
+ Adds the prefix string to a string-based callback.
+ """
+ if not prefix or not hasattr(self, '_callback_str'):
+ return
+ self._callback_str = prefix + '.' + self._callback_str
+
def resolve(self, path):
match = self.regex.search(path)
if match:
diff --git a/django/core/validators.py b/django/core/validators.py
index bd7d790e04..26165c4af1 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -284,7 +284,7 @@ class ValidateIfOtherFieldEquals(object):
self.always_test = True
def __call__(self, field_data, all_data):
- if all_data.has_key(self.other_field) and all_data[self.other_field] == self.other_value:
+ if self.other_field in all_data and all_data[self.other_field] == self.other_value:
for v in self.validator_list:
v(field_data, all_data)
@@ -322,7 +322,7 @@ class RequiredIfOtherFieldEquals(object):
self.always_test = True
def __call__(self, field_data, all_data):
- if all_data.has_key(self.other_field) and all_data[self.other_field] == self.other_value and not field_data:
+ if self.other_field in all_data and all_data[self.other_field] == self.other_value and not field_data:
raise ValidationError(self.error_message)
class RequiredIfOtherFieldDoesNotEqual(object):
@@ -335,7 +335,7 @@ class RequiredIfOtherFieldDoesNotEqual(object):
self.always_test = True
def __call__(self, field_data, all_data):
- if all_data.has_key(self.other_field) and all_data[self.other_field] != self.other_value and not field_data:
+ if self.other_field in all_data and all_data[self.other_field] != self.other_value and not field_data:
raise ValidationError(self.error_message)
class IsLessThanOtherField(object):
diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py
index 01eff22641..d56b8513f9 100644
--- a/django/db/backends/mysql_old/base.py
+++ b/django/db/backends/mysql_old/base.py
@@ -53,7 +53,7 @@ class MysqlDebugWrapper:
raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
def __getattr__(self, attr):
- if self.__dict__.has_key(attr):
+ if attr in self.__dict__:
return self.__dict__[attr]
else:
return getattr(self.cursor, attr)
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index be9c1db543..66479eb557 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -48,7 +48,7 @@ class UnicodeCursorWrapper(object):
return self.cursor.executemany(sql, new_param_list)
def __getattr__(self, attr):
- if self.__dict__.has_key(attr):
+ if attr in self.__dict__:
return self.__dict__[attr]
else:
return getattr(self.cursor, attr)
diff --git a/django/db/backends/util.py b/django/db/backends/util.py
index d9489d63da..2fb496ddea 100644
--- a/django/db/backends/util.py
+++ b/django/db/backends/util.py
@@ -34,7 +34,7 @@ class CursorDebugWrapper(object):
})
def __getattr__(self, attr):
- if self.__dict__.has_key(attr):
+ if attr in self.__dict__:
return self.__dict__[attr]
else:
return getattr(self.cursor, attr)
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index b65b67db5a..aa1a4ec08e 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -788,7 +788,7 @@ class SlugField(Field):
kwargs['maxlength'] = kwargs.get('maxlength', 50)
kwargs.setdefault('validator_list', []).append(validators.isSlug)
# Set db_index=True unless it's been set manually.
- if not kwargs.has_key('db_index'):
+ if 'db_index' not in kwargs:
kwargs['db_index'] = True
Field.__init__(self, *args, **kwargs)
diff --git a/django/db/models/fields/generic.py b/django/db/models/fields/generic.py
index 480ee689c9..f995ab2044 100644
--- a/django/db/models/fields/generic.py
+++ b/django/db/models/fields/generic.py
@@ -37,7 +37,7 @@ class GenericForeignKey(object):
def instance_pre_init(self, signal, sender, args, kwargs):
# Handle initalizing an object with the generic FK instaed of
# content-type/object-id fields.
- if kwargs.has_key(self.name):
+ if self.name in kwargs:
value = kwargs.pop(self.name)
kwargs[self.ct_field] = self.get_content_type(value)
kwargs[self.fk_field] = value._get_pk_val()
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index c336585ac9..bb17d4d7bf 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -471,7 +471,7 @@ class ForeignKey(RelatedField, Field):
to_field = to_field or to._meta.pk.name
kwargs['verbose_name'] = kwargs.get('verbose_name', '')
- if kwargs.has_key('edit_inline_type'):
+ if 'edit_inline_type' in kwargs:
import warnings
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
@@ -564,7 +564,7 @@ class OneToOneField(RelatedField, IntegerField):
to_field = to_field or to._meta.pk.name
kwargs['verbose_name'] = kwargs.get('verbose_name', '')
- if kwargs.has_key('edit_inline_type'):
+ if 'edit_inline_type' in kwargs:
import warnings
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
diff --git a/django/db/models/loading.py b/django/db/models/loading.py
index f4aff2438b..224f5e8451 100644
--- a/django/db/models/loading.py
+++ b/django/db/models/loading.py
@@ -103,7 +103,7 @@ def register_models(app_label, *models):
# in the _app_models dictionary
model_name = model._meta.object_name.lower()
model_dict = _app_models.setdefault(app_label, {})
- if model_dict.has_key(model_name):
+ if model_name in model_dict:
# The same model may be imported via different paths (e.g.
# appname.models and project.appname.models). We use the source
# filename as a means to detect identity.
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 88e2d88a46..93627f7b72 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -145,7 +145,7 @@ class Options(object):
def get_follow(self, override=None):
follow = {}
for f in self.fields + self.many_to_many + self.get_all_related_objects():
- if override and override.has_key(f.name):
+ if override and f.name in override:
child_override = override[f.name]
else:
child_override = None
@@ -187,7 +187,7 @@ class Options(object):
# TODO: follow
if not hasattr(self, '_field_types'):
self._field_types = {}
- if not self._field_types.has_key(field_type):
+ if field_type not in self._field_types:
try:
# First check self.fields.
for f in self.fields:
diff --git a/django/db/transaction.py b/django/db/transaction.py
index 4a0658e1c3..bb90713525 100644
--- a/django/db/transaction.py
+++ b/django/db/transaction.py
@@ -46,12 +46,12 @@ def enter_transaction_management():
when no current block is running).
"""
thread_ident = thread.get_ident()
- if state.has_key(thread_ident) and state[thread_ident]:
+ if thread_ident in state and state[thread_ident]:
state[thread_ident].append(state[thread_ident][-1])
else:
state[thread_ident] = []
state[thread_ident].append(settings.TRANSACTIONS_MANAGED)
- if not dirty.has_key(thread_ident):
+ if thread_ident not in dirty:
dirty[thread_ident] = False
def leave_transaction_management():
@@ -61,7 +61,7 @@ def leave_transaction_management():
those from outside. (Commits are on connection level.)
"""
thread_ident = thread.get_ident()
- if state.has_key(thread_ident) and state[thread_ident]:
+ if thread_ident in state and state[thread_ident]:
del state[thread_ident][-1]
else:
raise TransactionManagementError("This code isn't under transaction management")
@@ -84,7 +84,7 @@ def set_dirty():
changes waiting for commit.
"""
thread_ident = thread.get_ident()
- if dirty.has_key(thread_ident):
+ if thread_ident in dirty:
dirty[thread_ident] = True
else:
raise TransactionManagementError("This code isn't under transaction management")
@@ -96,7 +96,7 @@ def set_clean():
should happen.
"""
thread_ident = thread.get_ident()
- if dirty.has_key(thread_ident):
+ if thread_ident in dirty:
dirty[thread_ident] = False
else:
raise TransactionManagementError("This code isn't under transaction management")
@@ -106,7 +106,7 @@ def is_managed():
Checks whether the transaction manager is in manual or in auto state.
"""
thread_ident = thread.get_ident()
- if state.has_key(thread_ident):
+ if thread_ident in state:
if state[thread_ident]:
return state[thread_ident][-1]
return settings.TRANSACTIONS_MANAGED
diff --git a/django/http/__init__.py b/django/http/__init__.py
index ed2c128a16..a0c51ff0da 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -29,12 +29,12 @@ class HttpRequest(object):
def __getitem__(self, key):
for d in (self.POST, self.GET):
- if d.has_key(key):
+ if key in d:
return d[key]
raise KeyError, "%s not found in either POST or GET" % key
def has_key(self, key):
- return self.GET.has_key(key) or self.POST.has_key(key)
+ return key in self.GET or key in self.POST
def get_full_path(self):
return ''
@@ -57,7 +57,7 @@ def parse_file_upload(header_dict, post_data):
# name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
# or {'name': 'blah'} for POST fields
# We assume all uploaded files have a 'filename' set.
- if name_dict.has_key('filename'):
+ if 'filename' in name_dict:
assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
if not name_dict['filename'].strip():
continue
@@ -66,7 +66,7 @@ def parse_file_upload(header_dict, post_data):
filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
FILES.appendlist(name_dict['name'], {
'filename': filename,
- 'content-type': (submessage.has_key('Content-Type') and submessage['Content-Type'] or None),
+ 'content-type': 'Content-Type' in submessage and submessage['Content-Type'] or None,
'content': submessage.get_payload(),
})
else:
diff --git a/django/middleware/common.py b/django/middleware/common.py
index 9891b1efad..2c72c9a583 100644
--- a/django/middleware/common.py
+++ b/django/middleware/common.py
@@ -25,7 +25,7 @@ class CommonMiddleware(object):
"""
# Check for denied User-Agents
- if request.META.has_key('HTTP_USER_AGENT'):
+ if 'HTTP_USER_AGENT' in request.META:
for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
if user_agent_regex.search(request.META['HTTP_USER_AGENT']):
return http.HttpResponseForbidden('
Forbidden
')
diff --git a/django/newforms/fields.py b/django/newforms/fields.py
index 7c542b9001..509c099e86 100644
--- a/django/newforms/fields.py
+++ b/django/newforms/fields.py
@@ -457,7 +457,7 @@ class MultiValueField(Field):
for i, field in enumerate(self.fields):
try:
field_value = value[i]
- except KeyError:
+ except IndexError:
field_value = None
if self.required and field_value in EMPTY_VALUES:
raise ValidationError(gettext(u'This field is required.'))
diff --git a/django/newforms/forms.py b/django/newforms/forms.py
index 1f37da91ff..3df7a9e834 100644
--- a/django/newforms/forms.py
+++ b/django/newforms/forms.py
@@ -244,7 +244,7 @@ class BoundField(StrAndUnicode):
def as_widget(self, widget, attrs=None):
attrs = attrs or {}
auto_id = self.auto_id
- if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'):
+ if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
attrs['id'] = auto_id
if not self.form.is_bound:
data = self.form.initial.get(self.name, self.field.initial)
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py
index d50b1921ea..dd71ebc455 100644
--- a/django/newforms/widgets.py
+++ b/django/newforms/widgets.py
@@ -230,7 +230,7 @@ class RadioInput(StrAndUnicode):
return self.value == self.choice_value
def tag(self):
- if self.attrs.has_key('id'):
+ if 'id' in self.attrs:
self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
if self.is_checked():
@@ -276,7 +276,7 @@ class RadioSelect(Select):
class CheckboxSelectMultiple(SelectMultiple):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
- has_id = attrs and attrs.has_key('id')
+ has_id = attrs and 'id' in attrs
final_attrs = self.build_attrs(attrs, name=name)
output = [u'
']
str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
@@ -347,7 +347,7 @@ class MultiWidget(Widget):
id_for_label = classmethod(id_for_label)
def value_from_datadict(self, data, name):
- return [data.get(name + '_%s' % i) for i in range(len(self.widgets))]
+ return [widget.value_from_datadict(data, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
def format_output(self, rendered_widgets):
return u''.join(rendered_widgets)
diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
index 873bd0204a..0a4676ee8f 100644
--- a/django/oldforms/__init__.py
+++ b/django/oldforms/__init__.py
@@ -329,7 +329,7 @@ class FormField(object):
def convert_post_data(self, new_data):
name = self.get_member_name()
- if new_data.has_key(self.field_name):
+ if self.field_name in new_data:
d = new_data.getlist(self.field_name)
try:
converted_data = [self.__class__.html2python(data) for data in d]
diff --git a/django/template/__init__.py b/django/template/__init__.py
index a32b3f5a0b..a592765db6 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -338,7 +338,7 @@ class Parser(object):
return FilterExpression(token, self)
def find_filter(self, filter_name):
- if self.filters.has_key(filter_name):
+ if filter_name in self.filters:
return self.filters[filter_name]
else:
raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name
diff --git a/django/template/context.py b/django/template/context.py
index 25397b0e1a..59650b05fe 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -35,7 +35,7 @@ class Context(object):
def __getitem__(self, key):
"Get a variable's value, starting at the current context and going upward"
for d in self.dicts:
- if d.has_key(key):
+ if key in d:
return d[key]
raise KeyError(key)
@@ -45,7 +45,7 @@ class Context(object):
def has_key(self, key):
for d in self.dicts:
- if d.has_key(key):
+ if key in d:
return True
return False
@@ -54,7 +54,7 @@ class Context(object):
def get(self, key, otherwise=None):
for d in self.dicts:
- if d.has_key(key):
+ if key in d:
return d[key]
return otherwise
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 2deae6d403..1ebb01442e 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -87,7 +87,7 @@ class ForNode(Node):
def render(self, context):
nodelist = NodeList()
- if context.has_key('forloop'):
+ if 'forloop' in context:
parentloop = context['forloop']
else:
parentloop = {}
@@ -133,7 +133,7 @@ class IfChangedNode(Node):
self._varlist = varlist
def render(self, context):
- if context.has_key('forloop') and context['forloop']['first']:
+ if 'forloop' in context and context['forloop']['first']:
self._last_seen = None
try:
if self._varlist:
@@ -432,7 +432,7 @@ def cycle(parser, token):
name = args[1]
if not hasattr(parser, '_namedCycleNodes'):
raise TemplateSyntaxError("No named cycles in template: '%s' is not defined" % name)
- if not parser._namedCycleNodes.has_key(name):
+ if name not in parser._namedCycleNodes:
raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
return parser._namedCycleNodes[name]
@@ -911,7 +911,7 @@ def templatetag(parser, token):
if len(bits) != 2:
raise TemplateSyntaxError, "'templatetag' statement takes one argument"
tag = bits[1]
- if not TemplateTagNode.mapping.has_key(tag):
+ if tag not in TemplateTagNode.mapping:
raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \
(tag, TemplateTagNode.mapping.keys())
return TemplateTagNode(tag)
diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py
index cca30b462d..60bc0051a2 100644
--- a/django/utils/datastructures.py
+++ b/django/utils/datastructures.py
@@ -42,7 +42,7 @@ class MergeDict(object):
def has_key(self, key):
for dict in self.dicts:
- if dict.has_key(key):
+ if key in dict:
return True
return False
diff --git a/django/utils/functional.py b/django/utils/functional.py
index e3c0a3c76b..0c31c1f375 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -42,7 +42,7 @@ def lazy(func, *resultclasses):
res = self.__func(*self.__args, **self.__kw)
return self.__dispatch[type(res)][funcname](res, *args, **kw)
- if not self.__dispatch.has_key(klass):
+ if klass not in self.__dispatch:
self.__dispatch[klass] = {}
self.__dispatch[klass][funcname] = func
return __wrapper__
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index 0c8dcd540f..293b4ef9cd 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -199,7 +199,7 @@ def deactivate():
will resolve against the default translation object, again.
"""
global _active
- if _active.has_key(currentThread()):
+ if currentThread() in _active:
del _active[currentThread()]
def get_language():
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 0a19cfe986..0fec6b6c6f 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -97,7 +97,7 @@ def javascript_catalog(request, domain='djangojs', packages=None):
deliver your JavaScript source from Django templates.
"""
if request.GET:
- if request.GET.has_key('language'):
+ if 'language' in request.GET:
if check_for_language(request.GET['language']):
activate(request.GET['language'])
if packages is None:
@@ -136,7 +136,7 @@ def javascript_catalog(request, domain='djangojs', packages=None):
t.update(catalog._catalog)
src = [LibHead]
plural = None
- if t.has_key(''):
+ if '' in t:
for l in t[''].split('\n'):
if l.startswith('Plural-Forms:'):
plural = l.split(':',1)[1].strip()
@@ -155,7 +155,7 @@ def javascript_catalog(request, domain='djangojs', packages=None):
if type(k) in (str, unicode):
csrc.append("catalog['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(v)))
elif type(k) == tuple:
- if not pdict.has_key(k[0]):
+ if k[0] not in pdict:
pdict[k[0]] = k[1]
else:
pdict[k[0]] = max(k[1], pdict[k[0]])
diff --git a/docs/authentication.txt b/docs/authentication.txt
index 14ca581877..091a30a895 100644
--- a/docs/authentication.txt
+++ b/docs/authentication.txt
@@ -208,7 +208,8 @@ Hashtype is either ``sha1`` (default), ``md5`` or ``crypt`` -- the algorithm
used to perform a one-way hash of the password. Salt is a random string used
to salt the raw password to create the hash. Note that the ``crypt`` method is
only supported on platforms that have the standard Python ``crypt`` module
-available.
+available, and ``crypt`` support is only available in the Django development
+version.
For example::
diff --git a/docs/install.txt b/docs/install.txt
index c337be404b..defa3e38ad 100644
--- a/docs/install.txt
+++ b/docs/install.txt
@@ -45,16 +45,16 @@ Get your database running
=========================
If you plan to use Django's database API functionality, you'll need to
-make sure a database server is running. Django works with PostgreSQL_
-(recommended), MySQL_ and SQLite_.
+make sure a database server is running. Django works with PostgreSQL_,
+MySQL_ and SQLite_.
Additionally, you'll need to make sure your Python database bindings are
installed.
-* If you're using PostgreSQL, you'll need the psycopg_ package (version 2 is
- recommended with ``postgresql_psycopg2`` backend, version 1.1 works also with the
- ``postgresql``` backend).
-
+* If you're using PostgreSQL, you'll need the psycopg_ package. Django supports
+ both version 1 and 2. (When you configure Django's database layer, specify
+ either ``postgresql`` [for version 1] or ``postgresql_psycopg2`` [for version 2].)
+
If you're on Windows, check out the unofficial `compiled Windows version`_.
* If you're using MySQL, you'll need MySQLdb_, version 1.2.1p2 or higher.
diff --git a/docs/settings.txt b/docs/settings.txt
index 5315c683c7..9ab5f5ff82 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -562,9 +562,23 @@ strings for translation, but the translation won't happen at runtime -- so
you'll have to remember to wrap the languages in the *real* ``gettext()`` in
any code that uses ``LANGUAGES`` at runtime.
+LOGIN_REDIRECT_URL
+------------------
+
+**New in Django development version**
+
+Default: ``'/accounts/profile/'``
+
+The URL where requests are redirected after login when the
+``contrib.auth.login`` view gets no ``next`` parameter.
+
+This is used by the `@login_required`_ decorator, for example.
+
LOGIN_URL
---------
+**New in Django development version**
+
Default: ``'/accounts/login/'``
The URL where requests are redirected for login, specially when using the
@@ -573,6 +587,8 @@ The URL where requests are redirected for login, specially when using the
LOGOUT_URL
----------
+**New in Django development version**
+
Default: ``'/accounts/logout/'``
LOGIN_URL counterpart.
@@ -635,16 +651,6 @@ locales have different formats. For example, U.S. English would say
See `allowed date format strings`_. See also DATE_FORMAT, DATETIME_FORMAT,
TIME_FORMAT and YEAR_MONTH_FORMAT.
-LOGIN_REDIRECT_URL
-------------------
-
-Default: ``'/accounts/profile/'``
-
-The URL where requests are redirected after login when the
-``contrib.auth.login`` view gets no ``next`` parameter.
-
-This is used by the `@login_required`_ decorator, for example.
-
PREPEND_WWW
-----------
diff --git a/tests/regressiontests/dispatch/tests/test_saferef.py b/tests/regressiontests/dispatch/tests/test_saferef.py
index 233a2023e0..c0ec879a4b 100644
--- a/tests/regressiontests/dispatch/tests/test_saferef.py
+++ b/tests/regressiontests/dispatch/tests/test_saferef.py
@@ -55,8 +55,10 @@ class Tester(unittest.TestCase):
for t in self.ts:
if hasattr(t, 'x'):
self.assert_(sd.has_key(safeRef(t.x)))
+ self.assert_(safeRef(t.x) in sd)
else:
self.assert_(sd.has_key(safeRef(t)))
+ self.assert_(safeRef(t) in sd)
def testRepresentation (self):
"""Test that the reference object's representation works
diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py
index 3f4f41ae8d..0efec8cfa8 100644
--- a/tests/regressiontests/forms/localflavor.py
+++ b/tests/regressiontests/forms/localflavor.py
@@ -840,6 +840,40 @@ ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
>>> f.clean('12345-123')
u'12345-123'
+# BRCNPJField ############################################################
+
+>>> from django.contrib.localflavor.br.forms import BRCNPJField
+>>> f = BRCNPJField(required=True)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('12-345-678/9012-10')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CNPJ number.']
+>>> f.clean('12.345.678/9012-10')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CNPJ number.']
+>>> f.clean('12345678/9012-10')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CNPJ number.']
+>>> f.clean('64.132.916/0001-88')
+'64.132.916/0001-88'
+>>> f.clean('64-132-916/0001-88')
+'64-132-916/0001-88'
+>>> f.clean('64132916/0001-88')
+'64132916/0001-88'
+>>> f.clean('64.132.916/0001-XX')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires only numbers.']
+>>> f = BRCNPJField(required=False)
+>>> f.clean('')
+u''
+
# BRPhoneNumberField #########################################################
>>> from django.contrib.localflavor.br.forms import BRPhoneNumberField
@@ -1052,4 +1086,166 @@ states/territories as its choices.
+
+## ISIdNumberField #############################################################
+
+>>> from django.contrib.localflavor.is_.forms import *
+>>> f = ISIdNumberField()
+>>> f.clean('2308803449')
+u'230880-3449'
+>>> f.clean('230880-3449')
+u'230880-3449'
+>>> f.clean('230880 3449')
+u'230880-3449'
+>>> f.clean('230880343')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 10 characters.']
+>>> f.clean('230880343234')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 11 characters.']
+>>> f.clean('abcdefghijk')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('2308803439')
+Traceback (most recent call last):
+...
+ValidationError: [u'The Icelandic identification number is not valid.']
+>>> f.clean('2308803440')
+u'230880-3440'
+>>> f = ISIdNumberField(required=False)
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+## ISPhoneNumberField #############################################################
+
+>>> from django.contrib.localflavor.is_.forms import *
+>>> f = ISPhoneNumberField()
+>>> f.clean('1234567')
+u'1234567'
+>>> f.clean('123 4567')
+u'1234567'
+>>> f.clean('123-4567')
+u'1234567'
+>>> f.clean('123-456')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid value.']
+>>> f.clean('123456')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 7 characters.']
+>>> f.clean('123456555')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 8 characters.']
+>>> f.clean('abcdefg')
+Traceback (most recent call last):
+ValidationError: [u'Enter a valid value.']
+>>> f.clean(' 1234567 ')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 8 characters.']
+>>> f.clean(' 12367 ')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid value.']
+
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f = ISPhoneNumberField(required=False)
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+## ISPostalCodeSelect #############################################################
+
+>>> from django.contrib.localflavor.is_.forms import *
+>>> f = ISPostalCodeSelect()
+
+>>> f.render('foo', 'bar')
+u''
+
+## CLRutField #############################################################
+
+CLRutField is a Field that checks the validity of the Chilean
+personal identification number (RUT). It has two modes relaxed (default) and
+strict.
+
+>>> from django.contrib.localflavor.cl.forms import CLRutField
+>>> rut = CLRutField()
+
+>>> rut.clean('11-6')
+'11-6'
+>>> rut.clean('116')
+'11-6'
+
+# valid format, bad verifier.
+>>> rut.clean('11.111.111-0')
+Traceback (most recent call last):
+...
+ValidationError: [u'The Chilean RUT is not valid.']
+>>> rut.clean('111')
+Traceback (most recent call last):
+...
+ValidationError: [u'The Chilean RUT is not valid.']
+
+>>> rut.clean('767484100')
+'76.748.410-0'
+>>> rut.clean('78.412.790-7')
+'78.412.790-7'
+>>> rut.clean('8.334.6043')
+'8.334.604-3'
+>>> rut.clean('76793310-K')
+'76.793.310-K'
+
+Strict RUT usage (does not allow imposible values)
+>>> rut = CLRutField(strict=True)
+
+>>> rut.clean('11-6')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.']
+
+# valid format, bad verifier.
+>>> rut.clean('11.111.111-0')
+Traceback (most recent call last):
+...
+ValidationError: [u'The Chilean RUT is not valid.']
+
+# Correct input, invalid format.
+>>> rut.clean('767484100')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.']
+>>> rut.clean('78.412.790-7')
+'78.412.790-7'
+>>> rut.clean('8.334.6043')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.']
+>>> rut.clean('76793310-K')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.']
+
"""
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index 8f8b3c828a..e0d05a2c89 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -5,6 +5,7 @@ from regressions import regression_tests
form_tests = r"""
>>> from django.newforms import *
>>> import datetime
+>>> import time
>>> import re
###########
@@ -3297,6 +3298,94 @@ True
+# MultiWidget and MultiValueField #############################################
+# MultiWidgets are widgets composed of other widgets. They are usually
+# combined with MultiValueFields - a field that is composed of other fields.
+# MulitWidgets can themselved be composed of other MultiWidgets.
+# SplitDateTimeWidget is one example of a MultiWidget.
+
+>>> class ComplexMultiWidget(MultiWidget):
+... def __init__(self, attrs=None):
+... widgets = (
+... TextInput(),
+... SelectMultiple(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
+... SplitDateTimeWidget(),
+... )
+... super(ComplexMultiWidget, self).__init__(widgets, attrs)
+...
+... def decompress(self, value):
+... if value:
+... data = value.split(',')
+... return [data[0], data[1], datetime.datetime(*time.strptime(data[2], "%Y-%m-%d %H:%M:%S")[0:6])]
+... return [None, None, None]
+... def format_output(self, rendered_widgets):
+... return u'\n'.join(rendered_widgets)
+>>> w = ComplexMultiWidget()
+>>> print w.render('name', 'some text,JP,2007-04-25 06:24:00')
+
+
+
+
+>>> class ComplexField(MultiValueField):
+... def __init__(self, required=True, widget=None, label=None, initial=None):
+... fields = (
+... CharField(),
+... MultipleChoiceField(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
+... SplitDateTimeField()
+... )
+... super(ComplexField, self).__init__(fields, required, widget, label, initial)
+...
+... def compress(self, data_list):
+... if data_list:
+... return '%s,%s,%s' % (data_list[0],''.join(data_list[1]),data_list[2])
+... return None
+
+>>> f = ComplexField(widget=w)
+>>> f.clean(['some text', ['J','P'], ['2007-04-25','6:24:00']])
+u'some text,JP,2007-04-25 06:24:00'
+>>> f.clean(['some text',['X'], ['2007-04-25','6:24:00']])
+Traceback (most recent call last):
+...
+ValidationError: [u'Select a valid choice. X is not one of the available choices.']
+
+# If insufficient data is provided, None is substituted
+>>> f.clean(['some text',['JP']])
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> class ComplexFieldForm(Form):
+... field1 = ComplexField(widget=w)
+>>> f = ComplexFieldForm()
+>>> print f
+
+
+
+
+>>> f = ComplexFieldForm({'field1_0':'some text','field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'})
+>>> print f
+
+
+
+
+>>> f.clean_data
+{'field1': u'some text,JP,2007-04-25 06:24:00'}
+
#################################
# Tests of underlying functions #
#################################
diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py
index 385c3048d9..29cb8385e0 100644
--- a/tests/regressiontests/httpwrappers/tests.py
+++ b/tests/regressiontests/httpwrappers/tests.py
@@ -34,6 +34,9 @@ AttributeError: This QueryDict instance is immutable
>>> q.has_key('foo')
False
+>>> 'foo' in q
+False
+
>>> q.items()
[]
@@ -124,6 +127,9 @@ MultiValueDictKeyError: "Key 'foo' not found in "
>>> q.has_key('foo')
True
+>>> 'foo' in q
+True
+
>>> q.items()
[('foo', 'another'), ('name', 'john')]
@@ -218,9 +224,15 @@ AttributeError: This QueryDict instance is immutable
>>> q.has_key('foo')
True
+>>> 'foo' in q
+True
+
>>> q.has_key('bar')
False
+>>> 'bar' in q
+False
+
>>> q.items()
[('foo', 'bar')]
@@ -303,9 +315,15 @@ AttributeError: This QueryDict instance is immutable
>>> q.has_key('vote')
True
+>>> 'vote' in q
+True
+
>>> q.has_key('foo')
False
+>>> 'foo' in q
+False
+
>>> q.items()
[('vote', 'no')]