I created custom AddManipulator and ChangeManipulator classes in my
model to do model-level validation.  This did require a minor change
to django/db/models/manipulators.py.  The details of this change and
the definition of the model are appended here.  I have not done
extensive testing yet, but this seems to work.

Mark
-----------------------------------------------------------------------------
class MymodelManager(models.Manager):
    def get_query_set(self):
        current_user=threadlocals.get_current_user()
        if current_user.is_superuser:
            return super(MymodelManager, self).get_query_set()
        else:
            return super(MymodelManager,
self).get_query_set().filter(mymodel_owner=threadlocals.get_current_user())

class Mymodel(models.Model):
        mymodel_name = models.CharField('Mymodel Name', maxlength=50,
core=True)
        mymodel_owner = models.ForeignKey(User, editable=False)

        objects = MymodelManager()

        def __str__(self):
            return self.mymodel_name

        def save(self):
            if self.id:
                pass
            else:
               self.mymodel_owner = threadlocals.get_current_user()
            super(Mymodel,self).save()

        def get_absolute_url(self):
            return "/home/mymodelapp/mymodel/%s/" % self.id

        class Meta:
            unique_together = (("mymodel_name", "mymodel_owner"),)

        class Admin:
            fields = (
                (None, {
                    'fields': (('mymodel_name',),)}
                 ),
                )
            list_display = ('mymodel_name',)
            search_fields = ['mymodel_name']
            manager = MymodelManager()

        class AddManipulator(manipulators.AutomaticAddManipulator):

            def contribute_to_class(cls, other_cls, name):
                setattr(other_cls, name,
manipulators.ManipulatorDescriptor(name, cls))
            contribute_to_class = classmethod(contribute_to_class)

            def get_validation_errors(self, new_data):
                "Returns dictionary mapping field_names to error-
message lists"
                errors = {}
                self.prepare(new_data)
                for field in self.fields:
 
errors.update(field.get_validation_errors(new_data))
                    val_name = 'validate_%s' % field.field_name
                    if hasattr(self, val_name):
                        val = getattr(self, val_name)
                        try:
                            field.run_validator(new_data, val)
                        except (validators.ValidationError,
validators.CriticalValidationError), e:
                            errors.setdefault(field.field_name,
[]).extend(e.messages)
                for any_mymodel in Mymodel.objects.all():
                    if (any_mymodel.mymodel_name ==
new_data["mymodel_name"]):
                         errors.setdefault('mymodel_name',
[]).append(_('A mymodel named %(name)s already exists' %
{'name':new_data["mymodel_name"]}))
                return errors

        class
ChangeManipulator(manipulators.AutomaticChangeManipulator):

            def contribute_to_class(cls, other_cls, name):
                setattr(other_cls, name,
manipulators.ManipulatorDescriptor(name, cls))
            contribute_to_class = classmethod(contribute_to_class)

            def get_validation_errors(self, new_data):
                "Returns dictionary mapping field_names to error-
message lists"
                errors = {}
                self.prepare(new_data)
                for field in self.fields:
 
errors.update(field.get_validation_errors(new_data))
                    val_name = 'validate_%s' % field.field_name
                    if hasattr(self, val_name):
                        val = getattr(self, val_name)
                        try:
                            field.run_validator(new_data, val)
                        except (validators.ValidationError,
validators.CriticalValidationError), e:
                            errors.setdefault(field.field_name,
[]).extend(e.messages)
                # Above this line is code taken directly from django/
db/models/manipulators.py: per-field validation
                # Below this line is custom code to do per-model
validation
                for any_mymodel in
Mymodel.objects.exclude(id=self.original_object.id):
                    if (any_mymodel.mymodel_name ==
new_data["mymodel_name"]):
                         errors.setdefault('mymodel_name',
[]).append(_('A mymodel named %(name)s already exists' %
{'name':new_data["mymodel_name"]}))
                return errors

"""
The custom Add and Change Manipulators require that the
add_manipulator function in django/db/models/manipulators.py be
changed to:

def add_manipulators(sender):
    if (sender.__name__ in ["Mymodel",]):
        return
    cls = sender
    cls.add_to_class('AddManipulator', AutomaticAddManipulator)
    cls.add_to_class('ChangeManipulator', AutomaticChangeManipulator)
"""
-----------------------------------------------------------------------------


