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 in separate fields and get a much more pleasant view.

How to set filter_horizontal as the default value for all ManyToManyFields ?

(I would also be happy with filter_vertical ).

I searched for a solution and did not find anything in Google or SO. I can figure out how to do this with some metaprograms, but if someone has already done this or if it's somewhere in Django, I would like to hear about it.

+5
source share
2 answers

The best way to change the classes defined in existing code is to use mixin . You need to change the formfield_for_manytomany method of the formfield_for_manytomany class; The method is defined in BaseModelAdmin .

Add the following code to the module, which is guaranteed to work when you start your Django server [a models.py one of your own applications]:

 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 (August 27, 2019):

I am fully aware of how inheritance / inheritance works and what is best suited to solve such problems. However, as I have repeated in the comments below, subclasses will not solve the OP problem as indicated , i.e. doing filter_horizontal or filter_vertical by default . With the help of subclasses, you will need to not only register your subclass for all your models, but also unregister each subclass of ModelAdmin that is registered in the built-in Django applications and third-party applications that you installed, and then register new subclasses of ModelAdmin. So, for example, for the Django built-in custom model ...

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

... then repeat the same code for all Django applications and third-party applications that you have installed. I do not think that this is what the OP wanted, hence my answer.

+4
source

Still a bit hacky, but better than another answer that would cause all kinds of pain during Django upgrades. Inherit from this (not monkey patch):

 class BaseAdmin(models.Admin): def formfield_for_manytomany(self, db_field, request=None, **kwargs): """ Get a form Field for a ManyToManyField. Tweak so filter_horizontal control used by default. If raw_id or autocomplete are specified will take precedence over this. """ filter_horizontal_original = self.filter_horizontal self.filter_horizontal = (db_field.name,) form_field = super().formfield_for_manytomany(db_field, request=None, **kwargs) self.filter_horizontal = filter_horizontal_original return form_field @admin.register(AcmeModel) class AcmeModelAdmin(BaseAdmin): # Sub-classes can still specify raw_id, autocomplete, etc. # That will override our filter_horizontal defaulting. pass 

For some reason, setting filter_horizontal in __init__ does not work, and there is no get_filter_horizontal to override.

As mentioned above, avoid monkeys and just inherit from BaseClass . You and especially your colleagues will be grateful if you return to this after 6 months and cannot find it in the inheritance hierarchy.

0
source

Source: https://habr.com/ru/post/1388875/


All Articles