mirror of
				https://github.com/django/django.git
				synced 2025-10-30 23:47:49 +03:00 
			
		
		
		
	Merged soc2009/model-validation to trunk. Thanks, Honza!
git-svn-id: http://code.djangoproject.com/svn/django/trunk@12098 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		
							parent
							
								
									4e89105d64
								
							
						
					
					
						commit
						471596fc1a
					
				
							
								
								
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							|  | @ -254,6 +254,7 @@ answer newbie questions, and generally made Django that much better: | |||
|     Gasper Koren | ||||
|     Martin Kosír <martin@martinkosir.net> | ||||
|     Arthur Koziel <http://arthurkoziel.com> | ||||
|     Honza Kral <honza.kral@gmail.com> | ||||
|     Meir Kriheli <http://mksoft.co.il/> | ||||
|     Bruce Kroeze <http://coderseye.com/> | ||||
|     krzysiek.pawlik@silvermedia.pl | ||||
|  |  | |||
|  | @ -578,12 +578,12 @@ class ModelAdmin(BaseModelAdmin): | |||
|         """ | ||||
|         messages.info(request, message) | ||||
| 
 | ||||
|     def save_form(self, request, form, change): | ||||
|     def save_form(self, request, form, change, commit=False): | ||||
|         """ | ||||
|         Given a ModelForm return an unsaved instance. ``change`` is True if | ||||
|         the object is being changed, and False if it's being added. | ||||
|         """ | ||||
|         return form.save(commit=False) | ||||
|         return form.save(commit=commit) | ||||
| 
 | ||||
|     def save_model(self, request, obj, form, change): | ||||
|         """ | ||||
|  | @ -757,8 +757,12 @@ class ModelAdmin(BaseModelAdmin): | |||
|         if request.method == 'POST': | ||||
|             form = ModelForm(request.POST, request.FILES) | ||||
|             if form.is_valid(): | ||||
|                 # Save the object, even if inline formsets haven't been | ||||
|                 # validated yet. We need to pass the valid model to the | ||||
|                 # formsets for validation. If the formsets do not validate, we | ||||
|                 # will delete the object. | ||||
|                 new_object = self.save_form(request, form, change=False, commit=True) | ||||
|                 form_validated = True | ||||
|                 new_object = self.save_form(request, form, change=False) | ||||
|             else: | ||||
|                 form_validated = False | ||||
|                 new_object = self.model() | ||||
|  | @ -774,13 +778,15 @@ class ModelAdmin(BaseModelAdmin): | |||
|                                   prefix=prefix, queryset=inline.queryset(request)) | ||||
|                 formsets.append(formset) | ||||
|             if all_valid(formsets) and form_validated: | ||||
|                 self.save_model(request, new_object, form, change=False) | ||||
|                 form.save_m2m() | ||||
|                 for formset in formsets: | ||||
|                     self.save_formset(request, form, formset, change=False) | ||||
| 
 | ||||
|                 self.log_addition(request, new_object) | ||||
|                 return self.response_add(request, new_object) | ||||
|             elif form_validated: | ||||
|                 # The form was valid, but formsets were not, so delete the | ||||
|                 # object we saved above. | ||||
|                 new_object.delete() | ||||
|         else: | ||||
|             # Prepare the dict of initial data from the request. | ||||
|             # We have to special-case M2Ms as a list of comma-separated PKs. | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| from django.contrib.auth.models import User | ||||
| from django.contrib.auth.models import User, UNUSABLE_PASSWORD | ||||
| from django.contrib.auth import authenticate | ||||
| from django.contrib.auth.tokens import default_token_generator | ||||
| from django.contrib.sites.models import Site | ||||
|  | @ -21,6 +21,12 @@ class UserCreationForm(forms.ModelForm): | |||
|         model = User | ||||
|         fields = ("username",) | ||||
| 
 | ||||
|     def clean(self): | ||||
|         # Fill the password field so model validation won't complain about it | ||||
|         # being blank. We'll set it with the real value below. | ||||
|         self.instance.password = UNUSABLE_PASSWORD | ||||
|         super(UserCreationForm, self).clean() | ||||
| 
 | ||||
|     def clean_username(self): | ||||
|         username = self.cleaned_data["username"] | ||||
|         try: | ||||
|  | @ -34,15 +40,9 @@ class UserCreationForm(forms.ModelForm): | |||
|         password2 = self.cleaned_data["password2"] | ||||
|         if password1 != password2: | ||||
|             raise forms.ValidationError(_("The two password fields didn't match.")) | ||||
|         self.instance.set_password(password1) | ||||
|         return password2 | ||||
| 
 | ||||
|     def save(self, commit=True): | ||||
|         user = super(UserCreationForm, self).save(commit=False) | ||||
|         user.set_password(self.cleaned_data["password1"]) | ||||
|         if commit: | ||||
|             user.save() | ||||
|         return user | ||||
| 
 | ||||
| class UserChangeForm(forms.ModelForm): | ||||
|     username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$', | ||||
|         help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."), | ||||
|  |  | |||
|  | @ -297,7 +297,11 @@ class BaseGenericInlineFormSet(BaseModelFormSet): | |||
|         # Avoid a circular import. | ||||
|         from django.contrib.contenttypes.models import ContentType | ||||
|         opts = self.model._meta | ||||
|         self.instance = instance | ||||
|         if instance is None: | ||||
|             self.instance = self.model() | ||||
|         else: | ||||
|             self.instance = instance | ||||
|         self.save_as_new = save_as_new | ||||
|         self.rel_name = '-'.join(( | ||||
|             opts.app_label, opts.object_name.lower(), | ||||
|             self.ct_field.name, self.ct_fk_field.name, | ||||
|  | @ -324,15 +328,19 @@ class BaseGenericInlineFormSet(BaseModelFormSet): | |||
|         )) | ||||
|     get_default_prefix = classmethod(get_default_prefix) | ||||
| 
 | ||||
|     def save_new(self, form, commit=True): | ||||
|     def _construct_form(self, i, **kwargs): | ||||
|         # Avoid a circular import. | ||||
|         from django.contrib.contenttypes.models import ContentType | ||||
|         kwargs = { | ||||
|             self.ct_field.get_attname(): ContentType.objects.get_for_model(self.instance).pk, | ||||
|             self.ct_fk_field.get_attname(): self.instance.pk, | ||||
|         } | ||||
|         new_obj = self.model(**kwargs) | ||||
|         return save_instance(form, new_obj, commit=commit) | ||||
|         form = super(BaseGenericInlineFormSet, self)._construct_form(i, **kwargs) | ||||
|         if self.save_as_new: | ||||
|             # Remove the key from the form's data, we are only creating new instances. | ||||
|             form.data[form.add_prefix(self.ct_fk_field.name)] = None | ||||
|             form.data[form.add_prefix(self.ct_field.name)] = None | ||||
| 
 | ||||
|         # Set the GenericForeignKey value here so that the form can do its validation. | ||||
|         setattr(form.instance, self.ct_fk_field.attname, self.instance.pk) | ||||
|         setattr(form.instance, self.ct_field.attname, ContentType.objects.get_for_model(self.instance).pk) | ||||
|         return form | ||||
| 
 | ||||
| def generic_inlineformset_factory(model, form=ModelForm, | ||||
|                                   formset=BaseGenericInlineFormSet, | ||||
|  |  | |||
|  | @ -4,7 +4,8 @@ AR-specific Form helpers. | |||
| """ | ||||
| 
 | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import RegexField, CharField, Select, EMPTY_VALUES | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms.fields import RegexField, CharField, Select | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,9 +2,10 @@ | |||
| Australian-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.util import smart_unicode | ||||
| from django.forms.fields import Field, RegexField, Select | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| import re | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,8 +3,9 @@ | |||
| BR-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, CharField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField, CharField, Select | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| import re | ||||
|  |  | |||
|  | @ -2,9 +2,10 @@ | |||
| Canada-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.util import smart_unicode | ||||
| from django.forms.fields import Field, RegexField, Select | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| import re | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| Swiss-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField, Select | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| import re | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| Chile specific form helpers. | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import RegexField, Select | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.utils.encoding import smart_unicode | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| Czech-specific form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Select, RegexField, Field, EMPTY_VALUES | ||||
| from django.forms.fields import Select, RegexField, Field | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| import re | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| DE-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField, Select | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| import re | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,8 +3,9 @@ | |||
| Spanish-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import RegexField, Select | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| import re | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,8 +3,9 @@ FI-specific Form helpers | |||
| """ | ||||
| 
 | ||||
| import re | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField, Select | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
| class FIZipCodeField(RegexField): | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| FR-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField, Select | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| import re | ||||
|  |  | |||
|  | @ -5,8 +5,9 @@ ID-specific Form helpers | |||
| import re | ||||
| import time | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, Select | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.utils.encoding import smart_unicode | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| India-specific Form helpers. | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField, Select | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.utils.translation import gettext | ||||
| import re | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| Iceland specific form helpers. | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import RegexField, EMPTY_VALUES | ||||
| from django.forms.fields import RegexField | ||||
| from django.forms.widgets import Select | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.utils.encoding import smart_unicode | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| IT-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField, Select | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.contrib.localflavor.it.util import ssn_check_digit, vat_number_check_digit | ||||
|  |  | |||
|  | @ -3,8 +3,10 @@ Kuwait-specific Form helpers | |||
| """ | ||||
| import re | ||||
| from datetime import date | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField | ||||
| from django.utils.translation import gettext as _ | ||||
| 
 | ||||
| id_re = re.compile(r'^(?P<initial>\d{1})(?P<yy>\d\d)(?P<mm>\d\d)(?P<dd>\d\d)(?P<mid>\d{4})(?P<checksum>\d{1})') | ||||
|  |  | |||
|  | @ -4,8 +4,9 @@ NL-specific Form helpers | |||
| 
 | ||||
| import re | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, Select | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.utils.encoding import smart_unicode | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,8 +3,9 @@ Norwegian-specific Form helpers | |||
| """ | ||||
| 
 | ||||
| import re, datetime | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField, Select | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
| class NOZipCodeField(RegexField): | ||||
|  |  | |||
|  | @ -3,8 +3,9 @@ | |||
| PE-specific Form helpers. | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import RegexField, CharField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import RegexField, CharField, Select | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
| class PERegionSelect(Select): | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| PT-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField, Select | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| import re | ||||
|  |  | |||
|  | @ -5,8 +5,8 @@ Romanian specific form helpers. | |||
| 
 | ||||
| import re | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError, Field, RegexField, Select | ||||
| from django.forms.fields import EMPTY_VALUES | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| 
 | ||||
| class ROCIFField(RegexField): | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ Swedish specific Form helpers | |||
| import re | ||||
| from django import forms | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.forms.fields import EMPTY_VALUES | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.contrib.localflavor.se.utils import (id_number_checksum, | ||||
|     validate_id_birthday, format_personal_id_number, valid_organisation, | ||||
|     format_organisation_number) | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| USA-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES, CharField | ||||
| 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 | ||||
|  |  | |||
|  | @ -4,7 +4,8 @@ UY-specific form helpers. | |||
| """ | ||||
| import re | ||||
| 
 | ||||
| from django.forms.fields import Select, RegexField, EMPTY_VALUES | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms.fields import Select, RegexField | ||||
| from django.forms import ValidationError | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.contrib.localflavor.uy.util import get_validation_digit | ||||
|  |  | |||
|  | @ -2,8 +2,9 @@ | |||
| South Africa-specific Form helpers | ||||
| """ | ||||
| 
 | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from django.forms import ValidationError | ||||
| from django.forms.fields import Field, RegexField, EMPTY_VALUES | ||||
| from django.forms.fields import Field, RegexField | ||||
| from django.utils.checksums import luhn | ||||
| from django.utils.translation import gettext as _ | ||||
| import re | ||||
|  |  | |||
|  | @ -32,6 +32,42 @@ class FieldError(Exception): | |||
|     """Some kind of problem with a model field.""" | ||||
|     pass | ||||
| 
 | ||||
| class ValidationError(Exception): | ||||
| NON_FIELD_ERRORS = '__all__' | ||||
| class BaseValidationError(Exception): | ||||
|     """An error while validating data.""" | ||||
|     def __init__(self, message, code=None, params=None): | ||||
|         import operator | ||||
|         from django.utils.encoding import force_unicode | ||||
|         """ | ||||
|         ValidationError can be passed any object that can be printed (usually | ||||
|         a string), a list of objects or a dictionary. | ||||
|         """ | ||||
|         if isinstance(message, dict): | ||||
|             self.message_dict = message | ||||
|             # Reduce each list of messages into a single list. | ||||
|             message = reduce(operator.add, message.values()) | ||||
| 
 | ||||
|         if isinstance(message, list): | ||||
|             self.messages = [force_unicode(msg) for msg in message] | ||||
|         else: | ||||
|             self.code = code | ||||
|             self.params = params | ||||
|             message = force_unicode(message) | ||||
|             self.messages = [message] | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         # This is needed because, without a __str__(), printing an exception | ||||
|         # instance would result in this: | ||||
|         # AttributeError: ValidationError instance has no attribute 'args' | ||||
|         # See http://www.python.org/doc/current/tut/node10.html#handling | ||||
|         if hasattr(self, 'message_dict'): | ||||
|             return repr(self.message_dict) | ||||
|         return repr(self.messages) | ||||
| 
 | ||||
| class ValidationError(BaseValidationError): | ||||
|     pass | ||||
| 
 | ||||
| class UnresolvableValidationError(BaseValidationError): | ||||
|     """Validation error that cannot be resolved by the user.""" | ||||
|     pass | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										137
									
								
								django/core/validators.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								django/core/validators.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,137 @@ | |||
| import re | ||||
| 
 | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.utils.encoding import smart_unicode | ||||
| 
 | ||||
| # These values, if given to validate(), will trigger the self.required check. | ||||
| EMPTY_VALUES = (None, '', [], (), {}) | ||||
| 
 | ||||
| try: | ||||
|     from django.conf import settings | ||||
|     URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT | ||||
| except ImportError: | ||||
|     # It's OK if Django settings aren't configured. | ||||
|     URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' | ||||
| 
 | ||||
| class RegexValidator(object): | ||||
|     regex = '' | ||||
|     message = _(u'Enter a valid value.') | ||||
|     code = 'invalid' | ||||
| 
 | ||||
|     def __init__(self, regex=None, message=None, code=None): | ||||
|         if regex is not None: | ||||
|             self.regex = regex | ||||
|         if message is not None: | ||||
|             self.message = message | ||||
|         if code is not None: | ||||
|             self.code = code | ||||
| 
 | ||||
|         if isinstance(self.regex, basestring): | ||||
|             self.regex = re.compile(regex) | ||||
| 
 | ||||
|     def __call__(self, value): | ||||
|         """ | ||||
|         Validates that the input matches the regular expression. | ||||
|         """ | ||||
|         if not self.regex.search(smart_unicode(value)): | ||||
|             raise ValidationError(self.message, code=self.code) | ||||
| 
 | ||||
| class URLValidator(RegexValidator): | ||||
|     regex = re.compile( | ||||
|         r'^https?://' # http:// or https:// | ||||
|         r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain... | ||||
|         r'localhost|' #localhost... | ||||
|         r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip | ||||
|         r'(?::\d+)?' # optional port | ||||
|         r'(?:/?|[/?]\S+)$', re.IGNORECASE) | ||||
| 
 | ||||
|     def __init__(self, verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT): | ||||
|         super(URLValidator, self).__init__() | ||||
|         self.verify_exists = verify_exists | ||||
|         self.user_agent = validator_user_agent | ||||
| 
 | ||||
|     def __call__(self, value): | ||||
|         super(URLValidator, self).__call__(value) | ||||
|         if self.verify_exists: | ||||
|             import urllib2 | ||||
|             headers = { | ||||
|                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", | ||||
|                 "Accept-Language": "en-us,en;q=0.5", | ||||
|                 "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", | ||||
|                 "Connection": "close", | ||||
|                 "User-Agent": self.user_agent, | ||||
|             } | ||||
|             try: | ||||
|                 req = urllib2.Request(value, None, headers) | ||||
|                 u = urllib2.urlopen(req) | ||||
|             except ValueError: | ||||
|                 raise ValidationError(_(u'Enter a valid URL.'), code='invalid') | ||||
|             except: # urllib2.URLError, httplib.InvalidURL, etc. | ||||
|                 raise ValidationError(_(u'This URL appears to be a broken link.'), code='invalid_link') | ||||
| 
 | ||||
| 
 | ||||
| def validate_integer(value): | ||||
|     try: | ||||
|         int(value) | ||||
|     except (ValueError, TypeError), e: | ||||
|         raise ValidationError('') | ||||
| 
 | ||||
| 
 | ||||
| email_re = re.compile( | ||||
|     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom | ||||
|     r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string | ||||
|     r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE)  # domain | ||||
| validate_email = RegexValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid') | ||||
| 
 | ||||
| slug_re = re.compile(r'^[-\w]+$') | ||||
| validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid') | ||||
| 
 | ||||
| ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') | ||||
| validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid') | ||||
| 
 | ||||
| comma_separated_int_list_re = re.compile('^[\d,]+$') | ||||
| validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid') | ||||
| 
 | ||||
| 
 | ||||
| class BaseValidator(object): | ||||
|     compare = lambda self, a, b: a is not b | ||||
|     clean   = lambda self, x: x | ||||
|     message = _(u'Ensure this value is %(limit_value)s (it is %(show_value)s).') | ||||
|     code = 'limit_value' | ||||
| 
 | ||||
|     def __init__(self, limit_value): | ||||
|         self.limit_value = limit_value | ||||
| 
 | ||||
|     def __call__(self, value): | ||||
|         cleaned = self.clean(value) | ||||
|         params = {'limit_value': self.limit_value, 'show_value': cleaned} | ||||
|         if self.compare(cleaned, self.limit_value): | ||||
|             raise ValidationError( | ||||
|                 self.message % params, | ||||
|                 code=self.code, | ||||
|                 params=params, | ||||
|             ) | ||||
| 
 | ||||
| class MaxValueValidator(BaseValidator): | ||||
|     compare = lambda self, a, b: a > b | ||||
|     message = _(u'Ensure this value is less than or equal to %(limit_value)s.') | ||||
|     code = 'max_value' | ||||
| 
 | ||||
| class MinValueValidator(BaseValidator): | ||||
|     compare = lambda self, a, b: a < b | ||||
|     message = _(u'Ensure this value is greater than or equal to %(limit_value)s.') | ||||
|     code = 'min_value' | ||||
| 
 | ||||
| class MinLengthValidator(BaseValidator): | ||||
|     compare = lambda self, a, b: a < b | ||||
|     clean   = lambda self, x: len(x) | ||||
|     message = _(u'Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).') | ||||
|     code = 'min_length' | ||||
| 
 | ||||
| class MaxLengthValidator(BaseValidator): | ||||
|     compare = lambda self, a, b: a > b | ||||
|     clean   = lambda self, x: len(x) | ||||
|     message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).') | ||||
|     code = 'max_length' | ||||
| 
 | ||||
|  | @ -3,7 +3,8 @@ import sys | |||
| import os | ||||
| from itertools import izip | ||||
| import django.db.models.manager     # Imported to register signal handler. | ||||
| from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError | ||||
| from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS | ||||
| from django.core import validators | ||||
| from django.db.models.fields import AutoField, FieldDoesNotExist | ||||
| from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField | ||||
| from django.db.models.query import delete_objects, Q | ||||
|  | @ -12,9 +13,11 @@ from django.db.models.options import Options | |||
| from django.db import connections, transaction, DatabaseError, DEFAULT_DB_ALIAS | ||||
| from django.db.models import signals | ||||
| from django.db.models.loading import register_models, get_model | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| import django.utils.copycompat as copy | ||||
| from django.utils.functional import curry | ||||
| from django.utils.encoding import smart_str, force_unicode, smart_unicode | ||||
| from django.utils.text import get_text_list, capfirst | ||||
| from django.conf import settings | ||||
| 
 | ||||
