Django: "limit_choices_to" does not work on ManyToManyField

I am running Django 1.1 and cannot get the limit_choices_to parameter for my ManytoManyField to work.

I have two models:

class MemberPhoto(ImageModel): title = models.CharField(_('title'), max_length=255, blank=True, null=True) caption = models.CharField(_('caption'), max_length=255, blank=True, null=True) date_added = models.DateTimeField(_('date added'), default=datetime.now, editable=False) member = models.ForeignKey(User) def __unicode__(self): return u'%s (%s)' % (self.member.username, self.id) 

and

 class lock(models.Model): user = models.ForeignKey(User, related_name="owner") to_user = models.ForeignKey(User, related_name="to_user") unlocked_photos = models.ManyToManyField(MemberPhoto, blank=True, null=True, limit_choices_to = {'member':'user'}) objects = locking_manager() 

in the second model, I want to make sure that in Django admin, the only "unlocked_photos" ("MemberPhoto") objects represented in the multiple choice field are those who have a "member" value (user object) as a "blocking" object "user "(also the" User "object).

It seemed to me that I was following the Django docs, but that didn't work. I get the following error:

 TemplateSyntaxError Caught an exception while rendering: invalid input syntax for integer: "user" 

I tried changing the "limit_choices_to" to things like:

limit_choices_to = {'member': user} --- Doesn't work

limit_choices_to = {'member__username': 'kyle'} --- this works, but it's useless, I just manually enter the username

How can I instead get the user from the current "lock" object and filter the Member Member "Member" property?

Thanks to everyone who can help.

Kyle

+4
django admin manytomanyfield
source share
2 answers

I found an answer that achieves exactly what I wanted at this link: Django MTMField: limit_choices_to = other_ForeignKeyField_on_same_model? and I am posting my working code here for someone with the same problem. It seems that "limit_choices_to" simply cannot achieve what I wanted, and that setting up the form used by the administrator is the way to go:

 from django.contrib import admin from django import forms from gayhop.apps.locking.models import lock from gayhop.apps.photos.models import MemberPhoto class LockAdminForm(forms.ModelForm): class Meta: model = lock def __init__(self, *args, **kwargs): super(LockAdminForm, self).__init__(*args, **kwargs) self.fields['unlocked_photos'].queryset = MemberPhoto.objects.filter(member=self.instance.user) class LockAdmin(admin.ModelAdmin): form = LockAdminForm filter_horizontal = ('unlocked_photos',) django.contrib.admin.site.register(lock, LockAdmin) 

All you need to change is:

  • your model name (in the example above it is β€œlock”)
  • the name of the ManyToManyField field in your model (in the example above it is "unlocked_photos")
  • the name of the corresponding model (in the example above it is "MemberPhoto")
  • name of the field for which you want to filter related objects (in the above example, this is a "member")
  • the value of the field that you want to use to filter related objects (it will start with "self.instance." and then will be the name of the field, in the above example it is "user")
  • And finally, make sure your class names for the custom admin form and administrator model match.

Hope this helps someone!

+9
source share

To add to Kyle’s answer,

Creating a custom form is the only way to set up many, many fields in this way. But, as I discovered, this method only works if you modify an instance of this model. (at least in Django 1.5)

This is because: self.instance returns a Model object. (I know a crazy concept) But if you create an instance of this model since this model has not yet been created, self.instance will throw a DoesNotExist exception.

The way to solve this problem is to create two forms:

 class MyModelChangeForm(forms.ModelForm): class Meta: model = MyModel def __init__(self, *args, **kwargs): super(MyModelChangeForm, self).__init__(*args, **kwargs) my_model = self.instance self.fields['fields'].queryset = OtherRelatedModel.objects.filter(other_id=my_model.other) class MyModelCreationForm(forms.ModelForm): class Meta: model = MyModel def save(self, commit=True): my_model = super(MyModelCreationForm, self).save(commit=False) *** Do other things with the my_model if you want *** my_model.save() return my_model 

Then inside admin.py we will create a separate set of fields for our creation form:

 class MyModelAdmin(admin.ModelAdmin): filter_horizontal = ('other') list_display = ('field1', 'field2' 'field3') fieldsets = ( ("Model info:", {'fields': ("field1", "field2", "field3")}), ("More Model info:", {'fields': ("other",)}), ) add_fieldsets = ( ("Initial info:", {'fields': ("field1", "field2", "field3")}), ) form = MyModelChangeForm add_form = MyModelCreationForm def get_fieldsets(self, request, obj=None): if not obj: return self.add_fieldsets return super(MyModelAdmin, self).get_fieldsets(request, obj) def get_form(self, request, obj=None, **kwargs): """ Use special form during MyModel creation """ defaults = {} if obj is None: defaults.update({ 'form': self.add_form, 'fields': admin.util.flatten_fieldsets(self.add_fieldsets), }) defaults.update(kwargs) return super(MyModelAdmin, self).get_form(request, obj, **defaults) 

Note that we had to override get_form and get_fieldsets, if obj is None (or, in other words, if the request needs to add an instance of the model), it uses MyModelCreationForm. This is the same method that django developers use in django.contrib.auth.admin to retrieve their user form and UserCreation fields. (Look aside the source code for this example)

Finally, models will look something like this in model.py:

 class MyModel(models.Model): *** field definitions *** other = models.ManytoManyField(OtherRelatedModel, null=True, blank=True) class OtherRelatedModel(models.Model): other_id = model.AutoField(primary_key=True) *** more field definitions *** 

The only reason I included models is that you can see the definition of many for many fields in the MyModel class. Y < no null and empty for True. Just remember that if you do not, you will either have to give them a default value in the definition, or set them in the save () function in MyModelCreationForm.

Hope this helps! (If this is completely wrong, please correct me! I need to learn too.)

-Thanks

+1
source share

All Articles