Django: “limit_choices_to” doesn't work on ManyToManyField

后端 未结 2 1197
醉酒成梦
醉酒成梦 2021-01-05 16:52

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

I have two models:

class MemberPhoto(ImageMode         


        
相关标签:
2条回答
  • 2021-01-05 17:39

    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

    0 讨论(0)
  • 2021-01-05 17:52

    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!

    0 讨论(0)
提交回复
热议问题