Slide 1

Slide 1 text

DJANGO VALIDATION

Slide 2

Slide 2 text

LOÏC BISTUER ‣ @loic84 on IRC and Twitter ‣ I work at the World Food Programme ‣ Django core developer since 2014 ‣ Mostly contribute to Forms and ORM

Slide 3

Slide 3 text

MAIN CONCERNS WITH VALIDATION

Slide 4

Slide 4 text

MAIN CONCERNS WITH VALIDATION 1. Enforcement

Slide 5

Slide 5 text

MAIN CONCERNS WITH VALIDATION 1. Enforcement 2. User Experience

Slide 6

Slide 6 text

MAIN CONCERNS WITH VALIDATION 1. Enforcement 2. User Experience 3. Performance

Slide 7

Slide 7 text

MAIN CONCERNS WITH VALIDATION 1. Enforcement 2. User Experience 3. Performance 4. Convenience

Slide 8

Slide 8 text

MAIN CONCERNS WITH VALIDATION 1. Enforcement 2. User Experience 3. Performance 4. Convenience

Slide 9

Slide 9 text

MAIN CONCERNS WITH VALIDATION 1. Enforcement 2. User Experience 3. Performance 4. Convenience

Slide 10

Slide 10 text

MAIN CONCERNS WITH VALIDATION 1. Enforcement 2. User Experience 3. Performance 4. Convenience

Slide 11

Slide 11 text

WHERE TO VALIDATE DATA

Slide 12

Slide 12 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database

Slide 13

Slide 13 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database

Slide 14

Slide 14 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database

Slide 15

Slide 15 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database

Slide 16

Slide 16 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Good for UX

Slide 17

Slide 17 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Good for UX Works offline

Slide 18

Slide 18 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Good for UX Works offline Need to keep in sync
 with server validation

Slide 19

Slide 19 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database

Slide 20

Slide 20 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Not designed for the task

Slide 21

Slide 21 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Not designed for the task Easy to circumvent

Slide 22

Slide 22 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database

Slide 23

Slide 23 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task

Slide 24

Slide 24 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Easy to circumvent

Slide 25

Slide 25 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Easy to circumvent

Slide 26

Slide 26 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database

Slide 27

Slide 27 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task

Slide 28

Slide 28 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Doesn’t run by default

Slide 29

Slide 29 text

ENFORCE MODEL VALIDATION class ValidateModelMixin(object): def save(self, *args, **kwargs): # Run model validation. self.full_clean() super(ValidateModelMixin, self).save(*args, **kwargs)

Slide 30

Slide 30 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Doesn’t run by default Harder to circumvent if
 triggered from the `save()`
 method

Slide 31

Slide 31 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Doesn’t run by default Harder to circumvent if
 triggered from the `save()`
 method Not always accessible

Slide 32

Slide 32 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Doesn’t run by default Harder to circumvent if
 triggered from the `save()`
 method Not always accessible Redundant

Slide 33

Slide 33 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Doesn’t run by default Harder to circumvent if
 triggered from the `save()`
 method Not always accessible Redundant Breaks expectations

Slide 34

Slide 34 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database

Slide 35

Slide 35 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task

Slide 36

Slide 36 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Always enforced

Slide 37

Slide 37 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Always enforced Performance!

Slide 38

Slide 38 text

PERFORMANCE OF MODEL VALIDATION def validate_title(title): if title == 'boom': raise ValidationError('Boom!', code='boom') class Article(models.Model): title = models.CharField( max_length=42, validators=[validate_title], )

Slide 39

Slide 39 text

PERFORMANCE OF MODEL VALIDATION def validate_title(title): if title == 'boom': raise ValidationError('Boom!', code='boom') class Article(models.Model): title = models.CharField( max_length=42, validators=[validate_title], ) with transaction.atomic(): for i in range(1000000): article = Article(title=str(i)) article.full_clean() article.save()

Slide 40

Slide 40 text

