views:

89

answers:

2

I have the following models:

class Application(models.Model):
 users = models.ManyToManyField(User, through='Permission')
 folder = models.ForeignKey(Folder)

class Folder(models.Model):
 company = models.ManyToManyField(Compnay)

class UserProfile(models.Model):
 user = models.OneToOneField(User, related_name='profile')
 company = models.ManyToManyField(Company)

What I would like to do is to check whether one of the users of the Application has the same company as the Application (via Folder). If this is the case the Application instance should not be saved.

The problem is that the ManyToManyFields aren't updated until after the 'post-save' signal.
The only option seems to be the new m2m_changed signal. But I'm not sure how I then roll back the save that has already happened.
Another option would be to rewrite the save function (in models.py, because I'm talking about the admin here), but I'm not sure how I could access the manytomanyfield content.
Finally I've read something about rewriting the save function in the admin of the model in admin.py, however I still wouldn't know how you would access the manytomanyfield content.

I have been searching for this everywhere but nothing I come across seems to work for me.
If anything is unclear, please tell me.

Thanks for your help!
Heleen

A: 

forms.py

class ApplicationForm(ModelForm):
    class Meta:
        model = Application

    def clean(self):
        cleaned_data = self.cleaned_data
        users = cleaned_data['users']
        folder = cleaned_data['folder']
        if users.filter(profile__company__in=folder.company.all()).count() > 0:
            raise forms.ValidationError('One of the users of this Application works in one of the Folder companies!')
        return cleaned_data

admin.py

class ApplicationAdmin(ModelAdmin):
    form = ApplicationForm

Edit: Replaced initial (wrong) model validation example with form validation.

Béres Botond
When I did profile__company__in in quotes I got a syntax error.When I unquote it I get: 'Application' instance needs to have a primary key value before a many-to-many relationship can be used.Thanks for your help so far though!
Heyl1
Also, I would expect 'profile__company__in' = self.folder.company.all() to result in the user's companies having to be a subset of the folder's companies. Or am I wrong and does it result in an intersection, as intended?
Heyl1
I'm pretty sure it will do intersection. But you can test it to be sure.
Béres Botond
Thanks for the edit. I've tried your suggestion, but I ran into 2 problems.1. I had to unquote 'profile__company__in' because it came up with a syntax error: "keyword can't be an expression". This seems to be working fine now.2. It appears that 'users' is not in cleaned_data. I get a KeyError and when I look at the local variables in the Debug I can see that 'users' is not in cleaned_data. 'Permission' seems to be in POST though.I'm not sure whether this is happening because 'users' is a ManyToManyField or because the ManyToManyField has an intermediary through model.
Heyl1
Yes, indeed I'm not sure how I've written it like that and didn't notice.If you print *self.cleaned_data* in *clean* what do you see? And how does your Permission model look like?
Béres Botond
When I print self.cleaned_data I get the following: {'folder': <Folder: Test>}.My Permission model holds some data about what role the user has in the Application and if he's active. I have used auth.Group for role. So it looks like this: class Permission(models.Model): user = models.ForeignKey(User) application = models.ForeignKey('Application') role = models.ForeignKey(Group) active = models.BooleanField()(These StackOverlfow comments don't allow returns so it looks a bit messy here unfortunately).
Heyl1
A: 

Because I didn't get a reply from Botondus I decided to ask a new question in the Django Users Google Group and finally got the answer from jaymz.

I figured that Botondus method was the right way of doing it, it just wasn't quite working. The reason that it doesn't work in this case is because I'm using a Through model for the field I would like to do the validation on. Because of some earlier feedback I got on a previously posted question I gathered that first the Application instance is saved and then the ManyToMany instances are saved (I believe this is right, but correct me if I'm wrong). So I thought that, if I would perform the validation on the ManyToMany Field in the Through model, this would not prevent the Application instance being saved. But in fact it does prevent that from happening.

So if you have a ManyToMany Field inline in your model's admin and you would like to do validation on that field, you specify the clean function in the through model, like this:

admin.py
class PermissionInline(admin.TabularInline):
    form = PermissionForm
    model = Permission
    extra = 3

forms.py
class PermissionForm(forms.ModelForm):
    class Meta:
        model = Permission

    def clean(self):
        cleaned_data = self.cleaned_data
        user = cleaned_data['user']
        role = cleaned_data['role']
        if role.id != 1:
            folder = cleaned_data['application'].folder
            if len(filter(lambda x:x in user.profile.company.all(),folder.company.all())) > 0: # this is an intersection
                raise forms.ValidationError("One of the users of this Application works for one of the Repository's organisations!")
        return cleaned_data 

If the validation results in an error NOTHING (neither the application instance, nor the manytomany users instances) is saved and you get the chance to correct the error.

Heyl1