| class ModelBase(type): | ||||
|  | @ -639,6 +642,180 @@ class Model(object): | |||
|     def prepare_database_save(self, unused): | ||||
|         return self.pk | ||||
| 
 | ||||
|     def validate(self): | ||||
|         """ | ||||
|         Hook for doing any extra model-wide validation after clean() has been | ||||
|         called on every field. Any ValidationError raised by this method will | ||||
|         not be associated with a particular field; it will have a special-case | ||||
|         association with the field defined by NON_FIELD_ERRORS. | ||||
|         """ | ||||
|         self.validate_unique() | ||||
| 
 | ||||
|     def validate_unique(self): | ||||
|         unique_checks, date_checks = self._get_unique_checks() | ||||
| 
 | ||||
|         errors = self._perform_unique_checks(unique_checks) | ||||
|         date_errors = self._perform_date_checks(date_checks) | ||||
| 
 | ||||
|         for k, v in date_errors.items(): | ||||
|              errors.setdefault(k, []).extend(v) | ||||
| 
 | ||||
|         if errors: | ||||
|             raise ValidationError(errors) | ||||
| 
 | ||||
|     def _get_unique_checks(self): | ||||
|         from django.db.models.fields import FieldDoesNotExist, Field as ModelField | ||||
| 
 | ||||
|         unique_checks = list(self._meta.unique_together) | ||||
|         # these are checks for the unique_for_<date/year/month> | ||||
|         date_checks = [] | ||||
| 
 | ||||
|         # Gather a list of checks for fields declared as unique and add them to | ||||
|         # the list of checks. Again, skip empty fields and any that did not validate. | ||||
|         for f in self._meta.fields: | ||||
|             name = f.name | ||||
|             if f.unique: | ||||
|                 unique_checks.append((name,)) | ||||
|             if f.unique_for_date: | ||||
|                 date_checks.append(('date', name, f.unique_for_date)) | ||||
|             if f.unique_for_year: | ||||
|                 date_checks.append(('year', name, f.unique_for_year)) | ||||
|             if f.unique_for_month: | ||||
|                 date_checks.append(('month', name, f.unique_for_month)) | ||||
|         return unique_checks, date_checks | ||||
| 
 | ||||
| 
 | ||||
|     def _perform_unique_checks(self, unique_checks): | ||||
|         errors = {} | ||||
| 
 | ||||
|         for unique_check in unique_checks: | ||||
|             # Try to look up an existing object with the same values as this | ||||
|             # object's values for all the unique field. | ||||
| 
 | ||||
|             lookup_kwargs = {} | ||||
|             for field_name in unique_check: | ||||
|                 f = self._meta.get_field(field_name) | ||||
|                 lookup_value = getattr(self, f.attname) | ||||
|                 if f.null and lookup_value is None: | ||||
|                     # no value, skip the lookup | ||||
|                     continue | ||||
|                 if f.primary_key and not getattr(self, '_adding', False): | ||||
|                     # no need to check for unique primary key when editting  | ||||
|                     continue | ||||
|                 lookup_kwargs[str(field_name)] = lookup_value | ||||
| 
 | ||||
|             # some fields were skipped, no reason to do the check | ||||
|             if len(unique_check) != len(lookup_kwargs.keys()): | ||||
|                 continue | ||||
| 
 | ||||
|             qs = self.__class__._default_manager.filter(**lookup_kwargs) | ||||
| 
 | ||||
|             # Exclude the current object from the query if we are editing an | ||||
|             # instance (as opposed to creating a new one) | ||||
|             if not getattr(self, '_adding', False) and self.pk is not None: | ||||
|                 qs = qs.exclude(pk=self.pk) | ||||
| 
 | ||||
|             # This cute trick with extra/values is the most efficient way to | ||||
|             # tell if a particular query returns any results. | ||||
|             if qs.extra(select={'a': 1}).values('a').order_by(): | ||||
|                 if len(unique_check) == 1: | ||||
|                     key = unique_check[0] | ||||
|                 else: | ||||
|                     key = NON_FIELD_ERRORS | ||||
|                 errors.setdefault(key, []).append(self.unique_error_message(unique_check)) | ||||
| 
 | ||||
|         return errors | ||||
| 
 | ||||
|     def _perform_date_checks(self, date_checks): | ||||
|         errors = {} | ||||
|         for lookup_type, field, unique_for in date_checks: | ||||
|             lookup_kwargs = {} | ||||
|             # there's a ticket to add a date lookup, we can remove this special | ||||
|             # case if that makes it's way in | ||||
|             date = getattr(self, unique_for) | ||||
|             if lookup_type == 'date': | ||||
|                 lookup_kwargs['%s__day' % unique_for] = date.day | ||||
|                 lookup_kwargs['%s__month' % unique_for] = date.month | ||||
|                 lookup_kwargs['%s__year' % unique_for] = date.year | ||||
|             else: | ||||
|                 lookup_kwargs['%s__%s' % (unique_for, lookup_type)] = getattr(date, lookup_type) | ||||
|             lookup_kwargs[field] = getattr(self, field) | ||||
| 
 | ||||
|             qs = self.__class__._default_manager.filter(**lookup_kwargs) | ||||
|             # Exclude the current object from the query if we are editing an | ||||
|             # instance (as opposed to creating a new one) | ||||
|             if not getattr(self, '_adding', False) and self.pk is not None: | ||||
|                 qs = qs.exclude(pk=self.pk) | ||||
| 
 | ||||
|             # This cute trick with extra/values is the most efficient way to | ||||
|             # tell if a particular query returns any results. | ||||
|             if qs.extra(select={'a': 1}).values('a').order_by(): | ||||
|                 errors.setdefault(field, []).append( | ||||
|                     self.date_error_message(lookup_type, field, unique_for) | ||||
|                 ) | ||||
|         return errors | ||||
| 
 | ||||
|     def date_error_message(self, lookup_type, field, unique_for): | ||||
|         opts = self._meta | ||||
|         return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % { | ||||
|             'field_name': unicode(capfirst(opts.get_field(field).verbose_name)), | ||||
|             'date_field': unicode(capfirst(opts.get_field(unique_for).verbose_name)), | ||||
|             'lookup': lookup_type, | ||||
|         } | ||||
| 
 | ||||
|     def unique_error_message(self, unique_check): | ||||
|         opts = self._meta | ||||
|         model_name = capfirst(opts.verbose_name) | ||||
| 
 | ||||
|         # A unique field | ||||
|         if len(unique_check) == 1: | ||||
|             field_name = unique_check[0] | ||||
|             field_label = capfirst(opts.get_field(field_name).verbose_name) | ||||
|             # Insert the error into the error dict, very sneaky | ||||
|             return _(u"%(model_name)s with this %(field_label)s already exists.") %  { | ||||
|                 'model_name': unicode(model_name), | ||||
|                 'field_label': unicode(field_label) | ||||
|             } | ||||
|         # unique_together | ||||
|         else: | ||||
|             field_labels = map(lambda f: capfirst(opts.get_field(f).verbose_name), unique_check) | ||||
|             field_labels = get_text_list(field_labels, _('and')) | ||||
|             return _(u"%(model_name)s with this %(field_label)s already exists.") %  { | ||||
|                 'model_name': unicode(model_name), | ||||
|                 'field_label': unicode(field_labels) | ||||
|             } | ||||
| 
 | ||||
|     def full_validate(self, exclude=[]): | ||||
|         """ | ||||
|         Cleans all fields and raises ValidationError containing message_dict | ||||
|         of all validation errors if any occur. | ||||
|         """ | ||||
|         errors = {} | ||||
|         for f in self._meta.fields: | ||||
|             if f.name in exclude: | ||||
|                 continue | ||||
|             try: | ||||
|                 setattr(self, f.attname, f.clean(getattr(self, f.attname), self)) | ||||
|             except ValidationError, e: | ||||
|                 errors[f.name] = e.messages | ||||
| 
 | ||||
|         # Form.clean() is run even if other validation fails, so do the | ||||
|         # same with Model.validate() for consistency. | ||||
|         try: | ||||
|             self.validate() | ||||
|         except ValidationError, e: | ||||
|             if hasattr(e, 'message_dict'): | ||||
|                 if errors: | ||||
|                     for k, v in e.message_dict.items(): | ||||
|                         errors.set_default(k, []).extend(v) | ||||
|                 else: | ||||
|                     errors = e.message_dict | ||||
|             else: | ||||
|                 errors[NON_FIELD_ERRORS] = e.messages | ||||
| 
 | ||||
|         if errors: | ||||
|             raise ValidationError(errors) | ||||
| 
 | ||||
| 
 | ||||
| ############################################ | ||||
| # HELPER FUNCTIONS (CURRIED MODEL METHODS) # | ||||
|  |  | |||
|  | @ -13,12 +13,12 @@ from django.db.models.query_utils import QueryWrapper | |||
| from django.dispatch import dispatcher | ||||
| from django.conf import settings | ||||
| from django import forms | ||||
| from django.core import exceptions | ||||
| from django.core import exceptions, validators | ||||
| from django.utils.datastructures import DictWrapper | ||||
| from django.utils.functional import curry | ||||
| from django.utils.itercompat import tee | ||||
| from django.utils.text import capfirst | ||||
| from django.utils.translation import ugettext_lazy, ugettext as _ | ||||
| from django.utils.translation import ugettext_lazy as _, ugettext | ||||
| from django.utils.encoding import smart_unicode, force_unicode, smart_str | ||||
| from django.utils import datetime_safe | ||||
| 
 | ||||
|  | @ -60,6 +60,12 @@ class Field(object): | |||
|     # creates, creation_counter is used for all user-specified fields. | ||||
|     creation_counter = 0 | ||||
|     auto_creation_counter = -1 | ||||
|     default_validators = [] # Default set of validators | ||||
|     default_error_messages = { | ||||
|         'invalid_choice': _(u'Value %r is not a valid choice.'), | ||||
|         'null': _(u'This field cannot be null.'), | ||||
|         'blank': _(u'This field cannot be blank.'), | ||||
|     } | ||||
| 
 | ||||
|     # Generic field type description, usually overriden by subclasses | ||||
|     def _description(self): | ||||
|  | @ -73,7 +79,8 @@ class Field(object): | |||
|             db_index=False, rel=None, default=NOT_PROVIDED, editable=True, | ||||
|             serialize=True, unique_for_date=None, unique_for_month=None, | ||||
|             unique_for_year=None, choices=None, help_text='', db_column=None, | ||||
|             db_tablespace=None, auto_created=False): | ||||
|             db_tablespace=None, auto_created=False, validators=[], | ||||
|             error_messages=None): | ||||
|         self.name = name | ||||
|         self.verbose_name = verbose_name | ||||
|         self.primary_key = primary_key | ||||
|  | @ -106,6 +113,42 @@ class Field(object): | |||
|             self.creation_counter = Field.creation_counter | ||||
|             Field.creation_counter += 1 | ||||
| 
 | ||||
|         self.validators = self.default_validators + validators | ||||
| 
 | ||||
|         messages = {} | ||||
|         for c in reversed(self.__class__.__mro__): | ||||
|             messages.update(getattr(c, 'default_error_messages', {})) | ||||
|         messages.update(error_messages or {}) | ||||
|         self.error_messages = messages | ||||
| 
 | ||||
|     def __getstate__(self): | ||||
|         """ | ||||
|         Pickling support. | ||||
|         """ | ||||
|         from django.utils.functional import Promise | ||||
|         obj_dict = self.__dict__.copy() | ||||
|         items = [] | ||||
|         translated_keys = [] | ||||
|         for k, v in self.error_messages.items(): | ||||
|             if isinstance(v, Promise): | ||||
|                 args = getattr(v, '_proxy____args', None) | ||||
|                 if args: | ||||
|                     translated_keys.append(k) | ||||
|                     v = args[0] | ||||
|             items.append((k,v)) | ||||
|         obj_dict['_translated_keys'] = translated_keys | ||||
|         obj_dict['error_messages'] = dict(items) | ||||
|         return obj_dict | ||||
| 
 | ||||
|     def __setstate__(self, obj_dict): | ||||
|         """ | ||||
|         Unpickling support. | ||||
|         """ | ||||
|         translated_keys = obj_dict.pop('_translated_keys') | ||||
|         self.__dict__.update(obj_dict) | ||||
|         for k in translated_keys: | ||||
|             self.error_messages[k] = _(self.error_messages[k]) | ||||
| 
 | ||||
|     def __cmp__(self, other): | ||||
|         # This is needed because bisect does not take a comparison function. | ||||
|         return cmp(self.creation_counter, other.creation_counter) | ||||
|  | @ -127,6 +170,54 @@ class Field(object): | |||
|         """ | ||||
|         return value | ||||
| 
 | ||||
|     def run_validators(self, value): | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return | ||||
| 
 | ||||
|         errors = [] | ||||
|         for v in self.validators: | ||||
|             try: | ||||
|                 v(value) | ||||
|             except exceptions.ValidationError, e: | ||||
|                 if hasattr(e, 'code') and e.code in self.error_messages: | ||||
|                     message = self.error_messages[e.code] | ||||
|                     if e.params: | ||||
|                         message = message % e.params | ||||
|                     errors.append(message) | ||||
|                 else: | ||||
|                     errors.extend(e.messages) | ||||
|         if errors: | ||||
|             raise exceptions.ValidationError(errors) | ||||
| 
 | ||||
|     def validate(self, value, model_instance): | ||||
|         """ | ||||
|         Validates value and throws ValidationError. Subclasses should override | ||||
|         this to provide validation logic. | ||||
|         """ | ||||
|         if not self.editable: | ||||
|             # Skip validation for non-editable fields. | ||||
|             return | ||||
|         if self._choices and value: | ||||
|             if not value in dict(self.choices): | ||||
|                 raise exceptions.ValidationError(self.error_messages['invalid_choice'] % value) | ||||
| 
 | ||||
|         if value is None and not self.null: | ||||
|             raise exceptions.ValidationError(self.error_messages['null']) | ||||
| 
 | ||||
|         if not self.blank and value in validators.EMPTY_VALUES: | ||||
|             raise exceptions.ValidationError(self.error_messages['blank']) | ||||
| 
 | ||||
|     def clean(self, value, model_instance): | ||||
|         """ | ||||
|         Convert the value's type and run validation. Validation errors from to_python | ||||
|         and validate are propagated. The correct value is returned if no error is | ||||
|         raised. | ||||
|         """ | ||||
|         value = self.to_python(value) | ||||
|         self.validate(value, model_instance) | ||||
|         self.run_validators(value) | ||||
|         return value | ||||
| 
 | ||||
|     def db_type(self, connection): | ||||
|         """ | ||||
|         Returns the database column data type for this field, for the provided | ||||
|  | @ -377,9 +468,12 @@ class Field(object): | |||
|         return getattr(obj, self.attname) | ||||
| 
 | ||||
| class AutoField(Field): | ||||
|     description = ugettext_lazy("Integer") | ||||
|     description = _("Integer") | ||||
| 
 | ||||
|     empty_strings_allowed = False | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'This value must be an integer.'), | ||||
|     } | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         assert kwargs.get('primary_key', False) is True, "%ss must have primary_key=True." % self.__class__.__name__ | ||||
|         kwargs['blank'] = True | ||||
|  | @ -391,8 +485,10 @@ class AutoField(Field): | |||
|         try: | ||||
|             return int(value) | ||||
|         except (TypeError, ValueError): | ||||
|             raise exceptions.ValidationError( | ||||
|                 _("This value must be an integer.")) | ||||
|             raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
| 
 | ||||
|     def validate(self, value, model_instance): | ||||
|         pass | ||||
| 
 | ||||
|     def get_prep_value(self, value): | ||||
|         if value is None: | ||||
|  | @ -410,7 +506,10 @@ class AutoField(Field): | |||
| 
 | ||||
| class BooleanField(Field): | ||||
|     empty_strings_allowed = False | ||||
|     description = ugettext_lazy("Boolean (Either True or False)") | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'This value must be either True or False.'), | ||||
|     } | ||||
|     description = _("Boolean (Either True or False)") | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         kwargs['blank'] = True | ||||
|         if 'default' not in kwargs and not kwargs.get('null'): | ||||
|  | @ -424,8 +523,7 @@ class BooleanField(Field): | |||
|         if value in (True, False): return value | ||||
|         if value in ('t', 'True', '1'): return True | ||||
|         if value in ('f', 'False', '0'): return False | ||||
|         raise exceptions.ValidationError( | ||||
|             _("This value must be either True or False.")) | ||||
|         raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
| 
 | ||||
|     def get_prep_lookup(self, lookup_type, value): | ||||
|         # Special-case handling for filters coming from a web request (e.g. the | ||||
|  | @ -453,36 +551,35 @@ class BooleanField(Field): | |||
|         return super(BooleanField, self).formfield(**defaults) | ||||
| 
 | ||||
| class CharField(Field): | ||||
|     description = ugettext_lazy("String (up to %(max_length)s)") | ||||
|     description = _("String (up to %(max_length)s)") | ||||
| 
 | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(CharField, self).__init__(*args, **kwargs) | ||||
|         self.validators.append(validators.MaxLengthValidator(self.max_length)) | ||||
| 
 | ||||
|     def get_internal_type(self): | ||||
|         return "CharField" | ||||
| 
 | ||||
|     def to_python(self, value): | ||||
|         if isinstance(value, basestring): | ||||
|         if isinstance(value, basestring) or value is None: | ||||
|             return value | ||||
|         if value is None: | ||||
|             if self.null: | ||||
|                 return value | ||||
|             else: | ||||
|                 raise exceptions.ValidationError( | ||||
|                     ugettext_lazy("This field cannot be null.")) | ||||
|         return smart_unicode(value) | ||||
| 
 | ||||
|     def formfield(self, **kwargs): | ||||
|         # Passing max_length to forms.CharField means that the value's length | ||||
|         # will be validated twice. This is considered acceptable since we want | ||||
|         # the value in the form field (to pass into widget for example). | ||||
|         defaults = {'max_length': self.max_length} | ||||
|         defaults.update(kwargs) | ||||
|         return super(CharField, self).formfield(**defaults) | ||||
| 
 | ||||
| # TODO: Maybe move this into contrib, because it's specialized. | ||||
| class CommaSeparatedIntegerField(CharField): | ||||
|     description = ugettext_lazy("Comma-separated integers") | ||||
|     default_validators = [validators.validate_comma_separated_integer_list] | ||||
|     description = _("Comma-separated integers") | ||||
| 
 | ||||
|     def formfield(self, **kwargs): | ||||
|         defaults = { | ||||
|             'form_class': forms.RegexField, | ||||
|             'regex': '^[\d,]+$', | ||||
|             'max_length': self.max_length, | ||||
|             'error_messages': { | ||||
|                 'invalid': _(u'Enter only digits separated by commas.'), | ||||
|             } | ||||
|  | @ -493,9 +590,13 @@ class CommaSeparatedIntegerField(CharField): | |||
| ansi_date_re = re.compile(r'^\d{4}-\d{1,2}-\d{1,2}$') | ||||
| 
 | ||||
| class DateField(Field): | ||||
|     description = ugettext_lazy("Date (without time)") | ||||
|     description = _("Date (without time)") | ||||
| 
 | ||||
|     empty_strings_allowed = False | ||||
|     default_error_messages = { | ||||
|         'invalid': _('Enter a valid date in YYYY-MM-DD format.'), | ||||
|         'invalid_date': _('Invalid date: %s'), | ||||
|     } | ||||
|     def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): | ||||
|         self.auto_now, self.auto_now_add = auto_now, auto_now_add | ||||
|         #HACKs : auto_now_add/auto_now should be done as a default or a pre_save. | ||||
|  | @ -516,8 +617,7 @@ class DateField(Field): | |||
|             return value | ||||
| 
 | ||||
|         if not ansi_date_re.search(value): | ||||
|             raise exceptions.ValidationError( | ||||
|                 _('Enter a valid date in YYYY-MM-DD format.')) | ||||
|             raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
|         # Now that we have the date string in YYYY-MM-DD format, check to make | ||||
|         # sure it's a valid date. | ||||
|         # We could use time.strptime here and catch errors, but datetime.date | ||||
|  | @ -526,7 +626,7 @@ class DateField(Field): | |||
|         try: | ||||
|             return datetime.date(year, month, day) | ||||
|         except ValueError, e: | ||||
|             msg = _('Invalid date: %s') % _(str(e)) | ||||
|             msg = self.error_messages['invalid_date'] % _(str(e)) | ||||
|             raise exceptions.ValidationError(msg) | ||||
| 
 | ||||
