35

I have a custom TagField form field.

class TagField(forms.CharField):
    def __init__(self, *args, **kwargs):
        super(TagField, self).__init__(*args, **kwargs)
        self.widget = forms.TextInput(attrs={'class':'tag_field'})

As seen above, it uses a TextInput form field widget. But in admin I would like it to be displayed using Textarea widget. For this, there is formfield_overrides hook but it does not work for this case.

The admin declaration is:

class ProductAdmin(admin.ModelAdmin):
    ...
    formfield_overrides = {
        TagField: {'widget': admin.widgets.AdminTextareaWidget},
    }

This has no effect on the form field widget and tags are still rendered with a TextInput widget.

Any help is much appreciated.

--
omat

onurmatik
  • 5,105
  • 7
  • 42
  • 67

4 Answers4

51

The django admin uses custom widgets for many of its fields. The way to override fields is to create a Form for use with the ModelAdmin object.

# forms.py

from django import forms
from django.contrib import admin

class ProductAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ProductAdminForm, self).__init__(*args, **kwargs)
        self.fields['tags'].widget = admin.widgets.AdminTextareaWidget()

Then, in your ModelAdmin object, you specify the form:

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

class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm

admin.site.register(Product, ProductAdmin)

You can also override the queryset at this time: to filter objects according to another field in the model, for instance (since limit_choices_to cannot handle this)

Matthew Schinckel
  • 35,041
  • 6
  • 86
  • 121
  • This didn't work for me, you need to pass an instance of the widget, rather than the class. The instance worked perfectly, though. – Stavros Korokithakis Jun 25 '13 at 16:29
  • 3
    I typically would create custom admin forms in the admin.py and not mix them in with forms.py. This reduces any mixup for other developers and keeps all admin specific items in the same .py file. – MaestroFJP Jul 09 '14 at 03:42
  • Good point @MaestroFJP. I've been doing just that in my newer code. Or, have a submodule of forms/admin.py, if you have really complicated forms, and lots of them. – Matthew Schinckel Jul 16 '14 at 13:16
  • 1
    Question from a lowly humble newbie: What is `'tags'` in this example? – Jarad Dec 11 '17 at 21:57
  • The field name - it's an assumption that's what the field name is in that form. – Matthew Schinckel Dec 12 '17 at 06:31
43

You can override field widgets by extending the Meta class of a ModelForm since Django 1.2:

from django import forms
from django.contrib import admin


class ProductAdminForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = '__all__'  # edit: django >= 1.8
        widgets = {
            'tags': admin.widgets.AdminTextareaWidget
        }


class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm

https://docs.djangoproject.com/en/stable/topics/forms/modelforms/#overriding-the-default-fields

Florian
  • 2,562
  • 5
  • 25
  • 35
Murat Çorlu
  • 8,207
  • 5
  • 53
  • 78
  • 7
    Starting with Django 1.8 you should add `fields = '__all__'` to `Meta`. Vide http://stackoverflow.com/a/28306347/1161025. – maciek Mar 25 '15 at 08:27
7

For a specific field not a kind of fields I use:

Django 2.1.7

class ProjectAdminForm(forms.ModelForm):
    class Meta:
        model = Project
        fields = '__all__'
        widgets = {
            'project_description': forms.Textarea(attrs={'cols': 98})
        }
class ProjectAdmin(admin.ModelAdmin):
    form = ProjectAdminForm

Thanks, @Murat Çorlu

C.K.
  • 4,348
  • 29
  • 43
3

Try to change your field like this:

class TagField(forms.CharField):
    def __init__(self, *args, **kwargs):
        self.widget = forms.TextInput(attrs={'class':'tag_field'})
        super(TagField, self).__init__(*args, **kwargs)

This would allow to use the widget which comes from **kwargs. Otherwise your field will always use form.TextInput widget.

Andrey Fedoseev
  • 5,222
  • 1
  • 24
  • 19
  • i applied the change but now all tag fields are rendered as Textarea. – onurmatik Aug 12 '10 at 18:33
  • Looking at django.forms.fields I found that the correct way to specify the default widget for a field is: class TagField(form.CharField): widget = forms.TextInput def widget_attrs(self, widget): return {'class': 'tag_field'} You don't have override the `__init__` method. Try this. – Andrey Fedoseev Aug 12 '10 at 18:43
  • thanks but still no luck. all tag fields are displayed as Textarea, all over the admin, not just for ProductAdmin. – onurmatik Aug 13 '10 at 12:25