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
Tags:
limit_choices_to ManyToMany ManyToManyField admin inline formfield_for_manytomany
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. ManyToManyField no syncdb by powerfox 7 years, 7 months ago
  2. ManyToManyField that can be set even if it relies on a intermediary model. by quiesagua 4 years, 2 months ago
  3. Find deletable objects by writefaruq 5 years, 2 months ago
  4. Admin action for a "CSV Export" with ManyToManyField by CarlosRodriguez 2 years, 2 months ago
  5. RelatedNullFilterSpec: django-admin custom filter all/null/not null/choices by Codeko 5 years, 10 months ago

Comments

mhulse (on May 24, 2011):
<p>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???</p> <p>Thanks! M</p>

#

normic (on June 17, 2015):
<p>@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".</p> <p>Note for everyone: This still works with Django 1.8</p>

#

Alcolo47 (on November 20, 2015):
<p>That's work fine for me. But I made some modifications:</p>
  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:
<p>Code:</p> <pre>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) </pre> <p>Sample:</p> <pre>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) </pre>

#

Alcolo47 (on November 20, 2015):
<p>Oups: Corrected sample:</p> <pre>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) </pre>

#

Please login first before commenting.