|     def pre_save(self, model_instance, add): | ||||
|  | @ -575,7 +675,10 @@ class DateField(Field): | |||
|         return super(DateField, self).formfield(**defaults) | ||||
| 
 | ||||
| class DateTimeField(DateField): | ||||
|     description = ugettext_lazy("Date (with time)") | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.'), | ||||
|     } | ||||
|     description = _("Date (with time)") | ||||
| 
 | ||||
|     def get_internal_type(self): | ||||
|         return "DateTimeField" | ||||
|  | @ -596,8 +699,7 @@ class DateTimeField(DateField): | |||
|                 value, usecs = value.split('.') | ||||
|                 usecs = int(usecs) | ||||
|             except ValueError: | ||||
|                 raise exceptions.ValidationError( | ||||
|                     _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.')) | ||||
|                 raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
|         else: | ||||
|             usecs = 0 | ||||
|         kwargs = {'microsecond': usecs} | ||||
|  | @ -614,8 +716,7 @@ class DateTimeField(DateField): | |||
|                     return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3], | ||||
|                                              **kwargs) | ||||
|                 except ValueError: | ||||
|                     raise exceptions.ValidationError( | ||||
|                         _('Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format.')) | ||||
|                     raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
| 
 | ||||
|     def get_prep_value(self, value): | ||||
|         return self.to_python(value) | ||||
|  | @ -642,7 +743,11 @@ class DateTimeField(DateField): | |||
| 
 | ||||
| class DecimalField(Field): | ||||
|     empty_strings_allowed = False | ||||
|     description = ugettext_lazy("Decimal number") | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'This value must be a decimal number.'), | ||||
|     } | ||||
|     description = _("Decimal number") | ||||
| 
 | ||||
|     def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): | ||||
|         self.max_digits, self.decimal_places = max_digits, decimal_places | ||||
|         Field.__init__(self, verbose_name, name, **kwargs) | ||||
|  | @ -656,8 +761,7 @@ class DecimalField(Field): | |||
|         try: | ||||
|             return decimal.Decimal(value) | ||||
|         except decimal.InvalidOperation: | ||||
|             raise exceptions.ValidationError( | ||||
|                 _("This value must be a decimal number.")) | ||||
|             raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
| 
 | ||||
|     def _format(self, value): | ||||
|         if isinstance(value, basestring) or value is None: | ||||
|  | @ -696,18 +800,15 @@ class DecimalField(Field): | |||
|         return super(DecimalField, self).formfield(**defaults) | ||||
| 
 | ||||
| class EmailField(CharField): | ||||
|     description = ugettext_lazy("E-mail address") | ||||
|     default_validators = [validators.validate_email] | ||||
|     description = _("E-mail address") | ||||
| 
 | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         kwargs['max_length'] = kwargs.get('max_length', 75) | ||||
|         CharField.__init__(self, *args, **kwargs) | ||||
| 
 | ||||
|     def formfield(self, **kwargs): | ||||
|         defaults = {'form_class': forms.EmailField} | ||||
|         defaults.update(kwargs) | ||||
|         return super(EmailField, self).formfield(**defaults) | ||||
| 
 | ||||
| class FilePathField(Field): | ||||
|     description = ugettext_lazy("File path") | ||||
|     description = _("File path") | ||||
| 
 | ||||
|     def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): | ||||
|         self.path, self.match, self.recursive = path, match, recursive | ||||
|  | @ -729,7 +830,10 @@ class FilePathField(Field): | |||
| 
 | ||||
| class FloatField(Field): | ||||
|     empty_strings_allowed = False | ||||
|     description = ugettext_lazy("Floating point number") | ||||
|     default_error_messages = { | ||||
|         'invalid': _("This value must be a float."), | ||||
|     } | ||||
|     description = _("Floating point number") | ||||
| 
 | ||||
|     def get_prep_value(self, value): | ||||
|         if value is None: | ||||
|  | @ -745,8 +849,7 @@ class FloatField(Field): | |||
|         try: | ||||
|             return float(value) | ||||
|         except (TypeError, ValueError): | ||||
|             raise exceptions.ValidationError( | ||||
|                 _("This value must be a float.")) | ||||
|             raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
| 
 | ||||
|     def formfield(self, **kwargs): | ||||
|         defaults = {'form_class': forms.FloatField} | ||||
|  | @ -755,7 +858,10 @@ class FloatField(Field): | |||
| 
 | ||||
| class IntegerField(Field): | ||||
|     empty_strings_allowed = False | ||||
|     description = ugettext_lazy("Integer") | ||||
|     default_error_messages = { | ||||
|         'invalid': _("This value must be a float."), | ||||
|     } | ||||
|     description = _("Integer") | ||||
| 
 | ||||
|     def get_prep_value(self, value): | ||||
|         if value is None: | ||||
|  | @ -771,8 +877,7 @@ class IntegerField(Field): | |||
|         try: | ||||
|             return int(value) | ||||
|         except (TypeError, ValueError): | ||||
|             raise exceptions.ValidationError( | ||||
|                 _("This value must be an integer.")) | ||||
|             raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
| 
 | ||||
|     def formfield(self, **kwargs): | ||||
|         defaults = {'form_class': forms.IntegerField} | ||||
|  | @ -781,7 +886,7 @@ class IntegerField(Field): | |||
| 
 | ||||
| class BigIntegerField(IntegerField): | ||||
|     empty_strings_allowed = False | ||||
|     description = ugettext_lazy("Big (8 byte) integer") | ||||
|     description = _("Big (8 byte) integer") | ||||
|     MAX_BIGINT = 9223372036854775807 | ||||
|     def get_internal_type(self): | ||||
|         return "BigIntegerField" | ||||
|  | @ -794,7 +899,7 @@ class BigIntegerField(IntegerField): | |||
| 
 | ||||
| class IPAddressField(Field): | ||||
|     empty_strings_allowed = False | ||||
|     description = ugettext_lazy("IP address") | ||||
|     description = _("IP address") | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         kwargs['max_length'] = 15 | ||||
|         Field.__init__(self, *args, **kwargs) | ||||
|  | @ -809,7 +914,11 @@ class IPAddressField(Field): | |||
| 
 | ||||
| class NullBooleanField(Field): | ||||
|     empty_strings_allowed = False | ||||
|     description = ugettext_lazy("Boolean (Either True, False or None)") | ||||
|     default_error_messages = { | ||||
|         'invalid': _("This value must be either None, True or False."), | ||||
|     } | ||||
|     description = _("Boolean (Either True, False or None)") | ||||
| 
 | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         kwargs['null'] = True | ||||
|         Field.__init__(self, *args, **kwargs) | ||||
|  | @ -822,8 +931,7 @@ class NullBooleanField(Field): | |||
|         if value in ('None',): return None | ||||
|         if value in ('t', 'True', '1'): return True | ||||
|         if value in ('f', 'False', '0'): return False | ||||
|         raise exceptions.ValidationError( | ||||
|             _("This value must be either None, True or False.")) | ||||
|         raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
| 
 | ||||
|     def get_prep_lookup(self, lookup_type, value): | ||||
|         # Special-case handling for filters coming from a web request (e.g. the | ||||
|  | @ -849,7 +957,7 @@ class NullBooleanField(Field): | |||
|         return super(NullBooleanField, self).formfield(**defaults) | ||||
| 
 | ||||
| class PositiveIntegerField(IntegerField): | ||||
|     description = ugettext_lazy("Integer") | ||||
|     description = _("Integer") | ||||
| 
 | ||||
|     def get_internal_type(self): | ||||
|         return "PositiveIntegerField" | ||||
|  | @ -860,7 +968,7 @@ class PositiveIntegerField(IntegerField): | |||
|         return super(PositiveIntegerField, self).formfield(**defaults) | ||||
| 
 | ||||
| class PositiveSmallIntegerField(IntegerField): | ||||
|     description = ugettext_lazy("Integer") | ||||
|     description = _("Integer") | ||||
|     def get_internal_type(self): | ||||
|         return "PositiveSmallIntegerField" | ||||
| 
 | ||||
|  | @ -870,7 +978,7 @@ class PositiveSmallIntegerField(IntegerField): | |||
|         return super(PositiveSmallIntegerField, self).formfield(**defaults) | ||||
| 
 | ||||
| class SlugField(CharField): | ||||
|     description = ugettext_lazy("String (up to %(max_length)s)") | ||||
|     description = _("String (up to %(max_length)s)") | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         kwargs['max_length'] = kwargs.get('max_length', 50) | ||||
|         # Set db_index=True unless it's been set manually. | ||||
|  | @ -887,13 +995,13 @@ class SlugField(CharField): | |||
|         return super(SlugField, self).formfield(**defaults) | ||||
| 
 | ||||
| class SmallIntegerField(IntegerField): | ||||
|     description = ugettext_lazy("Integer") | ||||
|     description = _("Integer") | ||||
| 
 | ||||
|     def get_internal_type(self): | ||||
|         return "SmallIntegerField" | ||||
| 
 | ||||
| class TextField(Field): | ||||
|     description = ugettext_lazy("Text") | ||||
|     description = _("Text") | ||||
| 
 | ||||
|     def get_internal_type(self): | ||||
|         return "TextField" | ||||
|  | @ -904,9 +1012,12 @@ class TextField(Field): | |||
|         return super(TextField, self).formfield(**defaults) | ||||
| 
 | ||||
| class TimeField(Field): | ||||
|     description = ugettext_lazy("Time") | ||||
|     description = _("Time") | ||||
| 
 | ||||
|     empty_strings_allowed = False | ||||
|     default_error_messages = { | ||||
|         'invalid': _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.'), | ||||
|     } | ||||
|     def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): | ||||
|         self.auto_now, self.auto_now_add = auto_now, auto_now_add | ||||
|         if auto_now or auto_now_add: | ||||
|  | @ -935,8 +1046,7 @@ class TimeField(Field): | |||
|                 value, usecs = value.split('.') | ||||
|                 usecs = int(usecs) | ||||
|             except ValueError: | ||||
|                 raise exceptions.ValidationError( | ||||
|                     _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.')) | ||||
|                 raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
|         else: | ||||
|             usecs = 0 | ||||
|         kwargs = {'microsecond': usecs} | ||||
|  | @ -949,8 +1059,7 @@ class TimeField(Field): | |||
|                 return datetime.time(*time.strptime(value, '%H:%M')[3:5], | ||||
|                                          **kwargs) | ||||
|             except ValueError: | ||||
|                 raise exceptions.ValidationError( | ||||
|                     _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.')) | ||||
|                 raise exceptions.ValidationError(self.error_messages['invalid']) | ||||
| 
 | ||||
|     def pre_save(self, model_instance, add): | ||||
|         if self.auto_now or (self.auto_now_add and add): | ||||
|  | @ -983,21 +1092,17 @@ class TimeField(Field): | |||
|         return super(TimeField, self).formfield(**defaults) | ||||
| 
 | ||||
| class URLField(CharField): | ||||
|     description = ugettext_lazy("URL") | ||||
|     description = _("URL") | ||||
| 
 | ||||
|     def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): | ||||
|         kwargs['max_length'] = kwargs.get('max_length', 200) | ||||
|         self.verify_exists = verify_exists | ||||
|         CharField.__init__(self, verbose_name, name, **kwargs) | ||||
| 
 | ||||
|     def formfield(self, **kwargs): | ||||
|         defaults = {'form_class': forms.URLField, 'verify_exists': self.verify_exists} | ||||
|         defaults.update(kwargs) | ||||
|         return super(URLField, self).formfield(**defaults) | ||||
|         self.validators.append(validators.URLValidator(verify_exists=verify_exists)) | ||||
| 
 | ||||
| class XMLField(TextField): | ||||
|     description = ugettext_lazy("XML text") | ||||
|     description = _("XML text") | ||||
| 
 | ||||
|     def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): | ||||
|         self.schema_path = schema_path | ||||
|         Field.__init__(self, verbose_name, name, **kwargs) | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ from django.db.models.related import RelatedObject | |||
| from django.db.models.query import QuerySet | ||||
| from django.db.models.query_utils import QueryWrapper | ||||
| from django.utils.encoding import smart_unicode | ||||
| from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _ | ||||
| from django.utils.translation import ugettext_lazy as _, string_concat, ungettext, ugettext | ||||
| from django.utils.functional import curry | ||||
| from django.core import exceptions | ||||
| from django import forms | ||||
|  | @ -473,7 +473,7 @@ def create_many_related_manager(superclass, rel=False): | |||
|             if not rel.through._meta.auto_created: | ||||
|                 opts = through._meta | ||||
|                 raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name) | ||||
|             new_obj = super(ManyRelatedManager, self).using(self.instance._state.db).create(**kwargs) | ||||
|             new_obj = super(ManyRelatedManager, self).create(**kwargs) | ||||
|             self.add(new_obj) | ||||
|             return new_obj | ||||
|         create.alters_data = True | ||||
|  | @ -708,7 +708,10 @@ class ManyToManyRel(object): | |||
| 
 | ||||
| class ForeignKey(RelatedField, Field): | ||||
|     empty_strings_allowed = False | ||||
|     description = ugettext_lazy("Foreign Key (type determined by related field)") | ||||
|     default_error_messages = { | ||||
|         'invalid': _('Model %(model)s with pk %(pk)r does not exist.') | ||||
|     } | ||||
|     description = _("Foreign Key (type determined by related field)") | ||||
|     def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): | ||||
|         try: | ||||
|             to_name = to._meta.object_name.lower() | ||||
|  | @ -731,6 +734,18 @@ class ForeignKey(RelatedField, Field): | |||
| 
 | ||||
|         self.db_index = True | ||||
| 
 | ||||
|     def validate(self, value, model_instance): | ||||
|         if self.rel.parent_link: | ||||
|             return | ||||
|         super(ForeignKey, self).validate(value, model_instance) | ||||
|         if not value: | ||||
|             return | ||||
|         try: | ||||
|             self.rel.to._default_manager.get(**{self.rel.field_name:value}) | ||||
|         except self.rel.to.DoesNotExist, e: | ||||
|             raise exceptions.ValidationError( | ||||
|                     self.error_messages['invalid'] % {'model': self.rel.to._meta.verbose_name, 'pk': value}) | ||||
| 
 | ||||
|     def get_attname(self): | ||||
|         return '%s_id' % self.name | ||||
| 
 | ||||
|  | @ -812,7 +827,7 @@ class OneToOneField(ForeignKey): | |||
|     always returns the object pointed to (since there will only ever be one), | ||||
|     rather than returning a list. | ||||
|     """ | ||||
|     description = ugettext_lazy("One-to-one relationship") | ||||
|     description = _("One-to-one relationship") | ||||
|     def __init__(self, to, to_field=None, **kwargs): | ||||
|         kwargs['unique'] = True | ||||
|         super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs) | ||||
|  | @ -826,6 +841,12 @@ class OneToOneField(ForeignKey): | |||
|             return None | ||||
|         return super(OneToOneField, self).formfield(**kwargs) | ||||
| 
 | ||||
|     def save_form_data(self, instance, data): | ||||
|         if isinstance(data, self.rel.to): | ||||
|             setattr(instance, self.name, data) | ||||
|         else: | ||||
|             setattr(instance, self.attname, data) | ||||
| 
 | ||||
| def create_many_to_many_intermediary_model(field, klass): | ||||
|     from django.db import models | ||||
|     managed = True | ||||
|  | @ -866,7 +887,7 @@ def create_many_to_many_intermediary_model(field, klass): | |||
|     }) | ||||
| 
 | ||||
| class ManyToManyField(RelatedField, Field): | ||||
|     description = ugettext_lazy("Many-to-many relationship") | ||||
|     description = _("Many-to-many relationship") | ||||
|     def __init__(self, to, **kwargs): | ||||
|         try: | ||||
|             assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) | ||||
|  | @ -886,7 +907,7 @@ class ManyToManyField(RelatedField, Field): | |||
| 
 | ||||
|         Field.__init__(self, **kwargs) | ||||
| 
 | ||||
|         msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.') | ||||
|         msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.') | ||||
|         self.help_text = string_concat(self.help_text, ' ', msg) | ||||
| 
 | ||||
|     def get_choices_default(self): | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ TODO: | |||
|     "This form field requires foo.js" and form.js_includes() | ||||
| """ | ||||
| 
 | ||||
| from util import ValidationError | ||||
| from django.core.exceptions import ValidationError | ||||
| from widgets import * | ||||
| from fields import * | ||||
| from forms import * | ||||
|  |  | |||
|  | @ -14,15 +14,21 @@ try: | |||
| except ImportError: | ||||
|     from StringIO import StringIO | ||||
| 
 | ||||
| import django.core.exceptions | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.core import validators | ||||
| import django.utils.copycompat as copy | ||||
| from django.utils.translation import ugettext_lazy as _ | ||||
| from django.utils.encoding import smart_unicode, smart_str | ||||
| from django.utils.formats import get_format | ||||
| from django.utils.functional import lazy | ||||
| 
 | ||||
| from util import ErrorList, ValidationError | ||||
| from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget | ||||
| # Provide this import for backwards compatibility. | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| 
 | ||||
| from util import ErrorList | ||||
| from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, \ | ||||
|         FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, \ | ||||
|         DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget | ||||
| 
 | ||||
| __all__ = ( | ||||
|     'Field', 'CharField', 'IntegerField', | ||||
|  | @ -36,9 +42,6 @@ __all__ = ( | |||
|     'TypedChoiceField' | ||||
| ) | ||||
| 
 | ||||
| # These values, if given to to_python(), will trigger the self.required check. | ||||
| EMPTY_VALUES = (None, '') | ||||
| 
 | ||||
| def en_format(name): | ||||
|     """ | ||||
|     Helper function to stay backward compatible. | ||||
|  | @ -57,6 +60,7 @@ DEFAULT_DATETIME_INPUT_FORMATS = lazy(lambda: en_format('DATETIME_INPUT_FORMATS' | |||
| class Field(object): | ||||
|     widget = TextInput # Default widget to use when rendering this type of Field. | ||||
|     hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". | ||||
|     default_validators = [] # Default set of validators | ||||
|     default_error_messages = { | ||||
|         'required': _(u'This field is required.'), | ||||
|         'invalid': _(u'Enter a valid value.'), | ||||
|  | @ -66,7 +70,8 @@ class Field(object): | |||
|     creation_counter = 0 | ||||
| 
 | ||||
|     def __init__(self, required=True, widget=None, label=None, initial=None, | ||||
|                  help_text=None, error_messages=None, show_hidden_initial=False): | ||||
|                  help_text=None, error_messages=None, show_hidden_initial=False, | ||||
|                  validators=[]): | ||||
|         # required -- Boolean that specifies whether the field is required. | ||||
|         #             True by default. | ||||
|         # widget -- A Widget class, or instance of a Widget class, that should | ||||
|  | @ -82,6 +87,7 @@ class Field(object): | |||
|         # help_text -- An optional string to use as "help text" for this Field. | ||||
|         # show_hidden_initial -- Boolean that specifies if it is needed to render a | ||||
|         #                        hidden widget with initial value after widget. | ||||
|         # validators -- List of addtional validators to use | ||||
|         if label is not None: | ||||
|             label = smart_unicode(label) | ||||
|         self.required, self.label, self.initial = required, label, initial | ||||
|  | @ -105,16 +111,39 @@ class Field(object): | |||
|         self.creation_counter = Field.creation_counter | ||||
|         Field.creation_counter += 1 | ||||
| 
 | ||||
|         def set_class_error_messages(messages, klass): | ||||
|             for base_class in klass.__bases__: | ||||
|                 set_class_error_messages(messages, base_class) | ||||
|             messages.update(getattr(klass, 'default_error_messages', {})) | ||||
| 
 | ||||
|         messages = {} | ||||
|         set_class_error_messages(messages, self.__class__) | ||||
|         for c in reversed(self.__class__.__mro__): | ||||
|             messages.update(getattr(c, 'default_error_messages', {})) | ||||
|         messages.update(error_messages or {}) | ||||
|         self.error_messages = messages | ||||
| 
 | ||||
|         self.validators = self.default_validators + validators | ||||
| 
 | ||||
|     def to_python(self, value): | ||||
|         return value | ||||
| 
 | ||||
|     def validate(self, value): | ||||
|         if value in validators.EMPTY_VALUES and self.required: | ||||
|             raise ValidationError(self.error_messages['required']) | ||||
| 
 | ||||
|     def run_validators(self, value): | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return | ||||
|         errors = [] | ||||
|         for v in self.validators: | ||||
|             try: | ||||
|                 v(value) | ||||
|             except ValidationError, e: | ||||
|                 if hasattr(e, 'code') and e.code in self.error_messages: | ||||
|                     message = self.error_messages[e.code] | ||||
|                     if e.params: | ||||
|                         message = message % e.params | ||||
|                     errors.append(message) | ||||
|                 else: | ||||
|                     errors.extend(e.messages) | ||||
|         if errors: | ||||
|             raise ValidationError(errors) | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates the given value and returns its "cleaned" value as an | ||||
|  | @ -122,8 +151,9 @@ class Field(object): | |||
| 
 | ||||
|         Raises ValidationError for any errors. | ||||
|         """ | ||||
|         if self.required and value in EMPTY_VALUES: | ||||
|             raise ValidationError(self.error_messages['required']) | ||||
|         value = self.to_python(value) | ||||
|         self.validate(value) | ||||
|         self.run_validators(value) | ||||
|         return value | ||||
| 
 | ||||
|     def widget_attrs(self, widget): | ||||
|  | @ -141,27 +171,19 @@ class Field(object): | |||
|         return result | ||||
| 
 | ||||
| class CharField(Field): | ||||
|     default_error_messages = { | ||||
|         'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'), | ||||
|         'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'), | ||||
|     } | ||||
| 
 | ||||
