After several discussions with Honza, we are still on somewhat
different positions what the validator function signature should
be and how core validators should access the fields of a form or
a model instance.

In core validators, no special case handling of forms and models
is needed even in multi-value validators. All what is needed is
an abstraction of "the set of other values".
AlwaysMatchesOtherField, RequiredIfOtherFieldDoesNotEqual etc
will work with that abstraction, primary keys and other complex
model fields also work for same-type comparison.

My initial idea was to reduce the model instance to a dict (as
discussed previously), however, this is really too restrictive.
Passing the form or model instance and treating it in dict-like
manner provides much more flexibility.

That can be achieved by the following:

class Model(...):
    ...
    def get_value(self, field_name, *args):
        "Default is specified with the first positional argument"
        if args:
            return getattr(self, field_name, args[0])
        return getattr(self, field_name)

class BaseForm(...)
    ...
    def get_value(self, field_name, *args):
        "Default is specified with the first positional argument"
        if args:
            return self.cleaned_data.get(field_name, args[0])
        return self.cleaned_data[field_name]

(It's not possible to implement the __getitem__ protocol as it
already works differently in forms.)

In this case the form/model dichotomy is not required in
validator signature, simple validators work as follows:

def is_slug(value, instance=None):
    (access only value)

and multi-value validators as follows:

class AlwaysMatchesOtherField(object):
    ...
    def __call__(self, value, instance=None):
        if value != instance.get_value(self.other):
            raise ValidationError(...)

There are currently no model-specific complex validators. If a
need for them should arise, it's probably easiest to override
validate() in the corresponding class, calling super().validate()
and then perform any additional checks there.

Custom validators can either use the get_value() abstraction to
support both models and forms or be model/form-specific and use
the instance in whatever way they like. Checking whether you deal
with a model or form instance is generally not required -- if you
accidentally pass a custom form validator to a model, it will
visibly break and vice-versa.

Honza doesn't like this, mainly because it's too easy to shoot
yourself into foot with this (as outlined above, I disagree with
that) and as of now we are using the following instead to handle
model and form fields uniformly:

def _get_value(name, all_values, instance, do_rise=True):
    if instance is not None and hasattr(instance, name):
        return getattr(instance, name)
    if name in all_values:
        return all_values[name]
    if do_raise:
        raise AttributeError('%s not found in form values or model
instance'
                % name)
    return False

class AlwaysMatchesOtherField(object):
    ...
    def __call__(self, value, all_values={}, instance=None):
        if value != _get_value(self.other, all_values, instance):
            raise ValidationError(...)

Honza's comments:
-----------------

The problem is that I do not want Form class to be passed to
validators for several reasons:

Form shouldn't be anything except something that obtains data
from request and can validate them, passing it validators could
promote adding some methods and properties to be used in
validators, instead of in the form's clean() method.

Form and model treat data differently, that's just the way it is
and as it should be - pretending it's not true will  just create
opportunity for nasty surprises.

Validators wouldn't work outside forms and models (it's easy to
pass in a dictionary of all_values, but fabricating a class with
the same method is work).

I would still have to check whether I am dealing with model or
form when I would like to access some model's methods and
properties that cannot be retrieved this way.

I have more reasons, but these are the major ones, if it's not
enough, contact me and we can talk ;).

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to