PERFORMANCE OF MODEL VALIDATION def validate_title(title): if title == 'boom': raise ValidationError('Boom!', code='boom') class Article(models.Model): title = models.CharField( max_length=42, validators=[validate_title], ) ALTER TABLE article ADD CHECK(title <> 'boom'); Article.objects.bulk_create( Article(title=str(i)) for i in range(1000000) )

Slide 41

Slide 41 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Always enforced Performance! Backend specific

Slide 42

Slide 42 text

DATABASE CHECK CONSTRAINTS class Article(models.Model): title = models.CharField(max_length=42) class Meta: indexes = [ models.Constraint( [F('title') != Value('boom')], name='expression_check', ), ]

Slide 43

Slide 43 text

DATABASE CHECK CONSTRAINTS class Article(models.Model): title = models.CharField(max_length=42) class Meta: indexes = [ models.Constraint( [F('title') != Value('boom')], name='expression_check', ), ] ALTER TABLE article ADD CHECK(title <> 'boom');

Slide 44

Slide 44 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Always enforced Performance! Backend specific Harder to write

Slide 45

Slide 45 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Always enforced Performance! Backend specific Harder to write Harder to audit

Slide 46

Slide 46 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database Designed for the task Always enforced Performance! Backend specific Harder to write Harder to audit Harder to maintain

Slide 47

Slide 47 text

WHERE TO VALIDATE DATA 1. Frontend ‣ JavaScript ‣ HTML5 & Browser ‣ Native code / frameworks 2. Django View 3. Form / DRF Serializer 4. Model 5. Database

Slide 48

Slide 48 text

FIELD VALIDATION

Slide 49

Slide 49 text

FIELD DECLARATION ‣ Type validation ‣ class Field(object): ‣ class CharField(Field): ‣ class IntegerField(Field): ‣ class DateField(Field): ‣ etc.

Slide 50

Slide 50 text

FIELD DECLARATION ‣ Presence validation class Field(object): def __init__(self, required=True, ...): self.required = required ... class Field(object): def __init__(self, blank=False, ...): self.blank = blank ...

Slide 51

Slide 51 text

FIELD DECLARATION ‣ Bounds validation class CharField(Field): def __init__(self, max_length=None, min_length=None): ... class IntegerField(Field): def __init__(self, max_value=None, min_value=None): ...

Slide 52

Slide 52 text

FIELD DECLARATION ‣ Choice validation # django.forms.fields class ChoiceField(forms.Field): def __init__(self, choices=()): ... # django.db.models.fields class Field(object): def __init__(self, choices=()): ...

Slide 53

Slide 53 text

FIELD DECLARATION ‣ Format validation class RegexField(CharField): def __init__(self, regex, ...): ... class DateField(Field): def __init__(self, input_formats, ...) ...

Slide 54

Slide 54 text

FIELD DECLARATION ‣ Format validation class RegexField(CharField): def __init__(self, regex, ...): ... class DateField(Field): def __init__(self, input_formats, ...) ... class EmailField(CharField): default_validators = [validate_email] class SlugField(CharField): default_validators = [validate_slug]

Slide 55

Slide 55 text

FIELD DECLARATION ‣ Uniqueness validation class Field(object): def __init__(self, unique=False, unique_for_date=None, unique_for_month=None, unique_for_year=None, ...): ...

Slide 56

Slide 56 text

FIELD DECLARATION ‣ Field validators class Field(object): default_validators = [] def __init__(self, validators=(), ...): self.validators = list(itertools.chain( self.default_validators, validators))

Slide 57

Slide 57 text

FUNCTION-BASED VALIDATOR def validate_even(value): if value % 2 != 0: raise ValidationError( _('%(value)s is not an even number'), params={'value': value}, ) IntegerField(validators=[validate_even])

Slide 58

Slide 58 text

CLASS-BASED VALIDATOR class MultipleOf(object): def __init__(self, base): self.base = base def __call__(self, value): if value % self.base != 0: raise ValidationError( _('Field must be a multiple of %(base)d.'), params={'base': self.base, 'value': value}, ) IntegerField(validators=[MultipleOf(42)])

Slide 59

Slide 59 text