|     def __init__(self, max_length=None, min_length=None, *args, **kwargs): | ||||
|         self.max_length, self.min_length = max_length, min_length | ||||
|         super(CharField, self).__init__(*args, **kwargs) | ||||
|         if min_length is not None: | ||||
|             self.validators.append(validators.MinLengthValidator(min_length)) | ||||
|         if max_length is not None: | ||||
|             self.validators.append(validators.MaxLengthValidator(max_length)) | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|         "Validates max_length and min_length. Returns a Unicode object." | ||||
|         super(CharField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|     def to_python(self, value): | ||||
|         "Returns a Unicode object." | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return u'' | ||||
|         value = smart_unicode(value) | ||||
|         value_length = len(value) | ||||
|         if self.max_length is not None and value_length > self.max_length: | ||||
|             raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length}) | ||||
|         if self.min_length is not None and value_length < self.min_length: | ||||
|             raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length}) | ||||
|         return value | ||||
|         return smart_unicode(value) | ||||
| 
 | ||||
|     def widget_attrs(self, widget): | ||||
|         if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): | ||||
|  | @ -171,87 +193,82 @@ class CharField(Field): | |||
| class IntegerField(Field): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a whole number.'), | ||||
|         'max_value': _(u'Ensure this value is less than or equal to %s.'), | ||||
|         'min_value': _(u'Ensure this value is greater than or equal to %s.'), | ||||
|         'max_value': _(u'Ensure this value is less than or equal to %(limit_value)s.'), | ||||
|         'min_value': _(u'Ensure this value is greater than or equal to %(limit_value)s.'), | ||||
|     } | ||||
| 
 | ||||
|     def __init__(self, max_value=None, min_value=None, *args, **kwargs): | ||||
|         self.max_value, self.min_value = max_value, min_value | ||||
|         super(IntegerField, self).__init__(*args, **kwargs) | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|         if max_value is not None: | ||||
|             self.validators.append(validators.MaxValueValidator(max_value)) | ||||
|         if min_value is not None: | ||||
|             self.validators.append(validators.MinValueValidator(min_value)) | ||||
| 
 | ||||
|     def to_python(self, value): | ||||
|         """ | ||||
|         Validates that int() can be called on the input. Returns the result | ||||
|         of int(). Returns None for empty values. | ||||
|         """ | ||||
|         super(IntegerField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|         value = super(IntegerField, self).to_python(value) | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return None | ||||
| 
 | ||||
|         try: | ||||
|             value = int(str(value)) | ||||
|         except (ValueError, TypeError): | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
|         if self.max_value is not None and value > self.max_value: | ||||
|             raise ValidationError(self.error_messages['max_value'] % self.max_value) | ||||
|         if self.min_value is not None and value < self.min_value: | ||||
|             raise ValidationError(self.error_messages['min_value'] % self.min_value) | ||||
|         return value | ||||
| 
 | ||||
| class FloatField(Field): | ||||
| class FloatField(IntegerField): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a number.'), | ||||
|         'max_value': _(u'Ensure this value is less than or equal to %s.'), | ||||
|         'min_value': _(u'Ensure this value is greater than or equal to %s.'), | ||||
|     } | ||||
| 
 | ||||
|     def __init__(self, max_value=None, min_value=None, *args, **kwargs): | ||||
|         self.max_value, self.min_value = max_value, min_value | ||||
|         Field.__init__(self, *args, **kwargs) | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|     def to_python(self, value): | ||||
|         """ | ||||
|         Validates that float() can be called on the input. Returns a float. | ||||
|         Returns None for empty values. | ||||
|         Validates that float() can be called on the input. Returns the result | ||||
|         of float(). Returns None for empty values. | ||||
|         """ | ||||
|         super(FloatField, self).clean(value) | ||||
|         if not self.required and value in EMPTY_VALUES: | ||||
|         value = super(IntegerField, self).to_python(value) | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return None | ||||
| 
 | ||||
|         try: | ||||
|             # We always accept dot as decimal separator | ||||
|             if isinstance(value, str) or isinstance(value, unicode): | ||||
|                 value = float(value.replace(get_format('DECIMAL_SEPARATOR'), '.')) | ||||
|         except (ValueError, TypeError): | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
|         if self.max_value is not None and value > self.max_value: | ||||
|             raise ValidationError(self.error_messages['max_value'] % self.max_value) | ||||
|         if self.min_value is not None and value < self.min_value: | ||||
|             raise ValidationError(self.error_messages['min_value'] % self.min_value) | ||||
|         return value | ||||
| 
 | ||||
| class DecimalField(Field): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a number.'), | ||||
|         'max_value': _(u'Ensure this value is less than or equal to %s.'), | ||||
|         'min_value': _(u'Ensure this value is greater than or equal to %s.'), | ||||
|         'max_value': _(u'Ensure this value is less than or equal to %(limit_value)s.'), | ||||
|         'min_value': _(u'Ensure this value is greater than or equal to %(limit_value)s.'), | ||||
|         'max_digits': _('Ensure that there are no more than %s digits in total.'), | ||||
|         'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), | ||||
|         'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') | ||||
|     } | ||||
| 
 | ||||
|     def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): | ||||
|         self.max_value, self.min_value = max_value, min_value | ||||
|         self.max_digits, self.decimal_places = max_digits, decimal_places | ||||
|         Field.__init__(self, *args, **kwargs) | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|         if max_value is not None: | ||||
|             self.validators.append(validators.MaxValueValidator(max_value)) | ||||
|         if min_value is not None: | ||||
|             self.validators.append(validators.MinValueValidator(min_value)) | ||||
| 
 | ||||
|     def to_python(self, value): | ||||
|         """ | ||||
|         Validates that the input is a decimal number. Returns a Decimal | ||||
|         instance. Returns None for empty values. Ensures that there are no more | ||||
|         than max_digits in the number, and no more than decimal_places digits | ||||
|         after the decimal point. | ||||
|         """ | ||||
|         super(DecimalField, self).clean(value) | ||||
|         if not self.required and value in EMPTY_VALUES: | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return None | ||||
|         value = smart_str(value).strip() | ||||
|         try: | ||||
|  | @ -260,7 +277,12 @@ class DecimalField(Field): | |||
|                 value = Decimal(value.replace(get_format('DECIMAL_SEPARATOR'), '.')) | ||||
|         except DecimalException: | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
|         return value | ||||
| 
 | ||||
|     def validate(self, value): | ||||
|         super(DecimalField, self).validate(value) | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return | ||||
|         sign, digittuple, exponent = value.as_tuple() | ||||
|         decimals = abs(exponent) | ||||
|         # digittuple doesn't include any leading zeros. | ||||
|  | @ -273,10 +295,6 @@ class DecimalField(Field): | |||
|             digits = decimals | ||||
|         whole_digits = digits - decimals | ||||
| 
 | ||||
|         if self.max_value is not None and value > self.max_value: | ||||
|             raise ValidationError(self.error_messages['max_value'] % self.max_value) | ||||
|         if self.min_value is not None and value < self.min_value: | ||||
|             raise ValidationError(self.error_messages['min_value'] % self.min_value) | ||||
|         if self.max_digits is not None and digits > self.max_digits: | ||||
|             raise ValidationError(self.error_messages['max_digits'] % self.max_digits) | ||||
|         if self.decimal_places is not None and decimals > self.decimal_places: | ||||
|  | @ -295,13 +313,12 @@ class DateField(Field): | |||
|         super(DateField, self).__init__(*args, **kwargs) | ||||
|         self.input_formats = input_formats | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|     def to_python(self, value): | ||||
|         """ | ||||
|         Validates that the input can be converted to a date. Returns a Python | ||||
|         datetime.date object. | ||||
|         """ | ||||
|         super(DateField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return None | ||||
|         if isinstance(value, datetime.datetime): | ||||
|             return value.date() | ||||
|  | @ -324,13 +341,12 @@ class TimeField(Field): | |||
|         super(TimeField, self).__init__(*args, **kwargs) | ||||
|         self.input_formats = input_formats | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|     def to_python(self, value): | ||||
|         """ | ||||
|         Validates that the input can be converted to a time. Returns a Python | ||||
|         datetime.time object. | ||||
|         """ | ||||
|         super(TimeField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return None | ||||
|         if isinstance(value, datetime.time): | ||||
|             return value | ||||
|  | @ -351,13 +367,12 @@ class DateTimeField(Field): | |||
|         super(DateTimeField, self).__init__(*args, **kwargs) | ||||
|         self.input_formats = input_formats | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|     def to_python(self, value): | ||||
|         """ | ||||
|         Validates that the input can be converted to a datetime. Returns a | ||||
|         Python datetime.datetime object. | ||||
|         """ | ||||
|         super(DateTimeField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return None | ||||
|         if isinstance(value, datetime.datetime): | ||||
|             return value | ||||
|  | @ -392,40 +407,13 @@ class RegexField(CharField): | |||
|         if isinstance(regex, basestring): | ||||
|             regex = re.compile(regex) | ||||
|         self.regex = regex | ||||
|         self.validators.append(validators.RegexValidator(regex=regex)) | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates that the input matches the regular expression. Returns a | ||||
|         Unicode object. | ||||
|         """ | ||||
|         value = super(RegexField, self).clean(value) | ||||
|         if value == u'': | ||||
|             return value | ||||
|         if not self.regex.search(value): | ||||
|             raise ValidationError(self.error_messages['invalid']) | ||||
|         return value | ||||
| 
 | ||||
| email_re = re.compile( | ||||
|     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom | ||||
|     r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string | ||||
|     r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE)  # domain | ||||
| 
 | ||||
| class EmailField(RegexField): | ||||
| class EmailField(CharField): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a valid e-mail address.'), | ||||
|     } | ||||
| 
 | ||||
|     def __init__(self, max_length=None, min_length=None, *args, **kwargs): | ||||
|         RegexField.__init__(self, email_re, max_length, min_length, *args, | ||||
|                             **kwargs) | ||||
| 
 | ||||
| try: | ||||
|     from django.conf import settings | ||||
|     URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT | ||||
| except ImportError: | ||||
|     # It's OK if Django settings aren't configured. | ||||
|     URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' | ||||
| 
 | ||||
|     default_validators = [validators.validate_email] | ||||
| 
 | ||||
| class FileField(Field): | ||||
|     widget = FileInput | ||||
|  | @ -440,12 +428,9 @@ class FileField(Field): | |||
|         self.max_length = kwargs.pop('max_length', None) | ||||
|         super(FileField, self).__init__(*args, **kwargs) | ||||
| 
 | ||||
|     def clean(self, data, initial=None): | ||||
|         super(FileField, self).clean(initial or data) | ||||
|         if not self.required and data in EMPTY_VALUES: | ||||
|     def to_python(self, data): | ||||
|         if data in validators.EMPTY_VALUES: | ||||
|             return None | ||||
|         elif not data and initial: | ||||
|             return initial | ||||
| 
 | ||||
|         # UploadedFile objects should have name and size attributes. | ||||
|         try: | ||||
|  | @ -464,21 +449,24 @@ class FileField(Field): | |||
| 
 | ||||
|         return data | ||||
| 
 | ||||
|     def clean(self, data, initial=None): | ||||
|         if not data and initial: | ||||
|             return initial | ||||
|         return super(FileField, self).clean(data) | ||||
| 
 | ||||
| class ImageField(FileField): | ||||
|     default_error_messages = { | ||||
|         'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), | ||||
|     } | ||||
| 
 | ||||
|     def clean(self, data, initial=None): | ||||
|     def to_python(self, data): | ||||
|         """ | ||||
|         Checks that the file-upload field data contains a valid image (GIF, JPG, | ||||
|         PNG, possibly others -- whatever the Python Imaging Library supports). | ||||
|         """ | ||||
|         f = super(ImageField, self).clean(data, initial) | ||||
|         f = super(ImageField, self).to_python(data) | ||||
|         if f is None: | ||||
|             return None | ||||
|         elif not data and initial: | ||||
|             return initial | ||||
|         from PIL import Image | ||||
| 
 | ||||
|         # We need to get a file object for PIL. We might have a path or we might | ||||
|  | @ -517,59 +505,34 @@ class ImageField(FileField): | |||
|             f.seek(0) | ||||
|         return f | ||||
| 
 | ||||
| url_re = re.compile( | ||||
|     r'^https?://' # http:// or https:// | ||||
|     r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain... | ||||
|     r'localhost|' #localhost... | ||||
|     r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip | ||||
|     r'(?::\d+)?' # optional port | ||||
|     r'(?:/?|/\S+)$', re.IGNORECASE) | ||||
| 
 | ||||
| class URLField(RegexField): | ||||
| class URLField(CharField): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a valid URL.'), | ||||
|         'invalid_link': _(u'This URL appears to be a broken link.'), | ||||
|     } | ||||
| 
 | ||||
|     def __init__(self, max_length=None, min_length=None, verify_exists=False, | ||||
|             validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): | ||||
|         super(URLField, self).__init__(url_re, max_length, min_length, *args, | ||||
|             validator_user_agent=validators.URL_VALIDATOR_USER_AGENT, *args, **kwargs): | ||||
|         super(URLField, self).__init__(max_length, min_length, *args, | ||||
|                                        **kwargs) | ||||
|         self.verify_exists = verify_exists | ||||
|         self.user_agent = validator_user_agent | ||||
|         self.validators.append(validators.URLValidator(verify_exists=verify_exists, validator_user_agent=validator_user_agent)) | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|         # If no URL scheme given, assume http:// | ||||
|         if value and '://' not in value: | ||||
|             value = u'http://%s' % value | ||||
|         # If no URL path given, assume / | ||||
|         if value and not urlparse.urlsplit(value)[2]: | ||||
|             value += '/' | ||||
|         value = super(URLField, self).clean(value) | ||||
|         if value == u'': | ||||
|             return value | ||||
|         if self.verify_exists: | ||||
|             import urllib2 | ||||
|             headers = { | ||||
|                 "Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", | ||||
|                 "Accept-Language": "en-us,en;q=0.5", | ||||
|                 "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", | ||||
|                 "Connection": "close", | ||||
|                 "User-Agent": self.user_agent, | ||||
|             } | ||||
|             try: | ||||
|                 req = urllib2.Request(value, None, headers) | ||||
|                 u = urllib2.urlopen(req) | ||||
|             except ValueError: | ||||
|                 raise ValidationError(self.error_messages['invalid']) | ||||
|             except: # urllib2.URLError, httplib.InvalidURL, etc. | ||||
|                 raise ValidationError(self.error_messages['invalid_link']) | ||||
|         return value | ||||
|     def to_python(self, value): | ||||
|         if value: | ||||
|             if '://' not in value: | ||||
|                 # If no URL scheme given, assume http:// | ||||
|                 value = u'http://%s' % value | ||||
|             url_fields = list(urlparse.urlsplit(value)) | ||||
|             if not url_fields[2]: | ||||
|                 # the path portion may need to be added before query params | ||||
|                 url_fields[2] = '/' | ||||
|                 value = urlparse.urlunsplit(url_fields) | ||||
|         return super(URLField, self).to_python(value) | ||||
| 
 | ||||
| class BooleanField(Field): | ||||
|     widget = CheckboxInput | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|     def to_python(self, value): | ||||
|         """Returns a Python boolean object.""" | ||||
|         # Explicitly check for the string 'False', which is what a hidden field | ||||
|         # will submit for False. Also check for '0', since this is what | ||||
|  | @ -579,7 +542,7 @@ class BooleanField(Field): | |||
|             value = False | ||||
|         else: | ||||
|             value = bool(value) | ||||
|         super(BooleanField, self).clean(value) | ||||
|         value = super(BooleanField, self).to_python(value) | ||||
|         if not value and self.required: | ||||
|             raise ValidationError(self.error_messages['required']) | ||||
|         return value | ||||
|  | @ -591,7 +554,7 @@ class NullBooleanField(BooleanField): | |||
|     """ | ||||
|     widget = NullBooleanSelect | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|     def to_python(self, value): | ||||
|         """ | ||||
|         Explicitly checks for the string 'True' and 'False', which is what a | ||||
|         hidden field will submit for True and False, and for '1' and '0', which | ||||
|  | @ -605,6 +568,9 @@ class NullBooleanField(BooleanField): | |||
|         else: | ||||
|             return None | ||||
| 
 | ||||
|     def validate(self, value): | ||||
|         pass | ||||
| 
 | ||||
| class ChoiceField(Field): | ||||
|     widget = Select | ||||
|     default_error_messages = { | ||||
|  | @ -613,8 +579,8 @@ class ChoiceField(Field): | |||
| 
 | ||||
|     def __init__(self, choices=(), required=True, widget=None, label=None, | ||||
|                  initial=None, help_text=None, *args, **kwargs): | ||||
|         super(ChoiceField, self).__init__(required, widget, label, initial, | ||||
|                                           help_text, *args, **kwargs) | ||||
|         super(ChoiceField, self).__init__(required=required, widget=widget, label=label,  | ||||
|                                         initial=initial, help_text=help_text, *args, **kwargs) | ||||
|         self.choices = choices | ||||
| 
 | ||||
|     def _get_choices(self): | ||||
|  | @ -628,19 +594,19 @@ class ChoiceField(Field): | |||
| 
 | ||||
|     choices = property(_get_choices, _set_choices) | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|     def to_python(self, value): | ||||
|         "Returns a Unicode object." | ||||
|         if value in validators.EMPTY_VALUES: | ||||
|             return u'' | ||||
|         return smart_unicode(value) | ||||
| 
 | ||||
|     def validate(self, value): | ||||
|         """ | ||||
|         Validates that the input is in self.choices. | ||||
|         """ | ||||
|         value = super(ChoiceField, self).clean(value) | ||||
|         if value in EMPTY_VALUES: | ||||
|             value = u'' | ||||
|         value = smart_unicode(value) | ||||
|         if value == u'': | ||||
|             return value | ||||
|         if not self.valid_value(value): | ||||
|         super(ChoiceField, self).validate(value) | ||||
|         if value and not self.valid_value(value): | ||||
|             raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) | ||||
|         return value | ||||
| 
 | ||||
|     def valid_value(self, value): | ||||
|         "Check to see if the provided value is a valid choice" | ||||
|  | @ -661,27 +627,24 @@ class TypedChoiceField(ChoiceField): | |||
|         self.empty_value = kwargs.pop('empty_value', '') | ||||
|         super(TypedChoiceField, self).__init__(*args, **kwargs) | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|     def to_python(self, value): | ||||
|         """ | ||||
|         Validate that the value is in self.choices and can be coerced to the | ||||
|         right type. | ||||
|         """ | ||||
|         value = super(TypedChoiceField, self).clean(value) | ||||
|         if value == self.empty_value or value in EMPTY_VALUES: | ||||
|         value = super(TypedChoiceField, self).to_python(value) | ||||
|         super(TypedChoiceField, self).validate(value) | ||||
|         if value == self.empty_value or value in validators.EMPTY_VALUES: | ||||
|             return self.empty_value | ||||
| 
 | ||||
|         # Hack alert: This field is purpose-made to use with Field.to_python as | ||||
|         # a coercion function so that ModelForms with choices work. However, | ||||
|         # Django's Field.to_python raises | ||||
|         # django.core.exceptions.ValidationError, which is a *different* | ||||
|         # exception than django.forms.util.ValidationError. So we need to catch | ||||
|         # both. | ||||
|         try: | ||||
|             value = self.coerce(value) | ||||
|         except (ValueError, TypeError, django.core.exceptions.ValidationError): | ||||
|         except (ValueError, TypeError, ValidationError): | ||||
|             raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) | ||||
|         return value | ||||
| 
 | ||||
|     def validate(self, value): | ||||
|         pass | ||||
| 
 | ||||
| class MultipleChoiceField(ChoiceField): | ||||
|     hidden_widget = MultipleHiddenInput | ||||
|     widget = SelectMultiple | ||||
|  | @ -690,22 +653,23 @@ class MultipleChoiceField(ChoiceField): | |||
|         'invalid_list': _(u'Enter a list of values.'), | ||||
|     } | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|     def to_python(self, value): | ||||
|         if not value: | ||||
|             return [] | ||||
|         elif not isinstance(value, (list, tuple)): | ||||
|             raise ValidationError(self.error_messages['invalid_list']) | ||||
|         return [smart_unicode(val) for val in value] | ||||
| 
 | ||||
|     def validate(self, value): | ||||
|         """ | ||||
|         Validates that the input is a list or tuple. | ||||
|         """ | ||||
|         if self.required and not value: | ||||
|             raise ValidationError(self.error_messages['required']) | ||||
|         elif not self.required and not value: | ||||
|             return [] | ||||
|         if not isinstance(value, (list, tuple)): | ||||
|             raise ValidationError(self.error_messages['invalid_list']) | ||||
|         new_value = [smart_unicode(val) for val in value] | ||||
|         # Validate that each value in the value list is in self.choices. | ||||
|         for val in new_value: | ||||
|         for val in value: | ||||
|             if not self.valid_value(val): | ||||
|                 raise ValidationError(self.error_messages['invalid_choice'] % {'value': val}) | ||||
|         return new_value | ||||
| 
 | ||||
| class ComboField(Field): | ||||
|     """ | ||||
|  | @ -760,6 +724,9 @@ class MultiValueField(Field): | |||
|             f.required = False | ||||
|         self.fields = fields | ||||
| 
 | ||||
|     def validate(self, value): | ||||
|         pass | ||||
| 
 | ||||
|     def clean(self, value): | ||||
|         """ | ||||
|         Validates every value in the given list. A value is validated against | ||||
|  | @ -772,7 +739,7 @@ class MultiValueField(Field): | |||
|         clean_data = [] | ||||
|         errors = ErrorList() | ||||
|         if not value or isinstance(value, (list, tuple)): | ||||
|             if not value or not [v for v in value if v not in EMPTY_VALUES]: | ||||
|             if not value or not [v for v in value if v not in validators.EMPTY_VALUES]: | ||||
|                 if self.required: | ||||
|                     raise ValidationError(self.error_messages['required']) | ||||
|                 else: | ||||
|  | @ -784,7 +751,7 @@ class MultiValueField(Field): | |||
|                 field_value = value[i] | ||||
|             except IndexError: | ||||
|                 field_value = None | ||||
|             if self.required and field_value in EMPTY_VALUES: | ||||
|             if self.required and field_value in validators.EMPTY_VALUES: | ||||
|                 raise ValidationError(self.error_messages['required']) | ||||
|             try: | ||||
|                 clean_data.append(field.clean(field_value)) | ||||
|  | @ -795,7 +762,10 @@ class MultiValueField(Field): | |||
|                 errors.extend(e.messages) | ||||
|         if errors: | ||||
|             raise ValidationError(errors) | ||||
|         return self.compress(clean_data) | ||||
| 
 | ||||
|         out = self.compress(clean_data) | ||||
|         self.validate(out) | ||||
|         return out | ||||
| 
 | ||||
|     def compress(self, data_list): | ||||
|         """ | ||||
|  | @ -864,30 +834,24 @@ class SplitDateTimeField(MultiValueField): | |||
|         if data_list: | ||||
|             # Raise a validation error if time or date is empty | ||||
|             # (possible if SplitDateTimeField has required=False). | ||||
|             if data_list[0] in EMPTY_VALUES: | ||||
|             if data_list[0] in validators.EMPTY_VALUES: | ||||
|                 raise ValidationError(self.error_messages['invalid_date']) | ||||
|             if data_list[1] in EMPTY_VALUES: | ||||
|             if data_list[1] in validators.EMPTY_VALUES: | ||||
|                 raise ValidationError(self.error_messages['invalid_time']) | ||||
|             return datetime.datetime.combine(*data_list) | ||||
|         return None | ||||
| 
 | ||||
| ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') | ||||
| 
 | ||||
| class IPAddressField(RegexField): | ||||
| class IPAddressField(CharField): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u'Enter a valid IPv4 address.'), | ||||
|     } | ||||
|     default_validators = [validators.validate_ipv4_address] | ||||
| 
 | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs) | ||||
