Как изменить виджет по умолчанию для всех полей даты Django в ModelForm?

Учитывая набор типовых моделей:

# Application A
from django.db import models
class TypicalModelA(models.Model):
    the_date = models.DateField()

 # Application B
from django.db import models
class TypicalModelB(models.Model):
    another_date = models.DateField()

...

Как можно изменить виджет по умолчанию для всех полей DateFields на собственный MyDateWidget?

Я спрашиваю, потому что хочу, чтобы в моем приложении был jQueryUI datepicker для ввода дат.

Я рассмотрел настраиваемое поле, которое расширяет django.db.models.DateField с помощью моего настраиваемого виджета. Это лучший способ реализовать такого рода повсеместные изменения? Такое изменение потребует специального импорта специального MyDateField в каждую модель, что является трудоемким, подверженным ошибкам разработчика (то есть несколько моделей.DateField пройдут), и, на мой взгляд, это кажется ненужным дублированием усилий. С другой стороны, мне не нравится изменять то, что можно считать канонической версией models.DateField.

Мысли и вклад приветствуются.


person Brian M. Hunt    schedule 19.03.2009    source источник


Ответы (6)


Вы можете объявить атрибут в своем ModelForm классе с именем formfield_callback. Это должна быть функция, которая принимает экземпляр модели Django Field в качестве аргумента и возвращает экземпляр формы Field, чтобы представить его в форме.

Затем все, что вам нужно сделать, это посмотреть, является ли переданное поле модели экземпляром DateField, и, если да, вернуть ваше настраиваемое поле / виджет. В противном случае поле модели будет иметь метод с именем formfield, который вы можете вызвать, чтобы вернуть его поле формы по умолчанию.

Итак, что-то вроде:

def make_custom_datefield(f):
    if isinstance(f, models.DateField):
        # return form field with your custom widget here...
    else:
        return f.formfield(**kwargs)

class SomeForm(forms.ModelForm)
    formfield_callback = make_custom_datefield

    class Meta:
        # normal modelform stuff here...
person James Bennett    schedule 19.03.2009
comment
+1, если это обычное поведение, которое вам нужно для нескольких форм с DateTimeFields, это СУХОЙ способ сделать это. - person Carl Meyer; 19.03.2009
comment
Отличный материал. Где документируется formfield_callback? - person Brian M. Hunt; 19.03.2009
comment
Спасибо за отличную идею! Внутри класса SomeForm у меня есть свойство self.instance с объектом модели текущей формы. Кто-нибудь знает, как я могу получить этот объект внутри функции make_custom_datefield? - person ramusus; 05.07.2009
comment
Я предлагаю, чтобы, когда все, что вы хотите сделать, это использовать свой собственный класс поля формы, а не возвращать собственный экземпляр, вы должны сделать: return f.formfield (form_class = MyFormFieldClass) Это гарантирует, что Django по-прежнему позаботится об инициализации поля формы использование соответствующих меток, help_text, required-status и т. д. в зависимости от поля модели; вместо того, чтобы делать это самому. - person miracle2k; 12.12.2009
comment
Спасибо, Джеймс. Это очень помогло. Я опубликовал полный пример своей реализации в своем блоге (strattonbrazil.blogspot.com/2011/03/). - person voodoogiant; 27.03.2011
comment
Спасибо Джеймсу за решение DRY и спасибо voodoogiant за полную реализацию в вашем блоге. Работает отлично. - person KobeJohn; 09.11.2011
comment
Быстрое предупреждение, чтобы сэкономить время других. Если вы унаследованы от настраиваемого базового класса формы, который определяет formfield_callback, BaseForm.formfield_callback не будет вызываться. Это потому, что formfield_callback вызывается как часть new (то есть в ModelFormMetaClass). Я создал достойное решение этой проблемы, которое вы можете найти здесь, если вам интересно: stackoverflow.com/questions/7342925/. - person Josh; 31.08.2012
comment
Еще одно быстрое замечание - реализация formfield_callback по умолчанию принимает именованные аргументы; поэтому лучше добавить * args и ** kwargs в свой список аргументов formfield_callback и передать именованные аргументы в f.formfield(**kwargs) - person Serj Zaharchenko; 22.04.2015
comment
@ BrianM.Hunt (или, что более вероятно, другие задаются вопросом), документ для этого находится здесь: docs.djangoproject.com/en/3.1/ref/forms/models. Хотя, честно говоря, он мало что говорит, но этот ответ, по крайней мере, так же полезен, как и документы. - person logicOnAbstractions; 15.03.2021

Эта статья мне много раз помогала .

Суть его состоит в том, чтобы переопределить метод __init__ в ModelForm, затем вызвать метод __init__ суперкласса, а затем настроить поля индивидуально.

class PollForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(PollForm, self).__init__(*args, **kwargs)
        self.fields['question'].widget = forms.Textarea()

    class Meta:
        model = Poll

Этот метод может показаться более сложным, чем у Васила, но он предлагает дополнительное преимущество, заключающееся в возможности точно переопределить любой атрибут в поле без сброса каких-либо других атрибутов путем повторного объявления его.

ОБНОВЛЕНИЕ: Предлагаемый подход можно обобщить для изменения всех полей даты без строгого ввода каждого имени:

from django.forms import fields as formfields
from django.contrib.admin import widgets

class PollForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(PollForm, self).__init__(*args, **kwargs)
        for field_name in self.fields:
            field = self.fields[field_name]
            if isinstance(field, formfields.DateField):
                field.widget = widgets.AdminDateWidget()

    class Meta:
        model = Poll

У меня это сработало на python3 и django 1.11

person Soviut    schedule 19.03.2009
comment
Хорошая ссылка. Можно ли это сделать так, чтобы не вводить вручную строку self.fields ['question'] для каждой формы? (например, для поля в self.fields: if isinstance (field, models.DateField): field.widget = mywidget? Ура - person Brian M. Hunt; 19.03.2009

Что ж, создание настраиваемого поля модели просто для изменения его виджета формы по умолчанию - не самое очевидное место для начала.

Вы можете создать свой собственный виджет формы и переопределить поле в форме, указав свой собственный виджет, как в ответе Soviut.

Есть и более короткий путь:

class ArticleForm(ModelForm):
     pub_date = DateField(widget=MyDateWidget())

     class Meta:
         model = Article

Есть пример того, как писать виджеты форм, он находится где-то в пакете форм Django. Это датапикер с 3 раскрывающимися списками.

Когда я просто хочу добавить JavaScript к стандартному элементу ввода HTML, я обычно оставляю его таким, какой он есть, и изменяю его, ссылаясь на его идентификатор позже с помощью JavaScript. Вы можете легко уловить соглашение об именах для идентификаторов полей ввода, которые генерирует Django.

Вы также можете просто предоставить класс для виджета, когда переопределите его в форме. Затем поймайте их все с помощью jQuery по имени класса.

person Vasil    schedule 19.03.2009
comment
Спасибо за ответ. Решение, которое вы предлагаете, оставляя стандартный HTML, интересно, но все же трудоемко, подвержено ошибкам разработчика и требует большого количества дублирования кода. Я ищу решение, которое устранит эти проблемы. Есть предположения? - person Brian M. Hunt; 19.03.2009
comment
Ну, мне не нужно было что-то в масштабе, который вы пытаетесь (для большого количества входных данных в проектах), но администратор django делает это с помощью виджета datepicker, и вы можете заглянуть в код для django.contrib.admin, чтобы увидеть, как это делает это. - person Vasil; 19.03.2009

Я использую JQuery. Вам нужно только найти 'id' полей, которые вы хотите связать с выбором даты, и связать их с JQuery и правильным форматом отображения:

models.py

class ObjectForm(ModelForm):
    class Meta:
        model = Object        
        fields = ['FieldName1','FieldName2']

вверху страницы, которую вы визуализируете в своем представлении:

<head>
    <link type="text/css" href="/media/css/ui-darkness/jquery-ui-1.8.2.custom.css" rel="Stylesheet" /> 
    <script type="text/javascript" src="/media/js/jquery-1.4.2.min.js"></script>
    <script type="text/javascript" src="/media/js/jquery-ui-1.8.2.custom.min.js"></script>
</head>
<script type="text/javascript">
 $(function() {
        $("#id_FieldName1").datepicker({ dateFormat: 'yy-mm-dd' });
        $("#id_FieldName2").datepicker({ dateFormat: 'yy-mm-dd' });
 });
</script>
...
{{form}}
person MarC    schedule 23.07.2010

Вы действительно хотите определить собственный виджет и использовать внутренний класс Media для определения файлов JS (и CSS?), которые должны быть включены на страницу для работы виджета. Если вы сделаете это правильно, вы можете сделать свой виджет полностью автономным и многоразовым. См. django-markitup для одного пример этого (в нем есть многоразовый виджет для универсальный редактор разметки MarkItUp!).

Затем используйте formfield_callback (см. Ответ Джеймса Беннета), чтобы легко применить этот виджет ко всем DateField в форме.

person Carl Meyer    schedule 19.03.2009

Некоторые могут на это нахмуриться, но чтобы заменить средство выбора даты на ваш собственный виджет, я бы создал приложение monkeypatch для вашего проекта и исправил бы сам Django во время выполнения. Преимущество этого в том, что любые сторонние приложения также будут реализованы и, таким образом, предоставят единый интерфейс для конечного пользователя без необходимости изменения стороннего кода:

from django.forms.widgets import DateInput , DateTimeInput, TimeInput
from FOO.widgets import MyjQueryWidget

# be nice and tell you are patching
logger.info("Patching 'DateInput.widget = MyjQueryWidget': Replaces django DateInput to use my new super  'MyjQueryWidget'")

# be nicer and confirm signature of code we are patching and warn if it has changed - uncomment below to get the current hash fingerprint
# raise Exception(hashlib.md5(inspect.getsource(DateInput.widget)).hexdigest()) # uncommet to find latest hash
if not '<enter hexdigest fingerprint here>' == \
        hashlib.md5(inspect.getsource(DateInput.widget)).hexdigest():
    logger.warn("md5 signature of 'DateInput.widget' does not match Django 1.5. There is a slight chance patch "
                    "might be broken so please compare and update this monkeypatch.")

# be nicest and also update __doc__
DateInput.__doc__ = "*Monkeypatched by <app name>*: Replaced django DateInput.widget with my new super  'MyjQueryWidget'" + DateInput.__doc__ 

DateInput.widget = MyjQueryWidget

Вышеизложенное основано на моем html5monkeypatch, который я использую как часть своих проектов, взгляните на patch_widgets.py и patch_fields.py.

person Daniel Sokolowski    schedule 04.06.2013