Resize fields in Django Admin

Question:

Django tends to fill up horizontal space when adding or editing entries on the admin, but, in some cases, is a real waste of space, when, i.e., editing a date field, 8 characters wide, or a CharField, also 6 or 8 chars wide, and then the edit box goes up to 15 or 20 chars.

How can I tell the admin how wide a textbox should be, or the height of a TextField edit box?

Asked By: Andor

||

Answers:

You can set arbitrary HTML attributes on a widget using its “attrs” property.

You can do this in the Django admin using formfield_for_dbfield:

class MyModelAdmin(admin.ModelAdmin):
  def formfield_for_dbfield(self, db_field, **kwargs):
    field = super(ContentAdmin, self).formfield_for_dbfield(db_field, **kwargs)
    if db_field.name == 'somefield':
      field.widget.attrs['class'] = 'someclass ' + field.widget.attrs.get('class', '')
    return field

or with a custom Widget subclass and the formfield_overrides dictionary:

class DifferentlySizedTextarea(forms.Textarea):
  def __init__(self, *args, **kwargs):
    attrs = kwargs.setdefault('attrs', {})
    attrs.setdefault('cols', 80)
    attrs.setdefault('rows', 5)
    super(DifferentlySizedTextarea, self).__init__(*args, **kwargs)

class MyModelAdmin(admin.ModelAdmin):
  formfield_overrides = { models.TextField: {'widget': DifferentlySizedTextarea}}
Answered By: Carl Meyer

A quick and dirty option is to simply provide a custom template for the model in question.

If you create a template named admin/<app label>/<class name>/change_form.html then the admin will use that template instead of the default. That is, if you’ve got a model named Person in an app named people, you’d create a template named admin/people/person/change_form.html.

All the admin templates have an extrahead block you can override to place stuff in the <head>, and the final piece of the puzzle is the fact that every field has an HTML id of id_<field-name>.

So, you could put something like the following in your template:

{% extends "admin/change_form.html" %}

{% block extrahead %}
  {{ block.super }}
  <style type="text/css">
    #id_my_field { width: 100px; }
  </style>
{% endblock %}
Answered By: jacobian

If you want to change the attributes on a per-field instance, you can add the “attrs” property directly in to your form entries.

for example:

class BlogPostForm(forms.ModelForm):
    title = forms.CharField(label='Title:', max_length=128)
    body = forms.CharField(label='Post:', max_length=2000, 
        widget=forms.Textarea(attrs={'rows':'5', 'cols': '5'}))

    class Meta:
        model = BlogPost
        fields = ('title', 'body')

The “attrs” property basically passes along the HTML markup that will adjust the form field. Each entry is a tuple of the attribute you would like to override and the value you would like to override it with. You can enter as many attributes as you like as long as you separate each tuple with a comma.

Answered By: Jonathan

The best way I found is something like this:

class NotificationForm(forms.ModelForm):
    def __init__(self, *args, **kwargs): 
        super(NotificationForm, self).__init__(*args, **kwargs)
        self.fields['content'].widget.attrs['cols'] = 80
        self.fields['content'].widget.attrs['rows'] = 15
        self.fields['title'].widget.attrs['size'] = 50
    class Meta:
        model = Notification

Its much better for ModelForm than overriding fields with different widgets, as it preserves name and help_text attributes and also default values of model fields, so you don’t have to copy them to your form.

Answered By: kurczak

It’s well described in Django FAQ:

Q: How do I change the attributes for a widget on a field in my model?

A: Override the formfield_for_dbfield in the ModelAdmin/StackedInline/TabularInline class

class MyOtherModelInline(admin.StackedInline):
    model = MyOtherModel
    extra = 1

    def formfield_for_dbfield(self, db_field, **kwargs):
        # This method will turn all TextFields into giant TextFields
        if isinstance(db_field, models.TextField):
            return forms.CharField(widget=forms.Textarea(attrs={'cols': 130, 'rows':30, 'class': 'docx'}))
        return super(MyOtherModelInline, self).formfield_for_dbfield(db_field, **kwargs)
Answered By: HardQuestions

You should use ModelAdmin.formfield_overrides.

It is quite easy – in admin.py, define:

from django.forms import TextInput, Textarea
from django.db import models

class YourModelAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.CharField: {'widget': TextInput(attrs={'size':'20'})},
        models.TextField: {'widget': Textarea(attrs={'rows':4, 'cols':40})},
    }

admin.site.register(YourModel, YourModelAdmin)
Answered By: jki

I had a similar problem with TextField. I’m using Django 1.0.2 and wanted to change the default value for ‘rows’ in the associated textarea. formfield_overrides doesn’t exist in this version. Overriding formfield_for_dbfield worked but I had to do it for each of my ModelAdmin subclasses or it would result in a recursion error. Eventually, I found that adding the code below to models.py works:

from django.forms import Textarea

class MyTextField(models.TextField):
#A more reasonably sized textarea                                                                                                            
    def formfield(self, **kwargs):
         kwargs.update(
            {"widget": Textarea(attrs={'rows':2, 'cols':80})}
         )
         return super(MyTextField, self).formfield(**kwargs)

Then use MyTextField instead of TextField when defining your models. I adapted it from this answer to a similar question.

Answered By: msdin

You can always set your fields sizes in a custom stylesheet and tell Django to use that for your ModelAdmin class:

class MyModelAdmin(ModelAdmin):
    class Media:
        css = {"all": ("my_stylesheet.css",)}
Answered By: Gonzalo

Same answer as msdin but with TextInput instead of TextArea:

from django.forms import TextInput