CLASS-BASED VALIDATOR from django.utils.deconstruct import deconstructible @deconstructible class MultipleOf(object): def __init__(self, base): self.base = base def __call__(self, value): if value % self.base != 0: raise ValidationError( _('Field must be a multiple of %(base)d.'), params={'base': self.base, 'value': value}, ) def __eq__(self, other): return ( isinstance(other, self.__class__) and self.base == other.base )

Slide 60

Slide 60 text

PARTIAL-BASED VALIDATOR import functools def multiple_of(base, value): if value % base != 0: raise ValidationError( _('Field must be a multiple of %(base)d.'), params={'base': base, 'value': value}, ) multiple_of_42 = functools.partial(multiple_of, 42) IntegerField(validators=[multiple_of_42])

Slide 61

Slide 61 text

FIELD DECLARATION ‣ Error messages class Field(object): def __init__(self, error_messages=None, ...): ...

Slide 62

Slide 62 text

FIELD DECLARATION ‣ Error messages class Field(object): default_error_messages = { 'required': _('This field is required.'), } def __init__(self, error_messages=None, ...): messages = {} for c in reversed(self.__class__.__mro__): messages.update( getattr(c, 'default_error_messages', {})) messages.update(error_messages or {}) self.error_messages = messages ...

Slide 63

Slide 63 text

FIELD VALIDATION CYCLE

Slide 64

Slide 64 text

FIELD VALIDATION CYCLE ‣ Field.clean() def clean(self, value): value = self.to_python(value) self.validate(value) self.run_validators(value) return value

Slide 65

Slide 65 text

FIELD VALIDATION CYCLE ‣ Field.clean() def clean(self, value): value = self.to_python(value) self.validate(value) self.run_validators(value) return value

Slide 66

Slide 66 text

FIELD VALIDATION CYCLE ‣ Field.clean() def clean(self, value): value = self.to_python(value) self.validate(value) self.run_validators(value) return value

Slide 67

Slide 67 text

FIELD VALIDATION CYCLE ‣ Field.validate() def validate(self, value): if value in self.empty_values and self.required: raise ValidationError( self.error_messages['required'], code='required', )

Slide 68

Slide 68 text

FIELD VALIDATION CYCLE ‣ Field.clean() def clean(self, value): value = self.to_python(value) self.validate(value) self.run_validators(value) return value

Slide 69

Slide 69 text

FIELD VALIDATION CYCLE ‣ Field.run_validators() def run_validators(self, value): if value in self.empty_values: return errors = [] for validator in self.validators: try: validator(value) except ValidationError as e: if hasattr(e, 'code') and e.code in self.error_messages: e.message = self.error_messages[e.code] errors.extend(e.error_list) if errors: raise ValidationError(errors)

Slide 70

Slide 70 text

FIELD VALIDATION CYCLE ‣ Field.run_validators() def run_validators(self, value): if value in self.empty_values: return errors = [] for validator in self.validators: try: validator(value) except ValidationError as e: if hasattr(e, 'code') and e.code in self.error_messages: e.message = self.error_messages[e.code] errors.extend(e.error_list) if errors: raise ValidationError(errors)

Slide 71

Slide 71 text

FIELD VALIDATION CYCLE ‣ Field.clean() def clean(self, value): value = self.to_python(value) self.validate(value) self.run_validators(value) return value

Slide 72

Slide 72 text

FIELD VALIDATION CYCLE ‣ Field.clean() class CharField(Field): def __init__(self, max_length, min_length, ...): ... self.max_length = max_length self.min_length = min_length self.validators.append( validators.MinLengthValidator(min_length)) self.validators.append( validators.MaxLengthValidator(max_length))

Slide 73

Slide 73 text

VALIDATION ERROR

Slide 74

Slide 74 text

ANATOMY OF VALIDATION ERROR class ValidationError(Exception): def __init__(self, message, code=None, params=None): ...

Slide 75

Slide 75 text

ANATOMY OF VALIDATION ERROR class ValidationError(Exception): def __init__(self, message, code=None, params=None): ... ValidationError("Foo")

Slide 76

Slide 76 text