| 
 | ||||
| slug_re = re.compile(r'^[-\w]+$') | ||||
| 
 | ||||
| class SlugField(RegexField): | ||||
| class SlugField(CharField): | ||||
|     default_error_messages = { | ||||
|         'invalid': _(u"Enter a valid 'slug' consisting of letters, numbers," | ||||
|                      u" underscores or hyphens."), | ||||
|     } | ||||
| 
 | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(SlugField, self).__init__(slug_re, *args, **kwargs) | ||||
|     default_validators = [validators.validate_slug] | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| Form classes | ||||
| """ | ||||
| 
 | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.utils.copycompat import deepcopy | ||||
| from django.utils.datastructures import SortedDict | ||||
| from django.utils.html import conditional_escape | ||||
|  | @ -10,7 +11,7 @@ from django.utils.safestring import mark_safe | |||
| 
 | ||||
| from fields import Field, FileField | ||||
| from widgets import Media, media_property, TextInput, Textarea | ||||
| from util import flatatt, ErrorDict, ErrorList, ValidationError | ||||
| from util import flatatt, ErrorDict, ErrorList | ||||
| 
 | ||||
| __all__ = ('BaseForm', 'Form') | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,10 +1,11 @@ | |||
| from forms import Form | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.utils.encoding import StrAndUnicode | ||||
| from django.utils.safestring import mark_safe | ||||
| from django.utils.translation import ugettext as _ | ||||
| from fields import IntegerField, BooleanField | ||||
| from widgets import Media, HiddenInput | ||||
| from util import ErrorList, ErrorDict, ValidationError | ||||
| from util import ErrorList | ||||
| 
 | ||||
| __all__ = ('BaseFormSet', 'all_valid') | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,10 +9,12 @@ from django.utils.datastructures import SortedDict | |||
| from django.utils.text import get_text_list, capfirst | ||||
| from django.utils.translation import ugettext_lazy as _, ugettext | ||||
| 
 | ||||
| from util import ValidationError, ErrorList | ||||
| from forms import BaseForm, get_declared_fields, NON_FIELD_ERRORS | ||||
| from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES | ||||
| from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput | ||||
| from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, UnresolvableValidationError | ||||
| from django.core.validators import EMPTY_VALUES | ||||
| from util import ErrorList | ||||
| from forms import BaseForm, get_declared_fields | ||||
| from fields import Field, ChoiceField | ||||
| from widgets import SelectMultiple, HiddenInput, MultipleHiddenInput | ||||
| from widgets import media_property | ||||
| from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME | ||||
| 
 | ||||
|  | @ -27,20 +29,15 @@ __all__ = ( | |||
|     'ModelMultipleChoiceField', | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| def save_instance(form, instance, fields=None, fail_message='saved', | ||||
|                   commit=True, exclude=None): | ||||
| def construct_instance(form, instance, fields=None, exclude=None): | ||||
|     """ | ||||
|     Saves bound Form ``form``'s cleaned_data into model instance ``instance``. | ||||
| 
 | ||||
|     If commit=True, then the changes to ``instance`` will be saved to the | ||||
|     database. Returns ``instance``. | ||||
|     Constructs and returns a model instance from the bound ``form``'s | ||||
|     ``cleaned_data``, but does not save the returned instance to the | ||||
|     database. | ||||
|     """ | ||||
|     from django.db import models | ||||
|     opts = instance._meta | ||||
|     if form.errors: | ||||
|         raise ValueError("The %s could not be %s because the data didn't" | ||||
|                          " validate." % (opts.object_name, fail_message)) | ||||
| 
 | ||||
|     cleaned_data = form.cleaned_data | ||||
|     file_field_list = [] | ||||
|     for f in opts.fields: | ||||
|  | @ -65,9 +62,28 @@ def save_instance(form, instance, fields=None, fail_message='saved', | |||
|     for f in file_field_list: | ||||
|         f.save_form_data(instance, cleaned_data[f.name]) | ||||
| 
 | ||||
|     return instance | ||||
| 
 | ||||
| def save_instance(form, instance, fields=None, fail_message='saved', | ||||
|                   commit=True, exclude=None, construct=True): | ||||
|     """ | ||||
|     Saves bound Form ``form``'s cleaned_data into model instance ``instance``. | ||||
| 
 | ||||
|     If commit=True, then the changes to ``instance`` will be saved to the | ||||
|     database. Returns ``instance``. | ||||
| 
 | ||||
|     If construct=False, assume ``instance`` has already been constructed and | ||||
|     just needs to be saved. | ||||
|     """ | ||||
|     if construct: | ||||
|         instance = construct_instance(form, instance, fields, exclude) | ||||
|     opts = instance._meta | ||||
|     if form.errors: | ||||
|         raise ValueError("The %s could not be %s because the data didn't" | ||||
|                          " validate." % (opts.object_name, fail_message)) | ||||
| 
 | ||||
|     # Wrap up the saving of m2m data as a function. | ||||
|     def save_m2m(): | ||||
|         opts = instance._meta | ||||
|         cleaned_data = form.cleaned_data | ||||
|         for f in opts.many_to_many: | ||||
|             if fields and f.name not in fields: | ||||
|  | @ -120,7 +136,7 @@ def model_to_dict(instance, fields=None, exclude=None): | |||
|     the ``fields`` argument. | ||||
|     """ | ||||
|     # avoid a circular import | ||||
|     from django.db.models.fields.related import ManyToManyField, OneToOneField | ||||
|     from django.db.models.fields.related import ManyToManyField | ||||
|     opts = instance._meta | ||||
|     data = {} | ||||
|     for f in opts.fields + opts.many_to_many: | ||||
|  | @ -218,8 +234,10 @@ class BaseModelForm(BaseForm): | |||
|             # if we didn't get an instance, instantiate a new one | ||||
|             self.instance = opts.model() | ||||
|             object_data = {} | ||||
|             self.instance._adding = True | ||||
|         else: | ||||
|             self.instance = instance | ||||
|             self.instance._adding = False | ||||
|             object_data = model_to_dict(instance, opts.fields, opts.exclude) | ||||
|         # if initial was provided, it should override the values from instance | ||||
|         if initial is not None: | ||||
|  | @ -228,166 +246,32 @@ class BaseModelForm(BaseForm): | |||
|                                             error_class, label_suffix, empty_permitted) | ||||
| 
 | ||||
|     def clean(self): | ||||
|         self.validate_unique() | ||||
|         opts = self._meta | ||||
|         self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude) | ||||
|         try: | ||||
|             self.instance.full_validate(exclude=self._errors.keys()) | ||||
|         except ValidationError, e: | ||||
|             for k, v in e.message_dict.items(): | ||||
|                 if k != NON_FIELD_ERRORS: | ||||
|                     self._errors.setdefault(k, ErrorList()).extend(v) | ||||
| 
 | ||||
|                     # Remove the data from the cleaned_data dict since it was invalid | ||||
|                     if k in self.cleaned_data: | ||||
|                         del self.cleaned_data[k] | ||||
| 
 | ||||
|             if NON_FIELD_ERRORS in e.message_dict: | ||||
|                 raise ValidationError(e.message_dict[NON_FIELD_ERRORS]) | ||||
| 
 | ||||
|             # If model validation threw errors for fields that aren't on the | ||||
|             # form, the the errors cannot be corrected by the user. Displaying | ||||
|             # those errors would be pointless, so raise another type of | ||||
|             # exception that *won't* be caught and displayed by the form. | ||||
|             if set(e.message_dict.keys()) - set(self.fields.keys() + [NON_FIELD_ERRORS]): | ||||
|                 raise UnresolvableValidationError(e.message_dict) | ||||
| 
 | ||||
| 
 | ||||
|         return self.cleaned_data | ||||
| 
 | ||||
|     def validate_unique(self): | ||||
|         unique_checks, date_checks = self._get_unique_checks() | ||||
|         form_errors = [] | ||||
|         bad_fields = set() | ||||
| 
 | ||||
|         field_errors, global_errors = self._perform_unique_checks(unique_checks) | ||||
|         bad_fields.union(field_errors) | ||||
|         form_errors.extend(global_errors) | ||||
| 
 | ||||
|         field_errors, global_errors = self._perform_date_checks(date_checks) | ||||
|         bad_fields.union(field_errors) | ||||
|         form_errors.extend(global_errors) | ||||
| 
 | ||||
|         for field_name in bad_fields: | ||||
|             del self.cleaned_data[field_name] | ||||
|         if form_errors: | ||||
|             # Raise the unique together errors since they are considered | ||||
|             # form-wide. | ||||
|             raise ValidationError(form_errors) | ||||
| 
 | ||||
|     def _get_unique_checks(self): | ||||
|         from django.db.models.fields import FieldDoesNotExist, Field as ModelField | ||||
| 
 | ||||
|         # Gather a list of checks to perform. We only perform unique checks | ||||
|         # for fields present and not None in cleaned_data.  Since this is a | ||||
|         # ModelForm, some fields may have been excluded; we can't perform a unique | ||||
|         # check on a form that is missing fields involved in that check.  It also does | ||||
|         # not make sense to check data that didn't validate, and since NULL does not | ||||
|         # equal NULL in SQL we should not do any unique checking for NULL values. | ||||
|         unique_checks = [] | ||||
|         # these are checks for the unique_for_<date/year/month> | ||||
|         date_checks = [] | ||||
|         for check in self.instance._meta.unique_together[:]: | ||||
|             fields_on_form = [field for field in check if self.cleaned_data.get(field) is not None] | ||||
|             if len(fields_on_form) == len(check): | ||||
|                 unique_checks.append(check) | ||||
| 
 | ||||
|         # Gather a list of checks for fields declared as unique and add them to | ||||
|         # the list of checks. Again, skip empty fields and any that did not validate. | ||||
|         for name in self.fields: | ||||
|             try: | ||||
|                 f = self.instance._meta.get_field_by_name(name)[0] | ||||
|             except FieldDoesNotExist: | ||||
|                 # This is an extra field that's not on the ModelForm, ignore it | ||||
|                 continue | ||||
|             if not isinstance(f, ModelField): | ||||
|                 # This is an extra field that happens to have a name that matches, | ||||
|                 # for example, a related object accessor for this model.  So | ||||
|                 # get_field_by_name found it, but it is not a Field so do not proceed | ||||
|                 # to use it as if it were. | ||||
|                 continue | ||||
|             if self.cleaned_data.get(name) is None: | ||||
|                 continue | ||||
|             if f.unique: | ||||
|                 unique_checks.append((name,)) | ||||
|             if f.unique_for_date and self.cleaned_data.get(f.unique_for_date) is not None: | ||||
|                 date_checks.append(('date', name, f.unique_for_date)) | ||||
|             if f.unique_for_year and self.cleaned_data.get(f.unique_for_year) is not None: | ||||
|                 date_checks.append(('year', name, f.unique_for_year)) | ||||
|             if f.unique_for_month and self.cleaned_data.get(f.unique_for_month) is not None: | ||||
|                 date_checks.append(('month', name, f.unique_for_month)) | ||||
|         return unique_checks, date_checks | ||||
| 
 | ||||
| 
 | ||||
|     def _perform_unique_checks(self, unique_checks): | ||||
|         bad_fields = set() | ||||
|         form_errors = [] | ||||
| 
 | ||||
|         for unique_check in unique_checks: | ||||
|             # Try to look up an existing object with the same values as this | ||||
|             # object's values for all the unique field. | ||||
| 
 | ||||
|             lookup_kwargs = {} | ||||
|             for field_name in unique_check: | ||||
|                 lookup_value = self.cleaned_data[field_name] | ||||
|                 # ModelChoiceField will return an object instance rather than | ||||
|                 # a raw primary key value, so convert it to a pk value before | ||||
|                 # using it in a lookup. | ||||
|                 if isinstance(self.fields[field_name], ModelChoiceField): | ||||
|                     lookup_value =  lookup_value.pk | ||||
|                 lookup_kwargs[str(field_name)] = lookup_value | ||||
| 
 | ||||
|             qs = self.instance.__class__._default_manager.filter(**lookup_kwargs) | ||||
| 
 | ||||
|             # Exclude the current object from the query if we are editing an | ||||
|             # instance (as opposed to creating a new one) | ||||
|             if self.instance.pk is not None: | ||||
|                 qs = qs.exclude(pk=self.instance.pk) | ||||
| 
 | ||||
|             if qs.exists(): | ||||
|                 if len(unique_check) == 1: | ||||
|                     self._errors[unique_check[0]] = ErrorList([self.unique_error_message(unique_check)]) | ||||
|                 else: | ||||
|                     form_errors.append(self.unique_error_message(unique_check)) | ||||
| 
 | ||||
|                 # Mark these fields as needing to be removed from cleaned data | ||||
|                 # later. | ||||
|                 for field_name in unique_check: | ||||
|                     bad_fields.add(field_name) | ||||
|         return bad_fields, form_errors | ||||
| 
 | ||||
|     def _perform_date_checks(self, date_checks): | ||||
|         bad_fields = set() | ||||
|         for lookup_type, field, unique_for in date_checks: | ||||
|             lookup_kwargs = {} | ||||
|             # there's a ticket to add a date lookup, we can remove this special | ||||
|             # case if that makes it's way in | ||||
|             if lookup_type == 'date': | ||||
|                 date = self.cleaned_data[unique_for] | ||||
|                 lookup_kwargs['%s__day' % unique_for] = date.day | ||||
|                 lookup_kwargs['%s__month' % unique_for] = date.month | ||||
|                 lookup_kwargs['%s__year' % unique_for] = date.year | ||||
|             else: | ||||
|                 lookup_kwargs['%s__%s' % (unique_for, lookup_type)] = getattr(self.cleaned_data[unique_for], lookup_type) | ||||
|             lookup_kwargs[field] = self.cleaned_data[field] | ||||
| 
 | ||||
|             qs = self.instance.__class__._default_manager.filter(**lookup_kwargs) | ||||
|             # Exclude the current object from the query if we are editing an | ||||
|             # instance (as opposed to creating a new one) | ||||
|             if self.instance.pk is not None: | ||||
|                 qs = qs.exclude(pk=self.instance.pk) | ||||
| 
 | ||||
