Django: “limit_choices_to” doesn't work on ManyToManyField

拈花ヽ惹草 提交于 2019-11-30 21:10:52
Kyle Duncan

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'm posting my working code here for anybody having the same problem. It seems from looking around that "limit_choices_to" may simply not be able to achieve what I wanted, and that customizing the form used by the admin 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 have to change is:

  1. the name of your model (in the above example it's "lock")
  2. the name of the ManyToManyField field in your model (in the above example it's "unlocked_photos")
  3. the name of the related model (in the above example it's "MemberPhoto")
  4. the name of the field you want to filter related objects by (in the above example it's "member")
  5. the value for the field you want to use to filter related objects by (it will start with "self.instance." and then be the name of the field, in the above example it's "user")
  6. And finally make sure your class names for the custom admin form and admin model all match up.

Hope this helps somebody!

wspurgin

To add to Kyle's answer,

Creating a custom form is the only way for to customize a many to many field in that way. But, like I discovered, that method only works if you are changing an instance of that model. (at least in Django 1.5)

This is because: self.instance will return Model object. (I know crazy concept) But if you are creating an instance of that model, since that model hasn't been created yet, the self.instance will return a DoesNotExist exception.

A way around this issue is 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 would create a separate fieldset 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 the get_form and get_fieldsets that if the obj is None (or in other words if the request is to add an instance of the model) it uses the MyModelCreationForm. This is the same method that the django developers use in django.contrib.auth.admin to get their custom UserCreation form and fieldsets. (Look in side the source code there for that example)

Finally the models would 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 the models is so you could see the many to many field definition in class MyModel. It doesn't have to have null and blank set to True. Just remember if you don't you will have to either assign them a default value in the definition or set them in the save() function in the MyModelCreationForm.

Hope this helps! (If it's utterly wrong please correct me! I need to learn too.)

-Thanks

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!