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 -~----------~----~----~----~------~----~------~--~---