On Apr 6, 12:54 pm, "Mark Soper" <[EMAIL PROTECTED]> wrote:
> Thanks, Malcolm!  It's encouraging to hear that others are also
> working on this.  A follow-up question:
>
> I'm inclined to try approach #1, by adding custom subclasses of Add-
> and ChangeManipulator to the model (in this case called Thesis).  If I
> can figure out way to get the dispatch mechanism to associate these
> new custom manipulators with the model class (it doesn't happen by
> simply including these custom classes in the model definition), then I
> should have (the stock ChangeValidator already get it in __init___) or
> be able to pass (AddValidator would need such an __init__) access to
> model instance info in the manipulator context, allowing me to write
> model-aware validators here without changing any django code.
>
> I haven't tried this yet.  I'll keep you posted on how it goes.  If
> you have any insight on pros and cons of this approach, please let
> know.
>
> Mark
>
> > 1. Add a custom field validator to validator_list -
> > Field validator will only check at the individual field level where
> > information about the model instance (i.e. User info) isn't in
> > context.  Also, there doesn't seem to be a good way to find out
> > whether it is an "add" or a "change" operation, which need to be
> > handled differently.  Any way to get this out-of-context information?
>
> If you field submission includes enough information to work out the
> model, you can use the DB API to select the model. However, often that
> information is in the URL and it kind of assumes all the other data is
> valid. It's very hacky -- I'm using it in one application where there
> was no other choice, but I hate it as a solution.
>
> On Apr 6, 12:22 am, Malcolm Tredinnick <[EMAIL PROTECTED]>
> wrote:
>
> > On Thu, 2007-04-05 at 21:12 -0700, Mark Soper wrote:
> > > I'm trying to figure out the best way to validate aunique_together
> > > constraint.  The relevant parts of the model are here:
>
> > > ------------------------------------------
> > > class Thesis(models.Model):
> > >         thesis_name = CharField('Thesis Name', maxlength=50,
> > > core=True, validator_list=[???????])
> > >         thesis_owner = models.ForeignKey(User, editable=False)
>
> > >         def save(self):
> > >             if not self.id:
> > >                self.thesis_owner = threadlocals.get_current_user() ***
> > >             super(Thesis,self).save()
>
> > >        class Meta:
> > >            unique_together= (("thesis_name", "thesis_owner"),)
>
> > > *** - using the threadlocals middleware from Luke Plant
> > >http://lukeplant.me.uk/blog.php?id=1107301634
> > > -----------------------------------------
>
> > > I'm using the standard django admin interfaces.  The thesis_owner
> > > field is populated automatically with the user info, but not until
> > > after user inputs have been validated.  So the standard
> > >unique_togethervalidation doesn't catch duplicate entries.  They
> > > aren't caught until the database issues an IntegrityError, generating
> > > a fatal error to the user.  Instead I'd like to raise the standard red
> > > error banner with a message like "A thesis already exists with name
> > > XXXXX.  Please choose a different name".
>
> > I can't offer much encouragement here... the drawbacks to all the
> > solutions you list are very accurate. You've understood the problem. :-(
>
> > This is the motivation behind getting model-aware validation in place in
> > Django. We need it for exactly these types of cases.
>
> > > Possible solutions:
>
> > > 1. Add a custom field validator to validator_list -
> > > Field validator will only check at the individual field level where
> > > information about the model instance (i.e. User info) isn't in
> > > context.  Also, there doesn't seem to be a good way to find out
> > > whether it is an "add" or a "change" operation, which need to be
> > > handled differently.  Any way to get this out-of-context information?
>
> > If you field submission includes enough information to work out the
> > model, you can use the DB API to select the model. However, often that
> > information is in the URL and it kind of assumes all the other data is
> > valid. It's very hacky -- I'm using it in one application where there
> > was no other choice, but I hate it as a solution.
>
> > > 2. Custom uniqueness check in the model's save method -
> > > This approach allows for access to user info and checking for "add" or
> > > "change" (see above).  However, the save happens after the view's
> > > error checking has completed, so raising a ValidationError at this
> > > stage results in a fatal browser error rather than the red error
> > > banner.  Is it possible to cause save to raise the red banner instead?
>
> > Saving should never raise validation errors for exactly the reason you
> > mention. About the only thing saving might raise is database connection
> > errors (we can't help it if somebody pulls out the network cable) or
> > possibly IntegrityError: another thread or process beat you to the punch
> > and we can't control that either.
>
> > > 3. Supplement the user info into the POSTed data before it is passed
> > > to the validation -
> > > Theoretically the standard  manipulator_validator_unique_together
> > > function will catch the error.  Can this be done through a custom
> > > manager or manipulator or in some other way that doesn't require
> > > changes to the admin views, models, etc.?
>
> > Might be possible. Never tried it.
>
> > Regards,
> > Malcolm


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

Reply via email to