class ShortTextField(models.TextField):
    def formfield(self, **kwargs):
         kwargs.update(
            {"widget": TextInput(attrs={'size': 10})}
         )
         return super(ShortTextField, self).formfield(**kwargs)
Answered By: dan-klasson

for 1.6, using forms I had to specify the attributes of the textarea inside the charfield:

test1 = forms.CharField(max_length=400, widget=forms.Textarea( attrs={'rows':'2', 'cols': '10'}),  initial='', help_text=helptexts.helptxt['test'])
Answered By: deddu

To change the width for a specific field.

Made via ModelAdmin.get_form:

class YourModelAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        form = super(YourModelAdmin, self).get_form(request, obj, **kwargs)
        form.base_fields['myfield'].widget.attrs['style'] = 'width: 45em;'
        return form
Answered By: alanjds

Here is a simple, yet flexible solution. Use a custom form to override some widgets.

# models.py
class Elephant(models.Model):
    name = models.CharField(max_length=25)
    age = models.IntegerField()

# forms.py
class ElephantForm(forms.ModelForm):

    class Meta:
        widgets = {
            'age': forms.TextInput(attrs={'size': 3}),
        }

# admin.py
@admin.register(Elephant)
class ElephantAdmin(admin.ModelAdmin):
    form = ElephantForm

The widgets given in ElephantForm will replace the default ones. The key is the string representation of the field. Fields not specified in the form will use the default widget.

Note that although age is an IntegerField we can use the TextInput widget, because unlike the NumberInput, TextInput accepts the size attribute.

This solution is described in this article.

Answered By: Eerik Sven Puudist

If you are working with a ForeignKey field that involves choices/options/a dropdown menu, you can override formfield_for_foreignkey in the Admin instance:

class YourNewAdmin(admin.ModelAdmin):
    ...

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == 'your_fk_field':
            """ For your FK field of choice, override the dropdown style """
            kwargs["widget"] = django.forms.widgets.Select(attrs={
                'style': 'width: 250px;'
            })

        return super().formfield_for_foreignkey(db_field, request, **kwargs)

More information on this pattern here and here.

Answered By: Matt-Heun Hong

And one more example too :

class SecenekInline(admin.TabularInline):
   model = Secenek
   # classes = ['collapse']
   def formfield_for_dbfield(self, db_field, **kwargs):
       field = super(SecenekInline, self).formfield_for_dbfield(db_field, **kwargs)
       if db_field.name == 'harf':
           field.widget = TextInput(attrs={'size':2})
       return field
   formfield_overrides = {
       models.TextField: {'widget': Textarea(attrs={'rows':2})},
   }
   extra = 2

If you want to edit only a specific fields size, you can use this.

Answered By: mevaka

For example, there is Product model below:

class Product(models.Model):
    name = models.CharField(max_length=50)
    description = models.TextField() 
    price = models.DecimalField(decimal_places=2, max_digits=5)
    quantity = models.PositiveSmallIntegerField()
    
    def __str__(self):
        return self.name

Then, using formfield_overrides or form below.

formfield_overrides:

# "admin.py"

from django.contrib import admin
from .models import Product
from django.db import models
from django import forms

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    formfield_overrides = { # Here
        models.CharField: {
            'widget': forms.TextInput(attrs={'size':'50'})
        },
        models.TextField: {
            'widget': forms.Textarea(attrs={'rows':'1', 'cols':'50'})
        },
        models.DecimalField: {
            'widget': forms.NumberInput(attrs={'style': 'width:50ch'})
        },
        models.PositiveSmallIntegerField: {
            'widget': forms.NumberInput(attrs={'style': 'width:50ch'})
        },
    }

form:

# "admin.py"

from django.contrib import admin
from .models import Product
from django.db import models
from django import forms

class ProductForm(forms.ModelForm):
    class Meta:
        widgets = {
            'name': forms.TextInput(attrs={'size':'50'}),
            'description': forms.Textarea(attrs={'rows':'1', 'cols':'50'}),
            'price' : forms.NumberInput(attrs={
                'style': 'width:50ch'
            }),
            'quantity' : forms.NumberInput(attrs={
                'style': 'width:50ch'
            })
        }

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    form = ProductForm # Here

form:

# "admin.py"

from django import forms
from django.contrib import admin
from .models import Product

class ProductForm(forms.ModelForm):
    name = forms.CharField(
        widget=forms.TextInput(attrs={'size':'50'})
    )
    description = forms.CharField(
        widget=forms.Textarea(attrs={'rows':'1', 'cols':'50'})
    )
    price = forms.CharField(
        widget=forms.NumberInput(attrs={'style':'width:50ch'})
    )
    quantity = forms.CharField(
        widget=forms.NumberInput(attrs={'style':'width:50ch'})
    )

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    form = ProductForm # Here

You can change the size of the fields in "Add" and "Change" pages as shown below:

enter image description here

enter image description here

In addition, in this case of only formfield_overrides which I recommend the most, you can change the size of the fields in "Change List" page as shown below:

# "admin.py"

from django.contrib import admin
from .models import Product
from django.db import models
from django import forms

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ('name', 'description', 'price', 'quantity')
    list_editable = ('name',) # Here
    list_display_links = ('description',)

    formfield_overrides = { # Here
        models.CharField: {
            'widget': forms.TextInput(attrs={'size':'50'})
        },
        models.TextField: {
            'widget': forms.Textarea(attrs={'rows':'1', 'cols':'50'})
        },
        models.DecimalField: {
            'widget': forms.NumberInput(attrs={'style': 'width:50ch'})
        },
        models.PositiveSmallIntegerField: {
            'widget': forms.NumberInput(attrs={'style': 'width:50ch'})
        },
    }

enter image description here

Answered By: Kai – Kazuya Ito