|             if qs.exists(): | ||||
|                 self._errors[field] = ErrorList([ | ||||
|                     self.date_error_message(lookup_type, field, unique_for) | ||||
|                 ]) | ||||
|                 bad_fields.add(field) | ||||
|         return bad_fields, [] | ||||
| 
 | ||||
|     def date_error_message(self, lookup_type, field, unique_for): | ||||
|         return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % { | ||||
|             'field_name': unicode(self.fields[field].label), | ||||
|             'date_field': unicode(self.fields[unique_for].label), | ||||
|             'lookup': lookup_type, | ||||
|         } | ||||
| 
 | ||||
|     def unique_error_message(self, unique_check): | ||||
|         model_name = capfirst(self.instance._meta.verbose_name) | ||||
| 
 | ||||
|         # A unique field | ||||
|         if len(unique_check) == 1: | ||||
|             field_name = unique_check[0] | ||||
|             field_label = self.fields[field_name].label | ||||
|             # Insert the error into the error dict, very sneaky | ||||
|             return _(u"%(model_name)s with this %(field_label)s already exists.") %  { | ||||
|                 'model_name': unicode(model_name), | ||||
|                 'field_label': unicode(field_label) | ||||
|             } | ||||
|         # unique_together | ||||
|         else: | ||||
|             field_labels = [self.fields[field_name].label for field_name in unique_check] | ||||
|             field_labels = get_text_list(field_labels, _('and')) | ||||
|             return _(u"%(model_name)s with this %(field_label)s already exists.") %  { | ||||
|                 'model_name': unicode(model_name), | ||||
|                 'field_label': unicode(field_labels) | ||||
|             } | ||||
| 
 | ||||
|     def save(self, commit=True): | ||||
|         """ | ||||
|         Saves this ``form``'s cleaned_data into model instance | ||||
|  | @ -401,7 +285,7 @@ class BaseModelForm(BaseForm): | |||
|         else: | ||||
|             fail_message = 'changed' | ||||
|         return save_instance(self, self.instance, self._meta.fields, | ||||
|                              fail_message, commit, exclude=self._meta.exclude) | ||||
|                              fail_message, commit, construct=False) | ||||
| 
 | ||||
|     save.alters_data = True | ||||
| 
 | ||||
|  | @ -530,7 +414,7 @@ class BaseModelFormSet(BaseFormSet): | |||
|                 break | ||||
|         else: | ||||
|             return | ||||
|         unique_checks, date_checks = form._get_unique_checks() | ||||
|         unique_checks, date_checks = form.instance._get_unique_checks() | ||||
|         errors = [] | ||||
|         # Do each of the unique checks (unique and unique_together) | ||||
|         for unique_check in unique_checks: | ||||
|  | @ -743,6 +627,9 @@ class BaseInlineFormSet(BaseModelFormSet): | |||
| 
 | ||||
|             # Remove the foreign key from the form's data | ||||
|             form.data[form.add_prefix(self.fk.name)] = None | ||||
| 
 | ||||
|         # Set the fk value here so that the form can do it's validation. | ||||
|         setattr(form.instance, self.fk.get_attname(), self.instance.pk) | ||||
|         return form | ||||
| 
 | ||||
|     #@classmethod | ||||
|  |  | |||
|  | @ -1,7 +1,11 @@ | |||
| from django.utils.html import conditional_escape | ||||
| from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode | ||||
| from django.utils.encoding import StrAndUnicode, force_unicode | ||||
| from django.utils.safestring import mark_safe | ||||
| 
 | ||||
| # Import ValidationError so that it can be imported from this | ||||
| # module to maintain backwards compatibility. | ||||
| from django.core.exceptions import ValidationError | ||||
| 
 | ||||
| def flatatt(attrs): | ||||
|     """ | ||||
|     Convert a dictionary of attributes to a single string. | ||||
|  | @ -48,21 +52,3 @@ class ErrorList(list, StrAndUnicode): | |||
|     def __repr__(self): | ||||
|         return repr([force_unicode(e) for e in self]) | ||||
| 
 | ||||
| class ValidationError(Exception): | ||||
|     def __init__(self, message): | ||||
|         """ | ||||
|         ValidationError can be passed any object that can be printed (usually | ||||
|         a string) or a list of objects. | ||||
|         """ | ||||
|         if isinstance(message, list): | ||||
|             self.messages = ErrorList([smart_unicode(msg) for msg in message]) | ||||
|         else: | ||||
|             message = smart_unicode(message) | ||||
|             self.messages = ErrorList([message]) | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         # This is needed because, without a __str__(), printing an exception | ||||
|         # instance would result in this: | ||||
|         # AttributeError: ValidationError instance has no attribute 'args' | ||||
|         # See http://www.python.org/doc/current/tut/node10.html#handling | ||||
|         return repr(self.messages) | ||||
|  |  | |||
|  | @ -257,6 +257,17 @@ And here is a custom error message:: | |||
| In the `built-in Field classes`_ section below, each ``Field`` defines the | ||||
| error message keys it uses. | ||||
| 
 | ||||
| ``validators`` | ||||
| ~~~~~~~~~~~~~~ | ||||
| .. versionadded:: 1.2 | ||||
| 
 | ||||
| .. attribute:: Field.validators | ||||
| 
 | ||||
| The ``validators`` argument lets you provide a list of validation functions | ||||
| for this field. | ||||
| 
 | ||||
| See the :ref:`validators documentation <validators>` for more information. | ||||
| 
 | ||||
| Built-in ``Field`` classes | ||||
| -------------------------- | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ | |||
| Form and field validation | ||||
| ========================= | ||||
| 
 | ||||
| .. versionchanged:: 1.2 | ||||
| 
 | ||||
| Form validation happens when the data is cleaned. If you want to customize | ||||
| this process, there are various places you can change, each one serving a | ||||
| different purpose. Three types of cleaning methods are run during form | ||||
|  | @ -20,13 +22,38 @@ If you detect multiple errors during a cleaning method and wish to signal all | |||
| of them to the form submitter, it is possible to pass a list of errors to the | ||||
| ``ValidationError`` constructor. | ||||
| 
 | ||||
| The three types of cleaning methods are: | ||||
| Most validation can be done using `validators`_ - simple helpers that can be | ||||
| reused easily. Validators are simple functions (or callables) that take a single | ||||
| argument and raise ``ValidationError`` on invalid input. Validators are run | ||||
| after the field's ``to_python`` and ``validate`` methods have been called. | ||||
| 
 | ||||
|     * The ``clean()`` method on a Field subclass. This is responsible | ||||
|       for cleaning the data in a way that is generic for that type of field. | ||||
|       For example, a FloatField will turn the data into a Python ``float`` or | ||||
|       raise a ``ValidationError``. This method returns the clean data, which | ||||
|       is then inserted into the ``cleaned_data`` dictionary of the form. | ||||
| Validation of a Form is split into several steps, which can be customized or | ||||
| overridden: | ||||
| 
 | ||||
|     * The ``to_python()`` method on a Field is the first step in every | ||||
|       validation. It coerces the value to correct datatype and raises | ||||
|       ``ValidationError`` if that is not possible. This method accepts the raw | ||||
|       value from the widget and returns the converted value. For example, a | ||||
|       FloatField will turn the data into a Python ``float`` or raise a | ||||
|       ``ValidationError``. | ||||
| 
 | ||||
|     * The ``validate()`` method on a Field handles field-specific validation | ||||
|       that is not suitable for a validator, It takes a value that has been | ||||
|       coerced to correct datatype and raises ``ValidationError`` on any error. | ||||
|       This method does not return anything and shouldn't alter the value. You | ||||
|       should override it to handle validation logic that you can't or don't | ||||
|       want to put in a validator. | ||||
| 
 | ||||
|     * The ``run_validators()`` method on a Field runs all of the field's | ||||
|       validators and aggregates all the errors into a single | ||||
|       ``ValidationError``. You shouldn't need to override this method. | ||||
| 
 | ||||
|     * The ``clean()`` method on a Field subclass. This is responsible for | ||||
|       running ``to_python``, ``validate`` and ``run_validators`` in the correct | ||||
|       order and propagating their errors. If, at any time, any of the methods | ||||
|       raise ``ValidationError``, the validation stops and that error is raised. | ||||
|       This method returns the clean data, which is then inserted into the | ||||
|       ``cleaned_data`` dictionary of the form. | ||||
| 
 | ||||
|     * The ``clean_<fieldname>()`` method in a form subclass -- where | ||||
|       ``<fieldname>`` is replaced with the name of the form field attribute. | ||||
|  | @ -141,35 +168,68 @@ Since it can sometimes be easier to put things into place by seeing each | |||
| feature in use, here are a series of small examples that use each of the | ||||
| previous features. | ||||
| 
 | ||||
| .. _validators: | ||||
| 
 | ||||
| Using validators | ||||
| ~~~~~~~~~~~~~~~~ | ||||
| .. versionadded:: 1.2 | ||||
| 
 | ||||
| Django's form (and model) fields support use of simple utility functions and | ||||
| classes known as validators. These can passed to a field's constructor, via | ||||
| the field's ``validators`` argument, or defined on the Field class itself with | ||||
| the ``default_validators`` attribute. | ||||
| 
 | ||||
| Simple validators can be used to validate values inside the field, let's have | ||||
| a look at Django's ``EmailField``:: | ||||
| 
 | ||||
|     class EmailField(CharField): | ||||
|         default_error_messages = { | ||||
|             'invalid': _(u'Enter a valid e-mail address.'), | ||||
|         } | ||||
|         default_validators = [validators.validate_email] | ||||
| 
 | ||||
| As you can see, ``EmailField`` is just a ``CharField`` with customized error | ||||
| message and a validator that validates e-mail addresses. This can also be done | ||||
| on field definition so:: | ||||
| 
 | ||||
|     email = forms.EmailField() | ||||
| 
 | ||||
| is equivalent to:: | ||||
| 
 | ||||
|     email = forms.CharField(validators=[validators.validate_email], | ||||
|             error_messages={'invalid': _(u'Enter a valid e-mail address.')}) | ||||
| 
 | ||||
| 
 | ||||
| Form field default cleaning | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| Let's firstly create a custom form field that validates its input is a string | ||||
| containing comma-separated e-mail addresses, with at least one address. We'll | ||||
| keep it simple and assume e-mail validation is contained in a function called | ||||
| ``is_valid_email()``. The full class looks like this:: | ||||
| containing comma-separated e-mail addresses. The full class looks like this:: | ||||
| 
 | ||||
|     from django import forms | ||||
|     from django.core.validators import validate_email | ||||
| 
 | ||||
|     class MultiEmailField(forms.Field): | ||||
|         def clean(self, value): | ||||
|             """ | ||||
|             Check that the field contains one or more comma-separated emails | ||||
|             and normalizes the data to a list of the email strings. | ||||
|             """ | ||||
|         def to_python(self, value): | ||||
|             "Normalize data to a list of strings." | ||||
| 
 | ||||
|             # Return an empty list if no input was given. | ||||
|             if not value: | ||||
|                 raise forms.ValidationError('Enter at least one e-mail address.') | ||||
|             emails = value.split(',') | ||||
|             for email in emails: | ||||
|                 if not is_valid_email(email): | ||||
|                     raise forms.ValidationError('%s is not a valid e-mail address.' % email) | ||||
|                 return [] | ||||
|             return value.split(',') | ||||
| 
 | ||||
|             # Always return the cleaned data. | ||||
|             return emails | ||||
|         def validate(self, value): | ||||
|             "Check if value consists only of valid emails." | ||||
| 
 | ||||
| Every form that uses this field will have this ``clean()`` method run before | ||||
| anything else can be done with the field's data. This is cleaning that is | ||||
| specific to this type of field, regardless of how it is subsequently used. | ||||
|             # Use the parent's handling of required fields, etc. | ||||
|             super(MultiEmailField, self).validate(value) | ||||
| 
 | ||||
|             for email in value: | ||||
|                 validate_email(email) | ||||
| 
 | ||||
| Every form that uses this field will have these methods run before anything | ||||
| else can be done with the field's data. This is cleaning that is specific to | ||||
| this type of field, regardless of how it is subsequently used. | ||||
| 
 | ||||
| Let's create a simple ``ContactForm`` to demonstrate how you'd use this | ||||
| field:: | ||||
|  | @ -183,7 +243,8 @@ field:: | |||
| 
 | ||||
| Simply use ``MultiEmailField`` like any other form field. When the | ||||
| ``is_valid()`` method is called on the form, the ``MultiEmailField.clean()`` | ||||
| method will be run as part of the cleaning process. | ||||
| method will be run as part of the cleaning process and it will, in turn, call | ||||
| the custom ``to_python()`` and ``validate()`` methods. | ||||
| 
 | ||||
| Cleaning a specific field attribute | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  |  | |||
|  | @ -196,6 +196,17 @@ callable it will be called every time a new object is created. | |||
| If ``False``, the field will not be editable in the admin or via forms | ||||
| automatically generated from the model class. Default is ``True``. | ||||
| 
 | ||||
| ``error_messages`` | ||||
| ------------------ | ||||
| 
 | ||||
| .. versionadded:: 1.2 | ||||
| 
 | ||||
| .. attribute:: Field.error_messages | ||||
| 
 | ||||
| The ``error_messages`` argument lets you override the default messages that the | ||||
| field will raise. Pass in a dictionary with keys matching the error messages you | ||||
| want to override. | ||||
| 
 | ||||
| ``help_text`` | ||||
| ------------- | ||||
| 
 | ||||
|  | @ -284,6 +295,17 @@ underscores to spaces. See :ref:`Verbose field names <verbose-field-names>`. | |||
| 
 | ||||
| .. _model-field-types: | ||||
| 
 | ||||
| ``validators`` | ||||
| ------------------- | ||||
| 
 | ||||
| .. versionadded:: 1.2 | ||||
| 
 | ||||
| .. attribute:: Field.validators | ||||
| 
 | ||||
| A list of validators to run for this field.See the :ref:`validators | ||||
| documentation <validators>` for more information. | ||||
| 
 | ||||
| 
 | ||||
| Field types | ||||
| =========== | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,6 +27,31 @@ The keyword arguments are simply the names of the fields you've defined on your | |||
| model. Note that instantiating a model in no way touches your database; for | ||||
| that, you need to ``save()``. | ||||
| 
 | ||||
| Validating objects | ||||
| ================== | ||||
| 
 | ||||
| .. versionadded:: 1.2 | ||||
| 
 | ||||
| To validate your model, just call its ``full_validate()`` method: | ||||
| 
 | ||||
| .. method:: Model.full_validate([exclude=[]]) | ||||
| 
 | ||||
| The optional ``exclude`` argument can contain a list of field names that should | ||||
| be omitted when validating. This method raises ``ValidationError`` containing a | ||||
| message dict with errors from all fields. | ||||
| 
 | ||||
| To add your own validation logic, override the supplied ``validate()`` method: | ||||
| 
 | ||||
| .. method:: Model.validate() | ||||
| 
 | ||||
| The ``validate()`` method on ``Model`` by default checks for uniqueness of | ||||
| fields and group of fields that are declared to be unique so, remember to call | ||||
| ``self.validate_unique()`` or the superclasses ``validate`` method if you want | ||||
| this validation to run. | ||||
| 
 | ||||
| Any ``ValidationError`` raised in this method will be propagated in the | ||||
| ``message_dict`` under ``NON_FIELD_ERRORS``. | ||||
| 
 | ||||
| Saving objects | ||||
| ============== | ||||
| 
 | ||||
|  |  | |||
|  | @ -68,7 +68,7 @@ class ImprovedArticleWithParentLink(models.Model): | |||
|     article = models.OneToOneField(Article, parent_link=True) | ||||
| 
 | ||||
| class BetterWriter(Writer): | ||||
|     pass | ||||
|     score = models.IntegerField() | ||||
| 
 | ||||
