106

I have

class Cab(models.Model):
    name  = models.CharField( max_length=20 )
    descr = models.CharField( max_length=2000 )

class Cab_Admin(admin.ModelAdmin):
    ordering     = ('name',)
    list_display = ('name','descr', )
    # what to write here to make descr using TextArea?

admin.site.register( Cab, Cab_Admin )

how to assign TextArea widget to 'descr' field in admin interface?

upd:
In Admin interface only!

Good idea to use ModelForm.

Soviut
  • 88,194
  • 49
  • 192
  • 260
maxp
  • 5,454
  • 7
  • 28
  • 30

9 Answers9

109

You will have to create a forms.ModelForm that will describe how you want the descr field to be displayed, and then tell admin.ModelAdmin to use that form. For example:

from django import forms
class CabModelForm( forms.ModelForm ):
    descr = forms.CharField( widget=forms.Textarea )
    class Meta:
        model = Cab

class Cab_Admin( admin.ModelAdmin ):
    form = CabModelForm

The form attribute of admin.ModelAdmin is documented in the official Django documentation. Here is one place to look at.

IntrepidDude
  • 1,053
  • 3
  • 12
  • 21
ayaz
  • 10,406
  • 6
  • 33
  • 48
  • 7
    For simply replacing a form widget, you don't have to create your own entire form. You can just override `formfield_for_dbfield` on your `ModelAdmin`, see my answer below. – Carl Meyer Dec 02 '13 at 20:25
  • 1
    This gives `django.core.exceptions.ImproperlyConfigured: Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is prohibited` – John Wu Feb 23 '16 at 15:55
  • 4
    Starting from Django 1.7 you need to add also `fields = '__all__'` under `class Meta:`. – skoll May 04 '16 at 11:43
  • 2
    You don't need to create the form yourself, you can override `get_form` method. See [my answer](https://stackoverflow.com/a/50521580/52499). – x-yuri May 25 '18 at 04:29
  • beeter approach is to use Meta class Meta: model = Message widgets = { 'text': forms.Textarea(attrs={'cols': 80, 'rows': 20}), } – Amulya Acharya Mar 25 '19 at 04:20
  • Thanks a lot, that's the only serious answer that may be found for this question. Just note that the Meta of the Form class must contain a declaration of "fields", as already said here. – Ofer Jun 15 '23 at 13:40
67

For this case, the best option is probably just to use a TextField instead of CharField in your model. You can also override the formfield_for_dbfield method of your ModelAdmin class:

