4

How can I change the default widget for DateField instances in Django ADMIN?

I am aware of how to do this for a ModelForm - How do you change the default widget for all Django date fields in a ModelForm? - by providing formfield_callback that checks the field type and then provides a form field with the new widget.

However, some of my ModelAdmin's may already have a custom ModelForm, some haven't. I don't want to provide/change a ModelForm wherever there's a date field (that's the point).

A custom ModelAdmin which could be used for subclassing would be ok, though. How can a ModelAdmin change the date fields of its form, independent of whether a custom ModelForm has been provided or not?

Maybe a ModelAdmin with get_form() calling its super then setting the form's formfield_callback? Seems a little convoluted, not sure about the timing, and also then there's three things to be aware of if any ModelAdmin needs further customization...

P.S.: I'm asking this after https://stackoverflow.com/questions/8484811/jquery-datepicker-in-django-admin was erroneously closed for being an "exact duplicate" of the above-mentioned ModelForm question. I hope the difference is now clear.

Btw my personal motivation is the year navigation of django admin's default datepicker. Really bad for birthdays... :-) yes there's a three-year old ticket https://code.djangoproject.com/ticket/9388

Danny W. Adair
  • 12,498
  • 4
  • 43
  • 49

2 Answers2

2

I was able to customize the default date widget in the Django admin. I followed two tutorials (from Simple is Better than Complex and Django Custom Widget)

  1. Write a custom widget in a file called widgets.py inside your Django app
from django.forms import DateInput


class CustomDatePickerInput(DateInput):
    template_name = "app_name/widgets/custom_datepicker.html"

    class Media:
        css = {
            "all": (
                "https://cdnjs.cloudflare.com/ajax/libs/datepicker/0.6.5/datepicker.min.css",
            )
        }
        js = (
            "https://code.jquery.com/jquery-3.4.1.min.js",
            "https://cdnjs.cloudflare.com/ajax/libs/datepicker/0.6.5/datepicker.min.js",
            "https://cdnjs.cloudflare.com/ajax/libs/datepicker/0.6.5/i18n/datepicker.es-ES.min.js",
        )

Notice that the CSS and JS files that you may need should be added in the internal Media class as shown in the code above. In this case it is adding styles for the custom date picker.

  1. Define the template for your widget, for more date picker's options see here, put this file in app_name/templates/app_name/widgets/custom_datepicker.html or make sure that this template matches the value of template_name in your custom widget:
{% load i18n %}

{% include "django/forms/widgets/input.html" %}
{% get_current_language as LANGUAGE_CODE %}

<script>
  $(function () {
    const DATE_PICKER_OPTIONS = {
      'en-us': {
        format: 'yyyy-mm-dd',
        language: ''
      },
      'es': {
        format: 'dd/mm/yyyy',
        language: 'es-ES'
      }
    };
    let options = DATE_PICKER_OPTIONS['{{ LANGUAGE_CODE }}'];
    $("input[name='{{ widget.name }}']").datepicker({
      format: options.format,
      language: options.language
    });
  });
</script>
  1. You need to indicate what widget to use in date field when registering your model in the Django admin (use formfield_overrides)
@admin.register(YourModel)
class YourModelAdmin(admin.ModelAdmin):
    formfield_overrides = {models.DateField: {"widget": CustomDatePickerInput}}

Once this is completed, you will have a custom widget for dates in the Django admin. Keep in mind that this is just a guide, you can go further by reviewing the two tutorials I indicated.

Note: the implementation of this widget is also supporting internationalization.

lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
1

ModelAdmin can only change the widget of the form it renders, so it would be difficult to override the widgets of a custom form passed to it - at least I can't think of a clean way to do it.

I can see a few options for you, depending on how/what you want to customize.

  1. Modify the javascript that does the magic for the widget. This seems easy enough if your change can be handled there. Simply provide your own js/calendar.js and js/admin/DateTimeShortcuts.js the same way you would override the admin's templates. This has the added bonus of being upgrade friendly; since you are not modifying the django code base; also the change would be immediate for all forms that use the widget.

  2. Customize the built-in django widget to do what you want. This has the benefit of instantly working without any modifications of your existing code, but is not upgrade friendly unless you are careful about patching sources. The source is at django.contrib.admin.widgets

  3. Provide your own custom widget - but then you are back to the problem of going through the code and manually overriding all widget instances with your custom one.

  4. Use something like grappelli and modify the templates/js to do what you want. Added bonus here is that you'll get lots of other functionality for free (like the ability to have a dashboard, if you plug it in with django-admin-tools).

django uses jquery extensively in their admin and they have their own jquery namespace (django.jQuery); so if you plan on doing some custom widgets make sure you use the right namespace to avoid strange problems.

Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284