Django: добавляем в AdminDateWidget шорткат для инкремента месяца

Делюсь виджетом AdminMonthProlongWidget, который к стандартным для поля ввода даты шорткатам, таким как «Сегодня», «Выберите дату» (иконка), добавляет шорткат «+ месяц».

Для использования необходимо:

  • разместить файлы monthProlongShortcut.js и dateParser.js в директории со статикой (исходники ниже),
  • добавить код класса AdminMonthProlongWidget в модуль (например, widgets.py),
  • назначить виджет нужным полям типа DateField (можно в подклассе Meta для формы).

Исходные коды AdminMonthProlongWidget

monthProlongShortcut.js

/*global get_format,parse_date,quickElement*/
// Inserts month prolong shortcut button after all of the following:
//     <input type="text" class="vDateField">

(function () {
    "use strict";

    class MonthProlongShortcut {
        constructor(dateField) {
            this.dateField = dateField;
            this.updateValue();
            this.insertLink();
            this.dateField.addEventListener("change", () => this.updateValue());
        }

        formatDate(dateObj) {
            return dateObj.strftime(get_format('DATE_INPUT_FORMATS')[0]);
        }

        updateValue() {
            const dateString = this.dateField.value;
            if (dateString == null || dateString == '') {
                return false;
            }
            const dateObj = parseDate(dateString);
            this.dateField.value = (typeof dateObj == 'object' ? this.formatDate(dateObj) : dateObj);
        }

        insertLink() {
            const selector = '.' + DateTimeShortcuts.shortCutsClass;
            const shortcutsDiv = this.dateField.parentNode.querySelector(selector);
            shortcutsDiv.appendChild(document.createTextNode('\u00A0|\u00A0'));
            const prolongLink = quickElement('a', shortcutsDiv, '+ мес', 'href', '#');
            prolongLink.addEventListener("click", () => this.incrementMonth());
        }

        incrementMonth() {
            const dateObj = parseDate(this.dateField.value);
            if (typeof dateObj != 'object') {
                return false;
            }
            dateObj.setMonth(dateObj.getMonth() + 1);
            this.dateField.value = this.formatDate(dateObj);
        }
    }

    window.addEventListener('load', () => {
        const dateFields = document.querySelectorAll('input.vDateField');
        if (dateFields == null) {
            return false;
        }
        dateFields.forEach((dateField) => new MonthProlongShortcut(dateField));
    });

})();

dateParser.js

Код парсера строки с датой уже упоминался.

(function () {
    "use strict";
    function parseDate(dateString) {
        if (!dateString) {
            return new Date();
        }

        dateString = dateString.match(/(?:(\d{4})([\-\/.])([0-3]?\d)\2([0-3]?\d)|([0-3]?\d)([\-\/.])([0-3]?\d)\6(\d{4}))(?:\s+([012]?\d)([:hap])([0-5]\d))?/i) || [dateString];
        dateString.forEach((v, i, a) => {
            a[i] = v && v.match(/^\d+$/) ? parseInt(v, 10) : (v || 0);
        });

        if (dateString.length > 1) {
            if (!dateString[1]) {
                dateString[1] = dateString[8];
                dateString[2] = dateString[6];
                dateString[3] = dateString[7];
                dateString[4] = dateString[5];
            }

            if (dateString[3] > 12 && dateString[4] < 13) {
                dateString[0] = dateString[3];
                dateString[3] = dateString[4];
                dateString[4] = dateString[0];
            }

            if (dateString[10] === 'P' || dateString[10] === 'p' && dateString[9] < 13) {
                dateString[9] = (dateString[9] + 12) % 24;
            }

            if (dateString[3] > 12 || dateString[4] > ([0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][dateString[3]])) {
                return dateString[0];
            }

            return new Date(dateString[1], dateString[3] - 1, dateString[4], dateString[9], dateString[11], 0, 0);
        }
        return dateString[0];
    }
    window.parseDate = parseDate;
})();

widgets.py

from django.contrib.admin.widgets import AdminDateWidget


class AdminMonthProlongWidget(AdminDateWidget):
    class Media:
        js = (
            'core/js/dateParser.js',
            'core/js/monthProlongShortcut.js',
        )

forms.py

from core.widgets import AdminMonthProlongWidget


class MyForm(forms.ModelForm):
    class Meta:
        widgets = {'expiration_date': AdminMonthProlongWidget}
    ...