views:

71

answers:

1

I have the following models (simplified example):

class Book(models.Model):
 users = models.ManyToManyField(User, through=Permission)

class Permission(models.Model):
 user = models.ForeignKey(User)
 role = models.ForeignKey(Group)
 active = models.BooleanField()
 book = models.ForeignKey(Book)

What I need is that for a Book instance there cannot be more than one User of with the same Role and Active. So this is allowed:

Alice, Admin, False (not active), BookA
Dick, Admin, True (active), BookA
Chris, Editor, False (not active), BookA
Matt, Editor, False (not active), BookA

But this is not allowed:

Alice, Admin, True (active), BookA
Dick, Admin, True (active), BookA

Now this cannot be done with unique_together, because it only counts when active is True. I've tried to write a custom clean method (like how I have done here). But it seems that when you save a Book and it runs the validation on each Permission, the already validated Permission instances aren't saved until they've all been validated. This makes sense, because you don't want them to be saved in case something doesn't validate.

Could anyone tell me if there is a way to perform the validation described above?

P.S. I could imagine using the savepoint feature (http://docs.djangoproject.com/en/1.2/topics/db/transactions/), but I only really want to consider that as a last resort.
Maybe you can do something like: unique_together = [[book, role, active=1],] ?

Edit Sep. 23, 2010 14:00 Response to Manoj Govindan:

My admin.py (simplified version for clarity):

class BookAdmin(admin.ModelAdmin):
    inlines = (PermissionInline,)

class PermissionInline(admin.TabularInline):
    model = Permission

In the shell your validation would work. Because you first have to create the book instance and then you create all the Permission instances one by one: http://docs.djangoproject.com/en/1.2/topics/db/models/#extra-fields-on-many-to-many-relationships. So in the shell if you add 2 Permission instances the 1st Permission instance has been saved by the time 2nd is being validated, and so the validation works.

However when you use the Admin interface and you add all the book.users instances at the same time via the book users inline, I believe it does all the validation on all the book.users instances first, before it saves them. When I tried it, the validation didn't work, it just succeeded without an error when there should have been a ValidationError.

A: 

One way to do this is to use the newfangled model validation. Specifically, you can add a custom validate_unique method to Permission models to achieve this effect. For e.g.

from django.core.exceptions import ValidationError, NON_FIELD_ERRORS

class Permission(models.Model):
    ...
    def validate_unique(self, exclude = None):
        options = dict(book = self.book, role = self.role, active = True)
        if Permission.objects.filter(**options).count() != 0:
            template = """There cannot be more than one User of with the
                same Role and Active (book: {0})"""
            message = template.format(self.book)
            raise ValidationError({NON_FIELD_ERRORS: [message]})

I did some rudimentary testing using one of my projects' Admin app and it seemed to work.

Manoj Govindan
Thanks for your reply! Unfortunately it doesn't work, because when you create the instance the first time, `Permission.objects.filter(**options).count()` will always return 0. The permissions you've entered in the Book Permission Inline have not been saved yet so they won't be in the database and you can therefore not catch them using `Permission.objects.filter`. I've tested it and this is indeed what happens.
Heyl1
@Heyl1: I'm afraid I don't understand. Shouldn't the entry get created the first time? Otherwise how will a Permission ever get created?
Manoj Govindan
The book instance and the book.users instances aren't saved until after validation. This makes sense because if validation returns a ValidationError they shouldn't have already been saved. Permission.objects.filter will only return what has been saved and therefore will never include the new Permission objects you're now creating. So this validation will always return OK (except when you update the instance, it will then check the old book.users which might have changed in this updated version, so now the validation is simply incorrect).
Heyl1
@Heyl1: Looks like I am missing something here. Can you post (1) the code for your `admin.py` (2) sample code if any that you are trying in the shell?
Manoj Govindan
@Manoj Govindan: Please see my edit in the main question. I hope I've made it more clear now what I mean. I might be mistaken in the way things work but I think what I've explained in the edit is why the validation doens't work.
Heyl1