ANATOMY OF VALIDATION ERROR class ValidationError(Exception): def __init__(self, message, code=None, params=None): ... ValidationError("Foo") ValidationError(["Foo", "Bar"])

Slide 77

Slide 77 text

ANATOMY OF VALIDATION ERROR class ValidationError(Exception): def __init__(self, message, code=None, params=None): ... ValidationError("Foo") ValidationError(["Foo", "Bar"]) ValidationError({ "field1": ["Foo", "Bar"], "field2": "Baz", })

Slide 78

Slide 78 text

ANATOMY OF VALIDATION ERROR class ValidationError(Exception): def __init__(self, message, code=None, params=None): ... ValidationError([ "Bar", ValidationError("Foo"), ])

Slide 79

Slide 79 text

ANATOMY OF VALIDATION ERROR class ValidationError(Exception): def __init__(self, message, code=None, params=None): ... ValidationError({ "field1": ValidationError([ ValidationError("Foo"), ValidationError("Bar"), ]), })

Slide 80

Slide 80 text

ANATOMY OF VALIDATION ERROR class ValidationError(Exception): def __init__(self, message, code=None, params=None): ... if isinstance(message, dict): self.error_dict = {...} elif isinstance(message, list): self.error_list = [...] else: self.message = message self.code = code self.params = params self.error_list = [self]

Slide 81

Slide 81 text

ANATOMY OF VALIDATION ERROR class ValidationError(Exception): def __init__(self, message, code=None, params=None): ... if isinstance(message, dict): self.error_dict = {...} elif isinstance(message, list): self.error_list = [...] else: self.message = message self.code = code self.params = params self.error_list = [self]

Slide 82

Slide 82 text

RAISING VALIDATION ERRORS raise ValidationError("Invalid value: %s" % 42)

Slide 83

Slide 83 text

RAISING VALIDATION ERRORS from django.utils.translation import ugettext as _ raise ValidationError(_("Invalid value: %s") % 42)

Slide 84

Slide 84 text

RAISING VALIDATION ERRORS from django.utils.translation import ugettext as _ raise ValidationError( _("Invalid value: %s") % 42, code='invalid', )

Slide 85

Slide 85 text

RAISING VALIDATION ERRORS from django.utils.translation import ugettext as _ raise ValidationError( _("Invalid value: %s"), code='invalid', params=42, )

Slide 86

Slide 86 text

RAISING VALIDATION ERRORS from django.utils.translation import ugettext as _ raise ValidationError( _("Invalid value: %(value)s"), code='invalid', params={'value': 42}, )

Slide 87

Slide 87 text

RAISING VALIDATION ERRORS from django.utils.translation import ugettext as _ raise ValidationError( message=_("%(model_name)s with this %(field_labels)s " "already exists."), code='unique_together', params={ 'model': self, 'model_class': model_class, 'model_name': verbose_name), 'unique_check': unique_check, }, )

Slide 88

Slide 88 text

FORM VALIDATION

Slide 89

Slide 89 text

TRIGGERING FORM VALIDATION ‣ Form.is_valid() def is_valid(self): return self.is_bound and not self.errors

Slide 90

Slide 90 text

TRIGGERING VALIDATION ‣ Form.is_valid() def is_valid(self): return self.is_bound and not self.errors ‣ Form.errors @property def errors(self): if self._errors is None: self.full_clean() return self._errors

Slide 91

Slide 91 text

TRIGGERING VALIDATION ‣ Form.is_valid() def is_valid(self): return self.is_bound and not self.errors ‣ Form.errors @property def errors(self): if self._errors is None: self.full_clean() return self._errors ‣ Form.full_clean()

Slide 92

Slide 92 text

FORM VALIDATION CYCLE ‣ Form.full_clean() def full_clean(self): self._errors = ErrorDict() self.cleaned_data = {} self._clean_fields() self._clean_form() self._post_clean()

Slide 93

Slide 93 text

FORM VALIDATION CYCLE ‣ Form.full_clean() def full_clean(self): self._errors = ErrorDict() self.cleaned_data = {} self._clean_fields() self._clean_form() self._post_clean()

Slide 94

Slide 94 text

