Login

Limit queryset to objects related to parent in ManyToMany fields within admin inlines

Author:
DrMeers
Posted:
May 27, 2010
Language:
Python
Version:
1.2
Score:
4 (after 4 ratings)

formfield_for_manytomany allows you to limit the choices/queryset for a ManyToManyField, but without direct access to the parent object. This snippet stores a reference to the parent object in get_formset and allows limiting of ManyToManyFields to objects related to the same parent object. See ExampleInline for example usage.

If for some reason you have a ManyToManyField in a TabularInline, just change the template in the subclass.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from django.contrib import admin

class InlineWithLimitedManyToManyFields(admin.StackedInline):
    limited_manytomany_fields = {}
    
    def get_formset(self, request, obj=None, **kwargs):
        # Hack! Hook parent obj just in time to use in formfield_for_manytomany
        self.parent_obj = obj
        return super(InlineWithLimitedManyToManyFields, self).get_formset(
            request, obj, **kwargs)
    
    def formfield_for_manytomany(self, db_field, request, **kwargs):
        if db_field.name in self.limited_manytomany_fields.keys() and \
               hasattr(self,'parent_obj'):
            kwargs['queryset'] = db_field.rel.to.objects.filter(**{
                self.limited_manytomany_fields[db_field.name]: self.parent_obj
                })
        return super(
            InlineWithLimitedManyToManyFields, self).formfield_for_manytomany(
            db_field, request, **kwargs)


class ExampleInline(InlineWithLimitedManyToManyFields):
    model = ExampleModel
    limited_manytomany_fields = {
        'first_many_to_many_field_name':
            'field_name_for_foreignkey_in_related_model_to_parent',
        'second_many_to_many_field_name':
            'field_name_for_foreignkey_in_related_model_to_parent',
        }    

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

mhulse (on May 24, 2011):

Could someone provide an example of what the model should look like? I don't really understand what "field_name_for_foreignkey_in_related_model_to_parent" means; do I have to put a FK in the "inline" model???

Thanks! M

#

normic (on June 17, 2015):

@mhulse: field_name_for_foreignkey_in_related_model_to_parent means you should name the field which should be used for filtering the many to many, usually the ForeignKey field which connects your M2M with the "Parent".

Note for everyone: This still works with Django 1.8

#

Alcolo47 (on November 20, 2015):

That's work fine for me. But I made some modifications:

  1. Convert this class as mixin that can work with admin.ModelAdmin
  2. Add a function that can transform the parent_obj to the target object of the query:

Code:

class LimitedManyToManyFieldsMixin(object):
    limited_manytomany_fields = {}

    def get_formset(self, request, obj=None, **kwargs):
        # Hack! Hook parent obj just in time to use in formfield_for_manytomany
        self.parent_obj = obj
        return super(LimitedManyToManyFieldsMixin, self).get_formset(request, obj, **kwargs)

    def get_form(self, request, obj=None, **kwargs):
        # Hack! Hook parent obj just in time to use in formfield_for_manytomany
        self.parent_obj = obj
        return super(LimitedManyToManyFieldsMixin, self).get_form(request, obj, **kwargs)

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        if db_field.name in self.limited_manytomany_fields.keys() and hasattr(self,'parent_obj'):
            query_field = self.limited_manytomany_fields[db_field.name]
            obj = self.parent_obj
            if type(query_field) is tuple:
                query_field, obj_func = query_field
                obj = obj_func(obj)
            kwargs['queryset'] = db_field.rel.to.objects.filter(**{query_field: obj})

        return super(LimitedManyToManyFieldsMixin, self).formfield_for_manytomany(db_field, request, **kwargs)

Sample:

class Book(models.Model):
    title = models.CharField(max_length=200)

class Page(models.Model):
    book = models.ForeignKey(Book, related_name='pages')
    number = models.IntegerField()

class PrintQueue(models.Model):
    book = models.ForeignKey(Book, related_name='book_to_print')
    pages = models.ManyToManyField(Page, related_name='pages_to_print')

class PrintQueueAdmin(LimitedManyToManyFieldsMixin, admin.ModelAdmin):
    list_display = ('book',)
    filter_horizontal = ('pages',)
    limited_manytomany_fields = {'pages': ('book', lambda page: page.book)}
admin.site.register(PrintQueue, PrintQueueAdmin)

#

Alcolo47 (on November 20, 2015):

Oups: Corrected sample:

class Book(models.Model):
    title = models.CharField(max_length=200)

class Page(models.Model):
    book = models.ForeignKey(Book, related_name='pages')
    number = models.IntegerField()

class PrintQueue(models.Model):
    book_to_print = models.ForeignKey(Book)
    pages_to_print = models.ManyToManyField(Page)

class PrintQueueAdmin(LimitedManyToManyFieldsMixin, admin.ModelAdmin):
    list_display = ('book_to_print',)
    filter_horizontal = ('pages_to_print',)
    limited_manytomany_fields = {'pages_to_print': ('book', lambda printQueue: printQueue.book_to_print)}
admin.site.register(PrintQueue, PrintQueueAdmin)

#

Please login first before commenting.