Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In django-admin, how can I set filter_horizontal as the default?

The default widget for ManyToManyFields in django-admin is difficult to use. I can set filter_horizontal on individual fields and get a much nicer widget.

How can I set filter_horizontal as the default on all ManyToManyFields?

(I'd also be happy with filter_vertical of course.)

I've searched around for a solution and didn't find anything on Google or SO. I can think of how to do this with some meta-programming, but if someone already did this or if it's in Django somewhere, I'd love to hear about it.

like image 875
Jerome Baum Avatar asked Oct 10 '22 02:10

Jerome Baum


1 Answers

The best way to modify classes defined in pre-existing code is to use a mixin. You need to modify the formfield_for_manytomany method of ModelAdmin class; the method is defined in BaseModelAdmin.

Add the following code in a module that's guaranteed to run when your Django server starts up [a models.py of one of your own apps]:

from django.contrib.admin.options import ModelAdmin
from django.contrib.admin import widgets
class CustomModelAdmin:
    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
        """
        Get a form Field for a ManyToManyField.
        """
        # If it uses an intermediary model that isn't auto created, don't show
        # a field in admin.
        if not db_field.rel.through._meta.auto_created:
            return None
        db = kwargs.get('using')

        if db_field.name in self.raw_id_fields:
            kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
            kwargs['help_text'] = ''
        else:
            kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, False) # change second argument to True for filter_vertical

        return db_field.formfield(**kwargs)

ModelAdmin.__bases__ = (CustomModelAdmin,) + ModelAdmin.__bases__

Note (27 Aug 2019):

I'm fully aware of how subclassing/inheritance works, and that's best practice for solving problems like this. However, as I've reiterated in the comments below, subclassing will not solve the OP's problem as stated ie. making filter_horizontal or filter_vertical the default. With subclassing, not only will you need to register your subclass for all your models, you'll have to unregister each ModelAdmin subclass that's registered in builtin Django apps and third-party apps you've installed, then register your new ModelAdmin subclasses. So for example for Django's builtin User model ...

admin.site.unregister(User)
class CustomModelAdmin(admin.ModelAdmin):
    """ Add your changes here """
admin.site.register(User, CustomModelAdmin)

... then repeat similar code for all Django apps and third-party apps you've installed. I don't think this is what the OP wanted, hence my answer.

like image 116
Simon Kagwi Avatar answered Oct 12 '22 09:10

Simon Kagwi