FORM VALIDATION CYCLE ‣ Form.full_clean() def full_clean(self): self._errors = ErrorDict() self.cleaned_data = {} self._clean_fields() self._clean_form() self._post_clean()

Slide 95

Slide 95 text

FORM VALIDATION CYCLE ‣ Form._clean_fields() def _clean_fields(self): for name, field in self.fields.items(): value = field.widget.value_from_datadict( self.data, self.files, self.add_prefix(name))

Slide 96

Slide 96 text

FORM VALIDATION CYCLE ‣ Form._clean_fields() def _clean_fields(self): for name, field in self.fields.items(): value = field.widget.value_from_datadict( self.data, self.files, self.add_prefix(name)) try: value = field.clean(value) self.cleaned_data[name] = value

Slide 97

Slide 97 text

FORM VALIDATION CYCLE ‣ Form._clean_fields() def _clean_fields(self): for name, field in self.fields.items(): value = field.widget.value_from_datadict( self.data, self.files, self.add_prefix(name)) try: value = field.clean(value) self.cleaned_data[name] = value if hasattr(self, 'clean_%s' % name): value = getattr(self, 'clean_%s' % name)() self.cleaned_data[name] = value

Slide 98

Slide 98 text

FORM VALIDATION CYCLE ‣ Form._clean_fields() def _clean_fields(self): for name, field in self.fields.items(): value = field.widget.value_from_datadict( self.data, self.files, self.add_prefix(name)) try: value = field.clean(value) self.cleaned_data[name] = value if hasattr(self, 'clean_%s' % name): value = getattr(self, 'clean_%s' % name)() self.cleaned_data[name] = value except ValidationError as e: self.add_error(name, e)

Slide 99

Slide 99 text

FORM VALIDATION CYCLE ‣ Form.full_clean() def full_clean(self): self._errors = ErrorDict() self.cleaned_data = {} self._clean_fields() self._clean_form() self._post_clean()

Slide 100

Slide 100 text

FORM VALIDATION CYCLE ‣ Form._clean_form() def _clean_form(self): try: cleaned_data = self.clean() except ValidationError as e: self.add_error(None, e) else: if cleaned_data is not None: self.cleaned_data = cleaned_data

Slide 101

Slide 101 text

FORM VALIDATION CYCLE ‣ Form.full_clean() def full_clean(self): self._errors = ErrorDict() self.cleaned_data = {} self._clean_fields() self._clean_form() self._post_clean() ‣ Form._post_clean() def _post_clean(self): pass

Slide 102

Slide 102 text

FORM VALIDATION UTILS ‣ Form.add_error()

Slide 103

Slide 103 text

FORM VALIDATION UTILS ‣ Form.add_error() def add_error(self, field, error): if not isinstance(error, ValidationError): error = ValidationError(error)

Slide 104

Slide 104 text

FORM VALIDATION UTILS ‣ Form.add_error() def add_error(self, field, error): if not isinstance(error, ValidationError): error = ValidationError(error) if hasattr(error, 'error_dict'): error = error.error_dict else: error = {field or NON_FIELD_ERRORS: error.error_list}

Slide 105

Slide 105 text

FORM VALIDATION UTILS ‣ Form.add_error() def add_error(self, field, error): if not isinstance(error, ValidationError): error = ValidationError(error) if hasattr(error, 'error_dict'): error = error.error_dict else: error = {field or NON_FIELD_ERRORS: error.error_list} for field, error_list in error.items(): if field not in self.errors: self._errors[field] = self.error_class() self._errors[field].extend(error_list)

Slide 106

Slide 106 text

FORM VALIDATION UTILS ‣ Form.add_error() def add_error(self, field, error): if not isinstance(error, ValidationError): error = ValidationError(error) if hasattr(error, 'error_dict'): error = error.error_dict else: error = {field or NON_FIELD_ERRORS: error.error_list} for field, error_list in error.items(): if field not in self.errors: self._errors[field] = self.error_class() self._errors[field].extend(error_list) if field in self.cleaned_data: del self.cleaned_data[field]

Slide 107

Slide 107 text