class CabAdmin(admin.ModelAdmin):
    def formfield_for_dbfield(self, db_field, **kwargs):
        formfield = super(CabAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        if db_field.name == 'descr':
            formfield.widget = forms.Textarea(attrs=formfield.widget.attrs)
        return formfield
Carl Meyer
  • 122,012
  • 20
  • 106
  • 116
  • is there any downside in this compared to the selected answer? this looks cleaner to implement as we don't need to create a new ModelForm... – Filipe Pina Feb 23 '12 at 10:30
  • 4
    replaced `formfield.widget = forms.Textarea()` with `formfield.widget = forms.Textarea(attrs=formfield.widget.attrs)` to keep all attributes automatically generated for the TextField, Thanks! – Filipe Pina Feb 23 '12 at 10:53
  • 2
    I don't know why the other answer is accepted over this one; I think if all you're doing is replacing a widget this is simpler. But either will work fine. – Carl Meyer Feb 23 '12 at 15:20
  • This worked perfectly, thanks. The super() invocation seems a bit verbose though, is there a simpler way to do it? – rjh Apr 25 '15 at 07:08
  • 2
    @rjh In py3 you can eliminate everything inside the parens and just use `super()`. Otherwise no. – Carl Meyer Apr 25 '15 at 14:27
  • @CarlMeyer : is there any way I can keep all attributes of Charfield in textarea plus new attributes like cols and rows. – Anurag Rana Jan 17 '17 at 17:15
  • You should also add: `admin.site.register(Cab, CabAdmin)` after the class definition – nbeuchat Jun 22 '17 at 02:51
  • Is `formfield_for_dbfield` documented? – x-yuri May 25 '18 at 04:21
  • Using a `TextField` instead of a `CharField` on the model is a database-level change, and may have unintended consequences. There's a reason they're separate fields. Overriding the widget is the proper way to do this. – Adam Barnes Oct 02 '18 at 06:26
36

Ayaz has pretty much spot on, except for a slight change(?!):

class MessageAdminForm(forms.ModelForm):
    class Meta:
        model = Message
        widgets = {
            'text': forms.Textarea(attrs={'cols': 80, 'rows': 20}),
        }

class MessageAdmin(admin.ModelAdmin):
    form = MessageAdminForm
admin.site.register(Message, MessageAdmin)

So, you don't need to redefine a field in the ModelForm to change it's widget, just set the widgets dict in Meta.

Gezim
  • 7,112
  • 10
  • 62
  • 98
  • 1
    This does retain more "automatic" properties than Ayaz's answer, but any tip on how to keep the field length validation as well? – Filipe Pina Feb 23 '12 at 10:44
23

You don't need to create the form class yourself:

from django.contrib import admin
from django import forms

class MyModelAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        kwargs['widgets'] = {'descr': forms.Textarea}
        return super().get_form(request, obj, **kwargs)

admin.site.register(MyModel, MyModelAdmin)

See ModelAdmin.get_form.

x-yuri
  • 16,722
  • 15
  • 114
  • 161
14

You can subclass your own field with needed formfield method:

class CharFieldWithTextarea(models.CharField):

    def formfield(self, **kwargs):
        kwargs.update({"widget": forms.Textarea})
        return super(CharFieldWithTextarea, self).formfield(**kwargs)

This will take affect on all generated forms.

Ashish Gupta
  • 2,574
  • 2
  • 29
  • 58
Alex Koshelev
  • 16,879
  • 2
  • 34
  • 28
8

If you are trying to change the Textarea on admin.py, this is the solution that worked for me:

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

from books.models import Book

class BookForm(forms.ModelForm):
    description = forms.CharField( widget=forms.Textarea(attrs={'rows': 5, 'cols': 100}))
    class Meta:
        model = Book

class BookAdmin(admin.ModelAdmin):
    form = BookForm

admin.site.register(Book, BookAdmin)

If you are using a MySQL DB, your column length will usually be autoset to 250 characters, so you will want to run an ALTER TABLE to change the length in you MySQL DB, so that you can take advantage of the new larger Textarea that you have in you Admin Django site.

Aaron Lelevier
  • 19,850
  • 11
  • 76
  • 111
5

Instead of a models.CharField, use a models.TextField for descr.

mipadi
  • 398,885
  • 90
  • 523
  • 479
  • 4
    Unfortunately, max_length= is totally ignored by Django on a TextField, so when you need field length validation (such as letting secretaries enter event summaries) the only way to get it is to use a CharField. But a CharField is way to short to type 300 chars into. Hence the ayaz solution. – shacker Apr 21 '09 at 23:45
  • 3
    This isn't a good answer because it changes the database. The point of changing the widget is to change how the field is displayed, not to change the database schema –  Feb 14 '13 at 21:11
  • 1
    It's a great answer for someone (like me) who is learning how to use Django and would have set up the database differently in the first place if I'd known better. – Andy Swift Jan 15 '17 at 13:17
5

You can use models.TextField for this purpose:

class Sample(models.Model):
    field1 = models.CharField(max_length=128)
    field2 = models.TextField(max_length=1024*2)   # Will be rendered as textarea
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
vk-code
  • 950
  • 12
  • 22
3

Wanted to expand on Carl Meyer's answer, which works perfectly till this date.

I always use TextField instead of CharField (with or without choices) and impose character limits on UI/API side rather than at DB level. To make this work dynamically:

from django import forms
from django.contrib import admin


class BaseAdmin(admin.ModelAdmin):
    """
    Base admin capable of forcing widget conversion
    """
    def formfield_for_dbfield(self, db_field, **kwargs):
        formfield = super(BaseAdmin, self).formfield_for_dbfield(
            db_field, **kwargs)

        display_as_charfield = getattr(self, 'display_as_charfield', [])
        display_as_choicefield = getattr(self, 'display_as_choicefield', [])

        if db_field.name in display_as_charfield:
            formfield.widget = forms.TextInput(attrs=formfield.widget.attrs)
        elif db_field.name in display_as_choicefield:
            formfield.widget = forms.Select(choices=formfield.choices,
                                            attrs=formfield.widget.attrs)

        return formfield

I have a model name Post where title, slug & state are TextFields and state has choices. The admin definition looks like:

@admin.register(Post)
class PostAdmin(BaseAdmin):
    list_display = ('pk', 'title', 'author', 'org', 'state', 'created',)
    search_fields = [
        'title',
        'author__username',
    ]
    display_as_charfield = ['title', 'slug']
    display_as_choicefield = ['state']

Thought others looking for answers might find this useful.

tarequeh
  • 1,799
  • 18
  • 18