how to resize django ManyToManyField widget on admin page
Asked Answered
C

3

9

I'm trying to resize a many to many field of django 1.8 admin.

models.py

from django.db import models

class Material(models.Model):
    type = models.CharField(max_length=20, primary_key=True)
    def __unicode__(self):
        return self.type


class Pen(models.Model):
    label = models.CharField(max_length=20, blank=True, default='')
    materials = models.ManyToManyField(Material)

admin.py

from django.contrib import admin
from django.conf.urls import url
from .models import *
from django.forms import SelectMultiple

@admin.register(Pen)
class PenAdmin(admin.ModelAdmin):
    filter_horizontal = ('materials',)
    #formfield_overrides = { models.ManyToManyField: {'widget': SelectMultiple(attrs={'size':'5', 'width': '50px', 'style': 'width:50px'})}, }

    def get_form(self, request, obj=None, **kwargs):
        form = super(PenAdmin, self).get_form(request, obj, **kwargs)
        form.base_fields['materials'].widget.attrs['style'] = 'width: 70px;'
        return form

    class Media:
        js = ("js/resize_fields.js",)

@admin.register(Material)
class MaterialAdmin(admin.ModelAdmin):
    pass

resize_fields.js

document.onload = function() {
  document.getElementById("id_materials_to").style.width="70px";
};

As you can see I tried 3 approaches:

1: using formfield_overrides. It did not make any difference.

2: loading a javascript to resize the html object. It's firing up but not resizing it because the script seems to run before the widget is loaded.

3: using get_form. As shown below, it only resizes one of the select boxes.

enter image description here

Does anyone know how to resize it horizontally?

UPDATE: I discovered that:

class PenAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.ManyToManyField: {'widget': SelectMultiple(attrs={'size':'5', 'style': 'color:blue;width:250px'})},

    }

would work since I removed filter_horizontal but I want to use filter_horizontal! If I'm not mistaken, the filter_horizontal option will use the MultiWidget which is apparently not passing the override attributes to sub-widgets. Still, I think there must be a solution where I can run a javascript after the page load. plz help

Criseyde answered 4/2, 2016 at 21:17 Comment(0)
S
9

You can resize the widget by overriding CSS styles.

You'll need to override the following CSS classes:

  1. .selector - this div contains both the widgets.

  2. .selctor-available - this div contains the widget on the left.

  3. .selector-chosen - this div contains the widget on the right.

First, create a CSS file called resize-widget.css inside your project's static directory. Add the following code to the file:

UPDATE: For Django v2, these CSS rules will not work. See the other answer posted below.

.selector-available,
.selector-chosen {
    width: 200px;
}

.selector .selector-available input {
    width: 184px;
}

.selector select {
    width: 200px;
}

.selector {
    width: 450px;
}

In admin.py, supply that CSS file via class Media:

@admin.register(Pen)
class PenAdmin(admin.ModelAdmin):
    filter_horizontal = ('materials',)

    class Media:
        css = {
            'all': ('resize-widget.css',), # if you have saved this file in `static/css/` then the path must look like `('css/resize-widget.css',)`
        }

Visit admin site. The select widget must look like this:

enter image description here

Stairs answered 14/2, 2016 at 10:38 Comment(0)
U
2

I tried xyres answer and found that it worked BUT some of the CSS rules were being overridden by other rules that used more specific selectors.

To fix this I modified resize-widget.css to the following:

.form-horizontal .control-group .controls .selector {
    width: 100%;
}

.form-horizontal .control-group .controls .selector .selector-available,
.form-horizontal .control-group .controls .selector .selector-chosen {
    max-width: 45%;
}

.form-horizontal .control-group .controls .selector select {
    min-height: 200px;
}

These more specific rules were not being overridden and made it work for me.

Upcoming answered 5/1, 2018 at 4:45 Comment(1)
This should be the preferred solution for newer Django versions.Stairs
Z
0

There is an easier way to do it, is to override the formfield_for_manytomany

def formfield_for_manytomany(self, db_field, request, **kwargs):
        form_field = super().formfield_for_manytomany(db_field, request, **kwargs)
        if db_field.name in [*self.filter_horizontal]:
            form_field.widget.attrs={'size': '10'}
        return form_field
Zetana answered 12/8, 2020 at 5:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.