| class WriterProfile(models.Model): | ||||
|     writer = models.OneToOneField(Writer, primary_key=True) | ||||
|  | @ -555,6 +555,8 @@ inserted as 'initial' data in each Field. | |||
| <option value="3">Third test</option> | ||||
| </select>  Hold down "Control", or "Command" on a Mac, to select more than one.</li> | ||||
| >>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}, instance=art) | ||||
| >>> f.errors | ||||
| {} | ||||
| >>> f.is_valid() | ||||
| True | ||||
| >>> test_art = f.save() | ||||
|  | @ -967,10 +969,20 @@ ValidationError: [u'Select a valid choice. 4 is not one of the available choices | |||
| >>> ImprovedArticleWithParentLinkForm.base_fields.keys() | ||||
| [] | ||||
| 
 | ||||
| >>> bw = BetterWriter(name=u'Joe Better') | ||||
| >>> bw = BetterWriter(name=u'Joe Better', score=10) | ||||
| >>> bw.save() | ||||
| >>> sorted(model_to_dict(bw).keys()) | ||||
| ['id', 'name', 'writer_ptr'] | ||||
| ['id', 'name', 'score', 'writer_ptr'] | ||||
| 
 | ||||
| >>> class BetterWriterForm(ModelForm): | ||||
| ...     class Meta: | ||||
| ...         model = BetterWriter | ||||
| >>> form = BetterWriterForm({'name': 'Some Name', 'score': 12}) | ||||
| >>> form.is_valid() | ||||
| True | ||||
| >>> bw2 = form.save() | ||||
| >>> bw2.delete() | ||||
| 
 | ||||
| 
 | ||||
| >>> class WriterProfileForm(ModelForm): | ||||
| ...     class Meta: | ||||
|  | @ -1102,16 +1114,6 @@ True | |||
| 
 | ||||
| >>> instance.delete() | ||||
| 
 | ||||
| # Test the non-required FileField | ||||
| 
 | ||||
| >>> f = TextFileForm(data={'description': u'Assistance'}) | ||||
| >>> f.fields['file'].required = False | ||||
| >>> f.is_valid() | ||||
| True | ||||
| >>> instance = f.save() | ||||
| >>> instance.file | ||||
| <FieldFile: None> | ||||
| 
 | ||||
| >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance) | ||||
| >>> f.is_valid() | ||||
| True | ||||
|  | @ -1358,27 +1360,35 @@ __test__['API_TESTS'] += """ | |||
| ...    class Meta: | ||||
| ...        model = CommaSeparatedInteger | ||||
| 
 | ||||
| >>> f = CommaSeparatedIntegerForm().fields['field'] | ||||
| >>> f.clean('1,2,3') | ||||
| u'1,2,3' | ||||
| >>> f.clean('1a,2') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Enter only digits separated by commas.'] | ||||
| >>> f.clean(',,,,') | ||||
| u',,,,' | ||||
| >>> f.clean('1.2') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Enter only digits separated by commas.'] | ||||
| >>> f.clean('1,a,2') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Enter only digits separated by commas.'] | ||||
| >>> f.clean('1,,2') | ||||
| u'1,,2' | ||||
| >>> f.clean('1') | ||||
| u'1' | ||||
| >>> f = CommaSeparatedIntegerForm({'field': '1,2,3'}) | ||||
| >>> f.is_valid() | ||||
| True | ||||
| >>> f.cleaned_data | ||||
| {'field': u'1,2,3'} | ||||
| >>> f = CommaSeparatedIntegerForm({'field': '1a,2'}) | ||||
| >>> f.errors | ||||
| {'field': [u'Enter only digits separated by commas.']} | ||||
| >>> f = CommaSeparatedIntegerForm({'field': ',,,,'}) | ||||
| >>> f.is_valid() | ||||
| True | ||||
| >>> f.cleaned_data | ||||
| {'field': u',,,,'} | ||||
| >>> f = CommaSeparatedIntegerForm({'field': '1.2'}) | ||||
| >>> f.errors | ||||
| {'field': [u'Enter only digits separated by commas.']} | ||||
| >>> f = CommaSeparatedIntegerForm({'field': '1,a,2'}) | ||||
| >>> f.errors | ||||
| {'field': [u'Enter only digits separated by commas.']} | ||||
| >>> f = CommaSeparatedIntegerForm({'field': '1,,2'}) | ||||
| >>> f.is_valid() | ||||
| True | ||||
| >>> f.cleaned_data | ||||
| {'field': u'1,,2'} | ||||
| >>> f = CommaSeparatedIntegerForm({'field': '1'}) | ||||
| >>> f.is_valid() | ||||
| True | ||||
| >>> f.cleaned_data | ||||
| {'field': u'1'} | ||||
| 
 | ||||
| # unique/unique_together validation | ||||
| 
 | ||||
|  | @ -1415,13 +1425,16 @@ False | |||
| >>> form._errors | ||||
| {'__all__': [u'Price with this Price and Quantity already exists.']} | ||||
| 
 | ||||
| # This form is never valid because quantity is blank=False. | ||||
| >>> class PriceForm(ModelForm): | ||||
| ...     class Meta: | ||||
| ...         model = Price | ||||
| ...         exclude = ('quantity',) | ||||
| >>> form = PriceForm({'price': '6.00'}) | ||||
| >>> form.is_valid() | ||||
| True | ||||
| Traceback (most recent call last): | ||||
|   ... | ||||
| UnresolvableValidationError: {'quantity': [u'This field cannot be null.']} | ||||
| 
 | ||||
| # Unique & unique together with null values | ||||
| >>> class BookForm(ModelForm): | ||||
|  |  | |||
|  | @ -543,10 +543,6 @@ This is used in the admin for save_as functionality. | |||
| ...     'book_set-2-title': '', | ||||
| ... } | ||||
| 
 | ||||
| >>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True) | ||||
| >>> formset.is_valid() | ||||
| True | ||||
| 
 | ||||
| >>> new_author = Author.objects.create(name='Charles Baudelaire') | ||||
| >>> formset = AuthorBooksFormSet(data, instance=new_author, save_as_new=True) | ||||
| >>> [book for book in formset.save() if book.author.pk == new_author.pk] | ||||
|  | @ -1035,19 +1031,6 @@ False | |||
| >>> formset._non_form_errors | ||||
| [u'Please correct the duplicate data for price and quantity, which must be unique.'] | ||||
| 
 | ||||
| # only the price field is specified, this should skip any unique checks since the unique_together is not fulfilled. | ||||
| # this will fail with a KeyError if broken. | ||||
| >>> FormSet = modelformset_factory(Price, fields=("price",), extra=2) | ||||
| >>> data = { | ||||
| ...     'form-TOTAL_FORMS': '2', | ||||
| ...     'form-INITIAL_FORMS': '0', | ||||
| ...     'form-0-price': '24', | ||||
| ...     'form-1-price': '24', | ||||
| ... } | ||||
| >>> formset = FormSet(data) | ||||
| >>> formset.is_valid() | ||||
| True | ||||
| 
 | ||||
| >>> FormSet = inlineformset_factory(Author, Book, extra=0) | ||||
| >>> author = Author.objects.order_by('id')[0] | ||||
| >>> book_ids = author.book_set.values_list('id', flat=True) | ||||
|  |  | |||
							
								
								
									
										21
									
								
								tests/modeltests/validation/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								tests/modeltests/validation/__init__.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| import unittest | ||||
| 
 | ||||
| from django.core.exceptions import ValidationError | ||||
| 
 | ||||
| class ValidationTestCase(unittest.TestCase): | ||||
|     def assertFailsValidation(self, clean, failed_fields): | ||||
|         self.assertRaises(ValidationError, clean) | ||||
|         try: | ||||
|             clean() | ||||
|         except ValidationError, e: | ||||
|             self.assertEquals(sorted(failed_fields), sorted(e.message_dict.keys())) | ||||
|      | ||||
|     def assertFieldFailsValidationWithMessage(self, clean, field_name, message): | ||||
|         self.assertRaises(ValidationError, clean) | ||||
|         try: | ||||
|             clean() | ||||
|         except ValidationError, e: | ||||
|             self.assertTrue(field_name in e.message_dict) | ||||
|             self.assertEquals(message, e.message_dict[field_name]) | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										53
									
								
								tests/modeltests/validation/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								tests/modeltests/validation/models.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| from datetime import datetime | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.db import models | ||||
| from django.test import TestCase | ||||
| 
 | ||||
| 
 | ||||
| def validate_answer_to_universe(value): | ||||
|     if value != 42: | ||||
|         raise ValidationError('This is not the answer to life, universe and everything!', code='not42') | ||||
| 
 | ||||
| class ModelToValidate(models.Model): | ||||
|     name = models.CharField(max_length=100) | ||||
|     created = models.DateTimeField(default=datetime.now) | ||||
|     number = models.IntegerField() | ||||
|     parent = models.ForeignKey('self', blank=True, null=True) | ||||
|     email = models.EmailField(blank=True) | ||||
|     url = models.URLField(blank=True) | ||||
|     f_with_custom_validator = models.IntegerField(blank=True, null=True, validators=[validate_answer_to_universe]) | ||||
| 
 | ||||
|     def validate(self): | ||||
|         super(ModelToValidate, self).validate() | ||||
|         if self.number == 11: | ||||
|             raise ValidationError('Invalid number supplied!') | ||||
| 
 | ||||
| class UniqueFieldsModel(models.Model): | ||||
|     unique_charfield = models.CharField(max_length=100, unique=True) | ||||
|     unique_integerfield = models.IntegerField(unique=True) | ||||
|     non_unique_field = models.IntegerField() | ||||
| 
 | ||||
| class CustomPKModel(models.Model): | ||||
|     my_pk_field = models.CharField(max_length=100, primary_key=True) | ||||
| 
 | ||||
| class UniqueTogetherModel(models.Model): | ||||
|     cfield = models.CharField(max_length=100) | ||||
|     ifield = models.IntegerField() | ||||
|     efield = models.EmailField() | ||||
| 
 | ||||
|     class Meta: | ||||
|         unique_together = (('ifield', 'cfield',),('ifield', 'efield'), ) | ||||
| 
 | ||||
| class UniqueForDateModel(models.Model): | ||||
|     start_date = models.DateField() | ||||
|     end_date = models.DateTimeField() | ||||
|     count = models.IntegerField(unique_for_date="start_date", unique_for_year="end_date") | ||||
|     order = models.IntegerField(unique_for_month="end_date") | ||||
|     name = models.CharField(max_length=100) | ||||
| 
 | ||||
| class CustomMessagesModel(models.Model): | ||||
|     other  = models.IntegerField(blank=True, null=True) | ||||
|     number = models.IntegerField( | ||||
|         error_messages={'null': 'NULL', 'not42': 'AAARGH', 'not_equal': '%s != me'}, | ||||
|         validators=[validate_answer_to_universe] | ||||
|     ) | ||||
							
								
								
									
										13
									
								
								tests/modeltests/validation/test_custom_messages.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								tests/modeltests/validation/test_custom_messages.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| from modeltests.validation import ValidationTestCase | ||||
| from models import CustomMessagesModel | ||||
| 
 | ||||
| 
 | ||||
| class CustomMessagesTest(ValidationTestCase): | ||||
|     def test_custom_simple_validator_message(self): | ||||
|         cmm = CustomMessagesModel(number=12) | ||||
|         self.assertFieldFailsValidationWithMessage(cmm.full_validate, 'number', ['AAARGH']) | ||||
| 
 | ||||
|     def test_custom_null_message(self): | ||||
|         cmm = CustomMessagesModel() | ||||
|         self.assertFieldFailsValidationWithMessage(cmm.full_validate, 'number', ['NULL']) | ||||
| 
 | ||||
							
								
								
									
										58
									
								
								tests/modeltests/validation/test_unique.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								tests/modeltests/validation/test_unique.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | |||
| import unittest | ||||
| from django.conf import settings | ||||
| from django.db import connection | ||||
| from models import CustomPKModel, UniqueTogetherModel, UniqueFieldsModel, UniqueForDateModel, ModelToValidate | ||||
| 
 | ||||
| 
 | ||||
| class GetUniqueCheckTests(unittest.TestCase): | ||||
|     def test_unique_fields_get_collected(self): | ||||
|         m = UniqueFieldsModel() | ||||
|         self.assertEqual( | ||||
|             ([('id',), ('unique_charfield',), ('unique_integerfield',)], []), | ||||
|             m._get_unique_checks() | ||||
|         ) | ||||
| 
 | ||||
|     def test_unique_together_gets_picked_up(self): | ||||
|         m = UniqueTogetherModel() | ||||
|         self.assertEqual( | ||||
|             ([('ifield', 'cfield',),('ifield', 'efield'), ('id',), ], []), | ||||
|             m._get_unique_checks() | ||||
|         ) | ||||
| 
 | ||||
|     def test_primary_key_is_considered_unique(self): | ||||
|         m = CustomPKModel() | ||||
|         self.assertEqual(([('my_pk_field',)], []), m._get_unique_checks()) | ||||
| 
 | ||||
|     def test_unique_for_date_gets_picked_up(self): | ||||
|         m = UniqueForDateModel() | ||||
|         self.assertEqual(( | ||||
|                 [('id',)], | ||||
|                 [('date', 'count', 'start_date'), ('year', 'count', 'end_date'), ('month', 'order', 'end_date')] | ||||
|             ), m._get_unique_checks() | ||||
|         ) | ||||
| 
 | ||||
| class PerformUniqueChecksTest(unittest.TestCase): | ||||
|     def setUp(self): | ||||
|         # Set debug to True to gain access to connection.queries. | ||||
|         self._old_debug, settings.DEBUG = settings.DEBUG, True | ||||
|         super(PerformUniqueChecksTest, self).setUp() | ||||
| 
 | ||||
|     def tearDown(self): | ||||
|         # Restore old debug value. | ||||
|         settings.DEBUG = self._old_debug | ||||
|         super(PerformUniqueChecksTest, self).tearDown() | ||||
| 
 | ||||
|     def test_primary_key_unique_check_performed_when_adding(self): | ||||
|         """Regression test for #12132""" | ||||
|         l = len(connection.queries) | ||||
|         mtv = ModelToValidate(number=10, name='Some Name') | ||||
|         setattr(mtv, '_adding', True) | ||||
|         mtv.full_validate() | ||||
|         self.assertEqual(l+1, len(connection.queries)) | ||||
| 
 | ||||
|     def test_primary_key_unique_check_not_performed_when_not_adding(self): | ||||
|         """Regression test for #12132""" | ||||
|         l = len(connection.queries) | ||||
|         mtv = ModelToValidate(number=10, name='Some Name') | ||||
|         mtv.full_validate() | ||||
|         self.assertEqual(l, len(connection.queries)) | ||||
							
								
								
									
										58
									
								
								tests/modeltests/validation/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								tests/modeltests/validation/tests.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | |||
| from django.core.exceptions import ValidationError, NON_FIELD_ERRORS | ||||
| from django.db import models | ||||
| 
 | ||||
| from modeltests.validation import ValidationTestCase | ||||
| from models import * | ||||
| 
 | ||||
| from validators import TestModelsWithValidators | ||||
| from test_unique import GetUniqueCheckTests, PerformUniqueChecksTest | ||||
| from test_custom_messages import CustomMessagesTest | ||||
| 
 | ||||
| 
 | ||||
| class BaseModelValidationTests(ValidationTestCase): | ||||
| 
 | ||||
|     def test_missing_required_field_raises_error(self): | ||||
|         mtv = ModelToValidate(f_with_custom_validator=42) | ||||
|         self.assertFailsValidation(mtv.full_validate, ['name', 'number']) | ||||
| 
 | ||||
|     def test_with_correct_value_model_validates(self): | ||||
|         mtv = ModelToValidate(number=10, name='Some Name') | ||||
|         self.assertEqual(None, mtv.full_validate()) | ||||
| 
 | ||||
|     def test_custom_validate_method_is_called(self): | ||||
|         mtv = ModelToValidate(number=11) | ||||
|         self.assertFailsValidation(mtv.full_validate, [NON_FIELD_ERRORS, 'name']) | ||||
| 
 | ||||
|     def test_wrong_FK_value_raises_error(self): | ||||
|         mtv=ModelToValidate(number=10, name='Some Name', parent_id=3) | ||||
|         self.assertFailsValidation(mtv.full_validate, ['parent']) | ||||
| 
 | ||||
|     def test_correct_FK_value_validates(self): | ||||
|         parent = ModelToValidate.objects.create(number=10, name='Some Name') | ||||
|         mtv=ModelToValidate(number=10, name='Some Name', parent_id=parent.pk) | ||||
|         self.assertEqual(None, mtv.full_validate()) | ||||
| 
 | ||||
|     def test_wrong_email_value_raises_error(self): | ||||
|         mtv = ModelToValidate(number=10, name='Some Name', email='not-an-email') | ||||
|         self.assertFailsValidation(mtv.full_validate, ['email']) | ||||
| 
 | ||||
|     def test_correct_email_value_passes(self): | ||||
|         mtv = ModelToValidate(number=10, name='Some Name', email='valid@email.com') | ||||
|         self.assertEqual(None, mtv.full_validate()) | ||||
| 
 | ||||
|     def test_wrong_url_value_raises_error(self): | ||||
|         mtv = ModelToValidate(number=10, name='Some Name', url='not a url') | ||||
|         self.assertFieldFailsValidationWithMessage(mtv.full_validate, 'url', [u'Enter a valid value.']) | ||||
| 
 | ||||
|     def test_correct_url_but_nonexisting_gives_404(self): | ||||
|         mtv = ModelToValidate(number=10, name='Some Name', url='http://google.com/we-love-microsoft.html') | ||||
|         self.assertFieldFailsValidationWithMessage(mtv.full_validate, 'url', [u'This URL appears to be a broken link.']) | ||||
| 
 | ||||
|     def test_correct_url_value_passes(self): | ||||
|         mtv = ModelToValidate(number=10, name='Some Name', url='http://www.djangoproject.com/') | ||||
|         self.assertEqual(None, mtv.full_validate()) # This will fail if there's no Internet connection | ||||
| 
 | ||||
|     def test_text_greater_that_charfields_max_length_eaises_erros(self): | ||||
|         mtv = ModelToValidate(number=10, name='Some Name'*100) | ||||
|         self.assertFailsValidation(mtv.full_validate, ['name',]) | ||||
| 
 | ||||
							
								
								
									
										18
									
								
								tests/modeltests/validation/validators.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								tests/modeltests/validation/validators.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| from unittest import TestCase | ||||
| from modeltests.validation import ValidationTestCase | ||||
| from models import * | ||||
| 
 | ||||
| 
 | ||||
| class TestModelsWithValidators(ValidationTestCase): | ||||
|     def test_custom_validator_passes_for_correct_value(self): | ||||
|         mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=42) | ||||
|         self.assertEqual(None, mtv.full_validate()) | ||||
| 
 | ||||
|     def test_custom_validator_raises_error_for_incorrect_value(self): | ||||
|         mtv = ModelToValidate(number=10, name='Some Name', f_with_custom_validator=12) | ||||
|         self.assertFailsValidation(mtv.full_validate, ['f_with_custom_validator']) | ||||
|         self.assertFieldFailsValidationWithMessage( | ||||
|             mtv.full_validate, | ||||
|             'f_with_custom_validator', | ||||
|             [u'This is not the answer to life, universe and everything!'] | ||||
|         ) | ||||
							
								
								
									
										146
									
								
								tests/modeltests/validators/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								tests/modeltests/validators/tests.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,146 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| import re | ||||
| import types | ||||
| from unittest import TestCase | ||||
| from datetime import datetime, timedelta | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.core.validators import * | ||||
| 
 | ||||
| NOW = datetime.now() | ||||
| 
 | ||||
| TEST_DATA = ( | ||||
|     # (validator, value, expected), | ||||
|     (validate_integer, '42', None), | ||||
|     (validate_integer, '-42', None), | ||||
|     (validate_integer, -42, None), | ||||
|     (validate_integer, -42.5, None), | ||||
| 
 | ||||
|     (validate_integer, None, ValidationError), | ||||
|     (validate_integer, 'a', ValidationError), | ||||
| 
 | ||||
|     (validate_email, 'email@here.com', None), | ||||
|     (validate_email, 'weirder-email@here.and.there.com', None), | ||||
| 
 | ||||
|     (validate_email, None, ValidationError), | ||||
|     (validate_email, '', ValidationError), | ||||
|     (validate_email, 'abc', ValidationError), | ||||
|     (validate_email, 'a @x.cz', ValidationError), | ||||
|     (validate_email, 'something@@somewhere.com', ValidationError), | ||||
| 
 | ||||
|     (validate_slug, 'slug-ok', None), | ||||
|     (validate_slug, 'longer-slug-still-ok', None), | ||||
|     (validate_slug, '--------', None), | ||||
|     (validate_slug, 'nohyphensoranything', None), | ||||
| 
 | ||||
|     (validate_slug, '', ValidationError), | ||||
|     (validate_slug, ' text ', ValidationError), | ||||
|     (validate_slug, ' ', ValidationError), | ||||
|     (validate_slug, 'some@mail.com', ValidationError), | ||||
|     (validate_slug, '你好', ValidationError), | ||||
|     (validate_slug, '\n', ValidationError), | ||||
| 
 | ||||
|     (validate_ipv4_address, '1.1.1.1', None), | ||||
|     (validate_ipv4_address, '255.0.0.0', None), | ||||
|     (validate_ipv4_address, '0.0.0.0', None), | ||||
| 
 | ||||
|     (validate_ipv4_address, '256.1.1.1', ValidationError), | ||||
|     (validate_ipv4_address, '25.1.1.', ValidationError), | ||||
|     (validate_ipv4_address, '25,1,1,1', ValidationError), | ||||
|     (validate_ipv4_address, '25.1 .1.1', ValidationError), | ||||
| 
 | ||||
|     (validate_comma_separated_integer_list, '1', None), | ||||
|     (validate_comma_separated_integer_list, '1,2,3', None), | ||||
|     (validate_comma_separated_integer_list, '1,2,3,', None), | ||||
| 
 | ||||
|     (validate_comma_separated_integer_list, '', ValidationError), | ||||
|     (validate_comma_separated_integer_list, 'a,b,c', ValidationError), | ||||
|     (validate_comma_separated_integer_list, '1, 2, 3', ValidationError), | ||||
| 
 | ||||
|     (MaxValueValidator(10), 10, None), | ||||
|     (MaxValueValidator(10), -10, None), | ||||
|     (MaxValueValidator(10), 0, None), | ||||
|     (MaxValueValidator(NOW), NOW, None), | ||||
|     (MaxValueValidator(NOW), NOW - timedelta(days=1), None), | ||||
| 
 | ||||
|     (MaxValueValidator(0), 1, ValidationError), | ||||
|     (MaxValueValidator(NOW), NOW + timedelta(days=1), ValidationError), | ||||
| 
 | ||||
|     (MinValueValidator(-10), -10, None), | ||||
|     (MinValueValidator(-10), 10, None), | ||||
|     (MinValueValidator(-10), 0, None), | ||||
|     (MinValueValidator(NOW), NOW, None), | ||||
|     (MinValueValidator(NOW), NOW + timedelta(days=1), None), | ||||
| 
 | ||||
|     (MinValueValidator(0), -1, ValidationError), | ||||
|     (MinValueValidator(NOW), NOW - timedelta(days=1), ValidationError), | ||||
| 
 | ||||
|     (MaxLengthValidator(10), '', None), | ||||
|     (MaxLengthValidator(10), 10*'x', None), | ||||
| 
 | ||||
|     (MaxLengthValidator(10), 15*'x', ValidationError), | ||||
| 
 | ||||
|     (MinLengthValidator(10), 15*'x', None), | ||||
|     (MinLengthValidator(10), 10*'x', None), | ||||
| 
 | ||||
|     (MinLengthValidator(10), '', ValidationError), | ||||
| 
 | ||||
|     (URLValidator(), 'http://www.djangoproject.com/', None), | ||||
|     (URLValidator(), 'http://localhost/', None), | ||||
|     (URLValidator(), 'http://example.com/', None), | ||||
|     (URLValidator(), 'http://www.example.com/', None), | ||||
|     (URLValidator(), 'http://www.example.com:8000/test', None), | ||||
|     (URLValidator(), 'http://valid-with-hyphens.com/', None), | ||||
|     (URLValidator(), 'http://subdomain.domain.com/', None), | ||||
|     (URLValidator(), 'http://200.8.9.10/', None), | ||||
|     (URLValidator(), 'http://200.8.9.10:8000/test', None), | ||||
|     (URLValidator(), 'http://valid-----hyphens.com/', None), | ||||
|     (URLValidator(), 'http://example.com?something=value', None), | ||||
|     (URLValidator(), 'http://example.com/index.php?something=value&another=value2', None), | ||||
| 
 | ||||
|     (URLValidator(), 'foo', ValidationError), | ||||
|     (URLValidator(), 'http://', ValidationError), | ||||
|     (URLValidator(), 'http://example', ValidationError), | ||||
|     (URLValidator(), 'http://example.', ValidationError), | ||||
|     (URLValidator(), 'http://.com', ValidationError), | ||||
|     (URLValidator(), 'http://invalid-.com', ValidationError), | ||||
|     (URLValidator(), 'http://-invalid.com', ValidationError), | ||||
|     (URLValidator(), 'http://inv-.alid-.com', ValidationError), | ||||
|     (URLValidator(), 'http://inv-.-alid.com', ValidationError), | ||||
| 
 | ||||
|     (BaseValidator(True), True, None), | ||||
|     (BaseValidator(True), False, ValidationError), | ||||
| 
 | ||||
|     (RegexValidator('.*'), '', None), | ||||
|     (RegexValidator(re.compile('.*')), '', None), | ||||
|     (RegexValidator('.*'), 'xxxxx', None), | ||||
| 
 | ||||
|     (RegexValidator('x'), 'y', ValidationError), | ||||
|     (RegexValidator(re.compile('x')), 'y', ValidationError), | ||||
| ) | ||||
| 
 | ||||
