77

I have a DateTimeField field in my model. I wanted to display it as a checkbox widget in the Django admin site. To do this, I created a custom form widget. However, I do not know how to use my custom widget for only this one field.

The Django documentation explains how to use a custom widget for all fields of a certain type:

class StopAdmin(admin.ModelAdmin):
    formfield_overrides = {
        models.DateTimeField: {'widget': ApproveStopWidget }
    }

This is not granular enough though. I want to change it for only one field.

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
Belmin Fernandez
  • 8,307
  • 9
  • 51
  • 75

5 Answers5

139

Create a custom ModelForm for your ModelAdmin and add 'widgets' to its Meta class, like so:

class StopAdminForm(forms.ModelForm):
  class Meta:
    model = Stop
    widgets = {
      'field_name': ApproveStopWidget(),
    }
    fields = '__all__'

class StopAdmin(admin.ModelAdmin):
  form = StopAdminForm

Done!

Documentation for this is sort of non-intuitively placed in the ModelForm docs, without any mention to it given in the admin docs. See: Creating forms from models

coredumperror
  • 8,471
  • 6
  • 33
  • 42
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 5
    Unfortunately this only seems to work for the main "edit" page, and not on the list display, whereas `formfield_overrides` works for both. – Mechanical snail Sep 12 '12 at 02:40
  • 14
    And that warranted a downvote? Please take a moment to read "Vote Down" privilege description: http://stackoverflow.com/privileges/vote-down. I'm pretty positive my answer doesn't qualify as "an answer that is clearly and perhaps dangerously incorrect". – Chris Pratt Sep 12 '12 at 16:15
  • 1
    There's a range of opinions: http://meta.stackexchange.com/a/112664/146243, http://meta.stackexchange.com/a/46590/146243, http://meta.stackexchange.com/a/46596/146243. – Mechanical snail Sep 13 '12 at 00:20
  • 8
    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
  • 4
    ... and `approve_ts` means what? then repeated in next answers. There is no such thing in Django source, either in topic's question. – Sławomir Lenart Apr 26 '17 at 18:21
  • I updated `approve_ts` to `field_name` . Seems to have been a copy-paste that didn't get genericised. – coredumperror Aug 20 '22 at 08:02
36

After digging into the admin, model field and form field code, I believe the only way to carry out what I want is by creating a custom model field:

models.py

from django.db import models
from widgets import ApproveStopWidget

class ApproveStopModelField(models.DateTimeField):
    pass

class Stop(models.model):
    # Other fields
    approve_ts = ApproveStopModelField('Approve place', null=True, blank=True)

admin.py

from widgets import ApproveStopWidget
from models import ApproveStopModelField

class StopAdmin(admin.ModelAdmin):
    formfield_overrides = {
        ApproveStopModelField: {'widget': ApproveStopWidget }
    }

It gets the job done.

For the time being, I'll leave the question unanswered because I have the habit of missing the obvious. Perhaps some Django smartypants has a better solution.

Belmin Fernandez
  • 8,307
  • 9
  • 51
  • 75
  • 3
    For those wondering how to integrate this with South, the line you would need for this example is: `from south.modelsinspector import add_introspection_rule add_introspection_rules([], ["^appname\.models\.ApproveStopModelField"])` – yar Oct 19 '12 at 01:00
28

Override formfield_for_dbfield like thus:

class VehicleAdmin(admin.ModelAdmin):
    search_fields = ["name", "colour"]

    def formfield_for_dbfield(self, db_field, **kwargs):
        if db_field.name == 'colour':
            kwargs['widget'] = ColourChooserWidget
        return super(VehicleAdmin, self).formfield_for_dbfield(db_field,**kwargs)

(credit to http://www.kryogenix.org/days/2008/03/28/overriding-a-single-field-in-the-django-admin-using-newforms-admin/ )

Oli
  • 235,628
  • 64
  • 220
  • 299
Andy Baker
  • 21,158
  • 12
  • 58
  • 71
  • 6
    Nice. Adding a custom `ModelForm` for changing just a widget of one field seems like an overkill to me. – alekosot Sep 20 '16 at 08:59
  • Just tried this in Django 1.11.5 and it worked perfectly. My model has multiple TextFields and in the Admin change form I needed some to be regular – Stephen Blair Dec 06 '17 at 11:45
7

Django's ModelAdmin.get_changelist_form(self, request, **kwargs) will do the trick for the case of list_editable

class StopAdminForm(forms.ModelForm):
  class Meta:
    model = Stop
    widgets = {
      'approve_ts': ApproveStopWidget(),
    }

class StopAdmin(admin.ModelAdmin):
  form = StopAdminForm

  #just return the ModelForm class StopAdminForm
  def get_changelist_form(self, request, **kwargs):
        return StopAdminForm

Refer to Django Official documentation on this topic

I hope this will help

airstrike
  • 2,270
  • 1
  • 25
  • 26
Arya-2790306
  • 115
  • 1
  • 1
0

You can change the widget for only one field by assigning your widget to a field in a custom form and assigning the custom form to an admin as shown below:

# "admin.py"

class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        widgets = {
          'price': PriceWidget(),
        }
        fields = '__all__'

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    form = ProductForm
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129