FORM VALIDATION UTILS ‣ Form.add_error() def add_error(self, field, error): if not isinstance(error, ValidationError): error = ValidationError(error) if hasattr(error, 'error_dict'): error = error.error_dict else: error = {field or NON_FIELD_ERRORS: error.error_list} for field, error_list in error.items(): if field not in self.errors: self._errors[field] = self.error_class() self._errors[field].extend(error_list) if field in self.cleaned_data: del self.cleaned_data[field]

Slide 108

Slide 108 text

FORM VALIDATION UTILS ‣ Form.add_error() def add_error(self, field, error): if not isinstance(error, ValidationError): error = ValidationError(error) if hasattr(error, 'error_dict'): error = error.error_dict else: error = {field or NON_FIELD_ERRORS: error.error_list} for field, error_list in error.items(): if field not in self.errors: self._errors[field] = self.error_class() self._errors[field].extend(error_list) if field in self.cleaned_data: del self.cleaned_data[field]

Slide 109

Slide 109 text

FORM VALIDATION UTILS ‣ Form.add_error() def add_error(self, field, error): if not isinstance(error, ValidationError): error = ValidationError(error) if hasattr(error, 'error_dict'): error = error.error_dict else: error = {field or NON_FIELD_ERRORS: error.error_list} for field, error_list in error.items(): if field not in self.errors: self._errors[field] = self.error_class() self._errors[field].extend(error_list) if field in self.cleaned_data: del self.cleaned_data[field]

Slide 110

Slide 110 text

FORM VALIDATION UTILS ‣ Form.errors class ErrorDict(dict): def as_ul(self): ... def as_text(self): ... def as_data(self): ... def as_json(self, escape_html=False): ...

Slide 111

Slide 111 text

FORM VALIDATION UTILS ‣ Form.errors['field_name'] class ErrorList(list): def as_ul(self): ... def as_text(self): ...

Slide 112

Slide 112 text

FORM VALIDATION UTILS ‣ Form.errors['field_name'] class ErrorList(UserList, list): def __contains__(self, item): return item in list(self) def __eq__(self, other): return list(self) == other def __ne__(self, other): return list(self) != other def __getitem__(self, i): error = self.data[i] if isinstance(error, ValidationError): return list(error)[0] return force_text(error)

Slide 113

Slide 113 text

FORM VALIDATION UTILS ‣ Form.errors['field_name'] class ErrorList(UserList, list): def as_data(self): return ValidationError(self.data).error_list def get_json_data(self): errors = [] for error in self.as_data(): errors.append({ 'message': list(error)[0], 'code': error.code or '', }) return errors def as_json(self): return json.dumps(self.get_json_data())

Slide 114

Slide 114 text

FORM VALIDATION UTILS ‣ Form.has_error() def has_error(self, field, code=None): if code is None: return field in self.errors if field in self.errors: for error in self.errors.as_data()[field]: if error.code == code: return True return False

Slide 115

Slide 115 text

FORM VALIDATION UTILS class ValidationError(Exception): def __str__(self): if hasattr(self, 'error_dict'): return repr(dict(self)) return repr(list(self)) >>> str(ValidationError("Some error...")) "[u'Some error...']"

Slide 116

Slide 116 text

FORM VALIDATION UTILS class ValidationError(Exception): def __str__(self): if hasattr(self, 'error_dict'): return repr(dict(self)) elif not hasattr(self, 'code'): return repr(list(self)) else: return list(self)[0] >>> str(ValidationError("Some error...")) "Some error..."

Slide 117

Slide 117 text

MODEL VALIDATION

Slide 118

Slide 118 text

MODEL VALIDATION INSPIRED BY DJANGO’S FORM VALIDATION. Django 1.2 release notes

Slide 119

Slide 119 text

TRIGGERING MODEL VALIDATION ‣ Model.full_clean() try: article.full_clean() except ValidationError: is_valid = False else: is_valid = True

Slide 120

Slide 120 text

MODEL VALIDATION CYCLE ‣ Model.full_clean() def full_clean(self, exclude=None, validate_unique=True): errors = {} ... if errors: raise ValidationError(errors)