| def create_simple_test_method(validator, expected, value, num): | ||||
|     if isinstance(expected, type) and issubclass(expected, Exception): | ||||
|         test_mask = 'test_%s_raises_error_%d' | ||||
|         def test_func(self): | ||||
|             self.assertRaises(expected, validator, value) | ||||
|     else: | ||||
|         test_mask = 'test_%s_%d' | ||||
|         def test_func(self): | ||||
|             self.assertEqual(expected, validator(value)) | ||||
|     if isinstance(validator, types.FunctionType): | ||||
|         val_name = validator.__name__ | ||||
|     else: | ||||
|         val_name = validator.__class__.__name__ | ||||
|     test_name = test_mask % (val_name, num) | ||||
|     return test_name, test_func | ||||
| 
 | ||||
| # Dynamically assemble a test class with the contents of TEST_DATA | ||||
| 
 | ||||
| class TestSimpleValidators(TestCase): | ||||
|     pass | ||||
| 
 | ||||
| test_counter = 0 | ||||
| for validator, value, expected in TEST_DATA: | ||||
|     name, method = create_simple_test_method(validator, expected, value, test_counter) | ||||
|     setattr(TestSimpleValidators, name, method) | ||||
|     test_counter += 1 | ||||
|  | @ -6,8 +6,8 @@ tests = r""" | |||
| # CharField ################################################################### | ||||
| 
 | ||||
| >>> e = {'required': 'REQUIRED'} | ||||
| >>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s' | ||||
| >>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s' | ||||
| >>> e['min_length'] = 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s' | ||||
| >>> e['max_length'] = 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s' | ||||
| >>> f = CharField(min_length=5, max_length=10, error_messages=e) | ||||
| >>> f.clean('') | ||||
| Traceback (most recent call last): | ||||
|  | @ -26,8 +26,8 @@ ValidationError: [u'LENGTH 11, MAX LENGTH 10'] | |||
| 
 | ||||
| >>> e = {'required': 'REQUIRED'} | ||||
| >>> e['invalid'] = 'INVALID' | ||||
| >>> e['min_value'] = 'MIN VALUE IS %s' | ||||
| >>> e['max_value'] = 'MAX VALUE IS %s' | ||||
| >>> e['min_value'] = 'MIN VALUE IS %(limit_value)s' | ||||
| >>> e['max_value'] = 'MAX VALUE IS %(limit_value)s' | ||||
| >>> f = IntegerField(min_value=5, max_value=10, error_messages=e) | ||||
| >>> f.clean('') | ||||
| Traceback (most recent call last): | ||||
|  | @ -50,8 +50,8 @@ ValidationError: [u'MAX VALUE IS 10'] | |||
| 
 | ||||
| >>> e = {'required': 'REQUIRED'} | ||||
| >>> e['invalid'] = 'INVALID' | ||||
| >>> e['min_value'] = 'MIN VALUE IS %s' | ||||
| >>> e['max_value'] = 'MAX VALUE IS %s' | ||||
| >>> e['min_value'] = 'MIN VALUE IS %(limit_value)s' | ||||
| >>> e['max_value'] = 'MAX VALUE IS %(limit_value)s' | ||||
| >>> f = FloatField(min_value=5, max_value=10, error_messages=e) | ||||
| >>> f.clean('') | ||||
| Traceback (most recent call last): | ||||
|  | @ -74,8 +74,8 @@ ValidationError: [u'MAX VALUE IS 10'] | |||
| 
 | ||||
| >>> e = {'required': 'REQUIRED'} | ||||
| >>> e['invalid'] = 'INVALID' | ||||
| >>> e['min_value'] = 'MIN VALUE IS %s' | ||||
| >>> e['max_value'] = 'MAX VALUE IS %s' | ||||
| >>> e['min_value'] = 'MIN VALUE IS %(limit_value)s' | ||||
| >>> e['max_value'] = 'MAX VALUE IS %(limit_value)s' | ||||
| >>> e['max_digits'] = 'MAX DIGITS IS %s' | ||||
| >>> e['max_decimal_places'] = 'MAX DP IS %s' | ||||
| >>> e['max_whole_digits'] = 'MAX DIGITS BEFORE DP IS %s' | ||||
|  | @ -156,8 +156,8 @@ ValidationError: [u'INVALID'] | |||
| 
 | ||||
| >>> e = {'required': 'REQUIRED'} | ||||
| >>> e['invalid'] = 'INVALID' | ||||
| >>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s' | ||||
| >>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s' | ||||
| >>> e['min_length'] = 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s' | ||||
| >>> e['max_length'] = 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s' | ||||
| >>> f = RegexField(r'^\d+$', min_length=5, max_length=10, error_messages=e) | ||||
| >>> f.clean('') | ||||
| Traceback (most recent call last): | ||||
|  | @ -180,8 +180,8 @@ ValidationError: [u'LENGTH 11, MAX LENGTH 10'] | |||
| 
 | ||||
| >>> e = {'required': 'REQUIRED'} | ||||
| >>> e['invalid'] = 'INVALID' | ||||
| >>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s' | ||||
| >>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s' | ||||
| >>> e['min_length'] = 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s' | ||||
| >>> e['max_length'] = 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s' | ||||
| >>> f = EmailField(min_length=8, max_length=10, error_messages=e) | ||||
| >>> f.clean('') | ||||
| Traceback (most recent call last): | ||||
|  |  | |||
|  | @ -386,7 +386,7 @@ class FieldsTests(TestCase): | |||
|     def test_regexfield_31(self): | ||||
|         f = RegexField('^\d+$', min_length=5, max_length=10) | ||||
|         self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).']", f.clean, '123') | ||||
|         self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).']", f.clean, 'abc') | ||||
|         self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at least 5 characters (it has 3).', u'Enter a valid value.']", f.clean, 'abc') | ||||
|         self.assertEqual(u'12345', f.clean('12345')) | ||||
|         self.assertEqual(u'1234567890', f.clean('1234567890')) | ||||
|         self.assertRaisesErrorWithMessage(ValidationError, "[u'Ensure this value has at most 10 characters (it has 11).']", f.clean, '12345678901') | ||||
|  | @ -548,6 +548,10 @@ class FieldsTests(TestCase): | |||
|         self.assertEqual(u'http://example.com/', f.clean('http://example.com')) | ||||
|         self.assertEqual(u'http://example.com/test', f.clean('http://example.com/test')) | ||||
| 
 | ||||
|     def test_urlfield_ticket11826(self): | ||||
|         f = URLField() | ||||
|         self.assertEqual(u'http://example.com/?some_param=some_value', f.clean('http://example.com?some_param=some_value')) | ||||
| 
 | ||||
|     # BooleanField ################################################################ | ||||
| 
 | ||||
|     def test_booleanfield_44(self): | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ u'C1064AAB' | |||
| >>> f.clean('C1064AABB') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Ensure this value has at most 8 characters (it has 9).'] | ||||
| ValidationError: [u'Ensure this value has at most 8 characters (it has 9).', u'Enter a postal code in the format NNNN or ANNNNAAA.'] | ||||
| >>> f.clean('C1064AA') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
|  | @ -44,7 +44,7 @@ ValidationError: [u'Enter a postal code in the format NNNN or ANNNNAAA.'] | |||
| >>> f.clean('500') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Ensure this value has at least 4 characters (it has 3).'] | ||||
| ValidationError: [u'Ensure this value has at least 4 characters (it has 3).', u'Enter a postal code in the format NNNN or ANNNNAAA.'] | ||||
| >>> f.clean('5PPP') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
|  | @ -78,7 +78,7 @@ u'C1064AAB' | |||
| >>> f.clean('C1064AABB') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Ensure this value has at most 8 characters (it has 9).'] | ||||
| ValidationError: [u'Ensure this value has at most 8 characters (it has 9).', u'Enter a postal code in the format NNNN or ANNNNAAA.'] | ||||
| >>> f.clean('C1064AA') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
|  | @ -94,7 +94,7 @@ ValidationError: [u'Enter a postal code in the format NNNN or ANNNNAAA.'] | |||
| >>> f.clean('500') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Ensure this value has at least 4 characters (it has 3).'] | ||||
| ValidationError: [u'Ensure this value has at least 4 characters (it has 3).', u'Enter a postal code in the format NNNN or ANNNNAAA.'] | ||||
| >>> f.clean('5PPP') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
|  |  | |||
|  | @ -15,11 +15,11 @@ u'230880-3449' | |||
| >>> f.clean('230880343') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Ensure this value has at least 10 characters (it has 9).'] | ||||
| ValidationError: [u'Ensure this value has at least 10 characters (it has 9).', u'Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.'] | ||||
| >>> f.clean('230880343234') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Ensure this value has at most 11 characters (it has 12).'] | ||||
| ValidationError: [u'Ensure this value has at most 11 characters (it has 12).', u'Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.'] | ||||
| >>> f.clean('abcdefghijk') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
|  | @ -61,18 +61,18 @@ ValidationError: [u'Enter a valid value.'] | |||
| >>> f.clean('123456') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Ensure this value has at least 7 characters (it has 6).'] | ||||
| ValidationError: [u'Ensure this value has at least 7 characters (it has 6).', u'Enter a valid value.'] | ||||
| >>> f.clean('123456555') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
| ValidationError: [u'Ensure this value has at most 8 characters (it has 9).'] | ||||
| ValidationError: [u'Ensure this value has at most 8 characters (it has 9).', u'Enter a valid value.'] | ||||
| >>> 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 (it has 9).'] | ||||
| ValidationError: [u'Ensure this value has at most 8 characters (it has 9).', u'Enter a valid value.'] | ||||
| >>> f.clean(' 12367  ') | ||||
| Traceback (most recent call last): | ||||
| ... | ||||
|  |  | |||
|  | @ -38,6 +38,7 @@ from formsets import tests as formset_tests | |||
| from media import media_tests | ||||
| 
 | ||||
| from fields import FieldsTests | ||||
| from validators import TestFieldWithValidators | ||||
| 
 | ||||
| __test__ = { | ||||
|     'extra_tests': extra_tests, | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ Tests for forms/util.py module. | |||
| 
 | ||||
| tests = r""" | ||||
| >>> from django.forms.util import * | ||||
| >>> from django.core.exceptions import ValidationError | ||||
| >>> from django.utils.translation import ugettext_lazy | ||||
| 
 | ||||
| ########### | ||||
|  | @ -24,36 +25,36 @@ u'' | |||
| ################### | ||||
| 
 | ||||
| # Can take a string. | ||||
| >>> print ValidationError("There was an error.").messages | ||||
| >>> print ErrorList(ValidationError("There was an error.").messages) | ||||
| <ul class="errorlist"><li>There was an error.</li></ul> | ||||
| 
 | ||||
| # Can take a unicode string. | ||||
| >>> print ValidationError(u"Not \u03C0.").messages | ||||
| >>> print ErrorList(ValidationError(u"Not \u03C0.").messages) | ||||
| <ul class="errorlist"><li>Not π.</li></ul> | ||||
| 
 | ||||
| # Can take a lazy string. | ||||
| >>> print ValidationError(ugettext_lazy("Error.")).messages | ||||
| >>> print ErrorList(ValidationError(ugettext_lazy("Error.")).messages) | ||||
| <ul class="errorlist"><li>Error.</li></ul> | ||||
| 
 | ||||
| # Can take a list. | ||||
| >>> print ValidationError(["Error one.", "Error two."]).messages | ||||
| >>> print ErrorList(ValidationError(["Error one.", "Error two."]).messages) | ||||
| <ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul> | ||||
| 
 | ||||
| # Can take a mixture in a list. | ||||
| >>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages | ||||
| >>> print ErrorList(ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages) | ||||
| <ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul> | ||||
| 
 | ||||
| >>> class VeryBadError: | ||||
| ...     def __unicode__(self): return u"A very bad error." | ||||
| 
 | ||||
| # Can take a non-string. | ||||
| >>> print ValidationError(VeryBadError()).messages | ||||
| >>> print ErrorList(ValidationError(VeryBadError()).messages) | ||||
| <ul class="errorlist"><li>A very bad error.</li></ul> | ||||
| 
 | ||||
| # Escapes non-safe input but not input marked safe. | ||||
| >>> example = 'Example of link: <a href="http://www.example.com/">example</a>' | ||||
| >>> print ValidationError(example).messages | ||||
| >>> print ErrorList([example]) | ||||
| <ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul> | ||||
| >>> print ValidationError(mark_safe(example)).messages | ||||
| >>> print ErrorList([mark_safe(example)]) | ||||
| <ul class="errorlist"><li>Example of link: <a href="http://www.example.com/">example</a></li></ul> | ||||
| """ | ||||
|  |  | |||
							
								
								
									
										17
									
								
								tests/regressiontests/forms/validators.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								tests/regressiontests/forms/validators.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| from unittest import TestCase | ||||
| 
 | ||||
| from django import forms | ||||
| from django.core import validators | ||||
| from django.core.exceptions import ValidationError | ||||
| 
 | ||||
| 
 | ||||
| class TestFieldWithValidators(TestCase): | ||||
|     def test_all_errors_get_reported(self): | ||||
|         field = forms.CharField( | ||||
|             validators=[validators.validate_integer, validators.validate_email] | ||||
|         ) | ||||
|         self.assertRaises(ValidationError, field.clean, 'not int nor mail') | ||||
|         try: | ||||
|             field.clean('not int nor mail') | ||||
|         except ValidationError, e: | ||||
|             self.assertEqual(2, len(e.messages)) | ||||
|  | @ -81,7 +81,7 @@ class DeletionTests(TestCase): | |||
|         regression for #10750 | ||||
|         """ | ||||
|         # exclude some required field from the forms | ||||
|         ChildFormSet = inlineformset_factory(School, Child, exclude=['father', 'mother']) | ||||
|         ChildFormSet = inlineformset_factory(School, Child) | ||||
|         school = School.objects.create(name=u'test') | ||||
|         mother = Parent.objects.create(name=u'mother') | ||||
|         father = Parent.objects.create(name=u'father') | ||||
|  | @ -89,13 +89,13 @@ class DeletionTests(TestCase): | |||
|             'child_set-TOTAL_FORMS': u'1', | ||||
|             'child_set-INITIAL_FORMS': u'0', | ||||
|             'child_set-0-name': u'child', | ||||
|             'child_set-0-mother': unicode(mother.pk), | ||||
|             'child_set-0-father': unicode(father.pk), | ||||
|         } | ||||
|         formset = ChildFormSet(data, instance=school) | ||||
|         self.assertEqual(formset.is_valid(), True) | ||||
|         objects = formset.save(commit=False) | ||||
|         for obj in objects: | ||||
|             obj.mother = mother | ||||
|             obj.father = father | ||||
|             obj.save() | ||||
|         self.assertEqual(school.child_set.count(), 0) | ||||
|         objects[0].save() | ||||
|         self.assertEqual(school.child_set.count(), 1) | ||||
| 
 | ||||
|  |  | |||
|  | @ -147,6 +147,58 @@ class SlugFieldTests(django.test.TestCase): | |||
|         bs = BigS.objects.get(pk=bs.pk) | ||||
|         self.assertEqual(bs.s, 'slug'*50) | ||||
| 
 | ||||
| 
 | ||||
| class ValidationTest(django.test.TestCase): | ||||
|     def test_charfield_raises_error_on_empty_string(self): | ||||
|         f = models.CharField() | ||||
|         self.assertRaises(ValidationError, f.clean, "", None) | ||||
| 
 | ||||
|     def test_charfield_cleans_empty_string_when_blank_true(self): | ||||
|         f = models.CharField(blank=True) | ||||
|         self.assertEqual('', f.clean('', None)) | ||||
| 
 | ||||
|     def test_integerfield_cleans_valid_string(self): | ||||
|         f = models.IntegerField() | ||||
|         self.assertEqual(2, f.clean('2', None)) | ||||
| 
 | ||||
|     def test_integerfield_raises_error_on_invalid_intput(self): | ||||
|         f = models.IntegerField() | ||||
|         self.assertRaises(ValidationError, f.clean, "a", None) | ||||
| 
 | ||||
|     def test_charfield_with_choices_cleans_valid_choice(self): | ||||
|         f = models.CharField(max_length=1, choices=[('a','A'), ('b','B')]) | ||||
|         self.assertEqual('a', f.clean('a', None)) | ||||
| 
 | ||||
|     def test_charfield_with_choices_raises_error_on_invalid_choice(self): | ||||
|         f = models.CharField(choices=[('a','A'), ('b','B')]) | ||||
|         self.assertRaises(ValidationError, f.clean, "not a", None) | ||||
| 
 | ||||
|     def test_nullable_integerfield_raises_error_with_blank_false(self): | ||||
|         f = models.IntegerField(null=True, blank=False) | ||||
|         self.assertRaises(ValidationError, f.clean, None, None) | ||||
| 
 | ||||
|     def test_nullable_integerfield_cleans_none_on_null_and_blank_true(self): | ||||
|         f = models.IntegerField(null=True, blank=True) | ||||
|         self.assertEqual(None, f.clean(None, None)) | ||||
| 
 | ||||
|     def test_integerfield_raises_error_on_empty_input(self): | ||||
|         f = models.IntegerField(null=False) | ||||
|         self.assertRaises(ValidationError, f.clean, None, None) | ||||
|         self.assertRaises(ValidationError, f.clean, '', None) | ||||
| 
 | ||||
|     def test_charfield_raises_error_on_empty_input(self): | ||||
|         f = models.CharField(null=False) | ||||
|         self.assertRaises(ValidationError, f.clean, None, None) | ||||
| 
 | ||||
|     def test_datefield_cleans_date(self): | ||||
|         f = models.DateField() | ||||
|         self.assertEqual(datetime.date(2008, 10, 10), f.clean('2008-10-10', None)) | ||||
| 
 | ||||
|     def test_boolean_field_doesnt_accept_empty_input(self): | ||||
|         f = models.BooleanField() | ||||
|         self.assertRaises(ValidationError, f.clean, None, None) | ||||
| 
 | ||||
| 
 | ||||
| class BigIntegerFieldTests(django.test.TestCase): | ||||
|     def test_limits(self): | ||||
|         # Ensure that values that are right at the limits can be saved | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ def custom_create(request): | |||
|             model = Article | ||||
| 
 | ||||
|         def save(self, *args, **kwargs): | ||||
|             self.cleaned_data['slug'] = 'some-other-slug' | ||||
|             self.instance.slug = 'some-other-slug' | ||||
|             return super(SlugChangingArticleForm, self).save(*args, **kwargs) | ||||
| 
 | ||||
|     return create_object(request, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user