Slide 121

Slide 121 text

MODEL VALIDATION CYCLE ‣ Model.full_clean() def full_clean(self, exclude=None, validate_unique=True): ... try: self.clean_fields(exclude=exclude) except ValidationError as e: errors = e.update_error_dict(errors)

Slide 122

Slide 122 text

MODEL VALIDATION CYCLE ‣ Model.full_clean() def full_clean(self, exclude=None, validate_unique=True): ... try: self.clean() except ValidationError as e: errors = e.update_error_dict(errors)

Slide 123

Slide 123 text

MODEL VALIDATION CYCLE ‣ Model.full_clean() def full_clean(self, exclude=None, validate_unique=True): ... try: self.clean() except ValidationError as e: errors = e.update_error_dict(errors) ‣ Model.clean() def clean(self): pass

Slide 124

Slide 124 text

MODEL VALIDATION CYCLE ‣ Model.full_clean() def full_clean(self, exclude=None, validate_unique=True): ... if validate_unique: for name in errors.keys(): if name not in exclude: exclude.append(name) try: self.validate_unique(exclude=exclude) except ValidationError as e: errors = e.update_error_dict(errors)

Slide 125

Slide 125 text

MODEL VALIDATION CYCLE ‣ Field.unique_for_date ALTER TABLE article ADD EXCLUDE USING GIST ( title WITH =, daterange(pub_date, pub_date, '[]') WITH && );

Slide 126

Slide 126 text

MODEL VALIDATION CYCLE ‣ Field.unique_for_month ALTER TABLE article ADD EXCLUDE USING GIST ( title WITH =, daterange( date_trunc('month', pub_date::timestamp)::date, (date_trunc('month', pub_date::timestamp) + '1month - 1day')::date, '[]' ) WITH && );

Slide 127

Slide 127 text

MODEL VALIDATION CYCLE ‣ Field.unique_for_year ALTER TABLE article ADD EXCLUDE USING GIST ( title WITH =, daterange( date_trunc('year', pub_date::timestamp)::date, (date_trunc('year', pub_date::timestamp) + '1year - 1day')::date, '[]' ) WITH && );

Slide 128

Slide 128 text

MODEL VALIDATION CYCLE ‣ Model.full_clean() def full_clean(self, exclude=None, validate_unique=True): ... if validate_unique: for name in errors.keys(): if name not in exclude: exclude.append(name) try: self.validate_unique(exclude=exclude) except ValidationError as e: errors = e.update_error_dict(errors)

Slide 129

Slide 129 text

MODELFORM VALIDATION

Slide 130

Slide 130 text

MODELFORM VALIDATION CYCLE ‣ ModelForm._post_clean() def _post_clean(self): self.instance = construct_instance(...)

Slide 131

Slide 131 text

MODELFORM VALIDATION CYCLE ‣ ModelForm._post_clean() def _post_clean(self): self.instance = construct_instance(...) exclude = self._get_validation_exclusions()

Slide 132

Slide 132 text

MODELFORM VALIDATION CYCLE ‣ ModelForm._post_clean() def _post_clean(self): self.instance = construct_instance(...) exclude = self._get_validation_exclusions() try: self.instance.full_clean( exclude=exclude, validate_unique=False) except ValidationError as e: self._update_errors(e)

Slide 133

Slide 133 text

MODELFORM VALIDATION CYCLE ‣ ModelForm._post_clean() def _post_clean(self): self.instance = construct_instance(...) exclude = self._get_validation_exclusions() try: self.instance.full_clean( exclude=exclude, validate_unique=False) except ValidationError as e: self._update_errors(e)

Slide 134

Slide 134 text

MODELFORM VALIDATION CYCLE ‣ ModelForm._post_clean() def _post_clean(self): self.instance = construct_instance(...) exclude = self._get_validation_exclusions() try: self.instance.full_clean( exclude=exclude, validate_unique=False) except ValidationError as e: self._update_errors(e) if self._validate_unique: self.validate_unique()

Slide 135

Slide 135 text

CLOSING WORDS

Slide 136

Slide 136 text

THANK YOU