¿Cómo se cambia el widget predeterminado para todos los campos de fecha de Django en un formulario de modelo?


Dado un conjunto de modelos típicos:

# 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()

...

¿Cómo se puede cambiar el widget predeterminado para todos los campos de datos a un MyDateWidget personalizado?

Pregunto porque quiero que mi aplicación tenga un datepicker jQueryUI para introducir fechas.

He considerado un campo personalizado que extiende django.db.modelo.DateField con mi widget personalizado. ¿Es esta la mejor manera de implementar este tipo de cambio general? Tal cambio requerirá la importación específica de un MyDateField en cada modelo, que es intensivo en mano de obra, propenso a errores de desarrollador (es decir, unos pocos modelos.DateField pasará), y en mi mente parece una duplicación innecesaria de esfuerzos. Por otro lado, no me gusta modificar lo que podría considerarse la versión canónica de los modelos.DateField.

Los pensamientos y las aportaciones son apreciados.

Author: Brian M. Hunt, 2009-03-19

6 answers

Puede declarar un atributo en su clase ModelForm, llamado formfield_callback. Esta debería ser una función que toma una instancia de Django model Field como argumento, y devuelve una instancia de form Field para representarla en la forma.

Entonces todo lo que tiene que hacer es mirar para ver si el campo del modelo pasado es una instancia de DateField y, si es así, devolver su campo/widget personalizado. Si no, el campo modelo tendrá un método llamado formfield que puede llamar para devolver su campo de formulario predeterminado.

Entonces, algo como:

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

class SomeForm(forms.ModelForm)
    formfield_callback = make_custom_datefield

    class Meta:
        # normal modelform stuff here...
 55
Author: James Bennett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-19 05:47:30

Bueno, hacer un campo de modelo personalizado solo para cambiar su widget de formulario predeterminado no es realmente el lugar obvio para comenzar.

Puede crear su propio widget de formulario y anular el campo en el formulario, especificando su propio widget como en la respuesta de Soviut.

También hay un camino más corto:

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

     class Meta:
         model = Article

Hay un ejemplo de cómo escribir widgets de formulario, está en algún lugar del paquete forms de Django. Es un datepicker con 3 desplegables.

Lo que suelo hacer cuando solo quiero agregar algo de JavaScript a un elemento de entrada HTML estándar es dejarlo como está y modificarlo haciendo referencia a su id más tarde con JavaScript. Puede capturar fácilmente la convención de nomenclatura para los ID de los campos de entrada que genera Django.

También puedes simplemente proporcionar la clase para el widget cuando la sobreescribas en el formulario. Luego atrápalos a todos con jQuery por el nombre de la clase.

 6
Author: Vasil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-06 23:59:06

Este artículo me ha ayudado muchas veces.

La esencia de esto implica sobreescribir el método __init__ de ModelForm, luego llamar al método super class' __init__, luego ajustar los campos individualmente.

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

Este método puede parecer más complicado que el de Vasil, pero ofrece el beneficio adicional de poder anular con precisión cualquier atributo en un campo sin restablecer ningún otro atributo al volver a declararlo.

ACTUALIZACIÓN: enfoque Sugerido podría generalizarse para cambiar todos los campos de fecha sin escribir cada nombre estrictamente:

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

Que funcionó para mí en python3 y django 1.11

 6
Author: Soviut,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-31 14:22:40

Uso jQuery. Solo tienes que buscar el ' id ' de los campos que quieres asociar con el selector de fecha y enlazarlos con jQuery y el formato de visualización correcto:

Models.py

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

En la parte superior de la página que renderiza con su vista:

<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}}
 3
Author: MarC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-07-23 20:52:33

Desea definir un widget personalizado, y usar la clase Media interna del widget para definir el JS (y CSS?) archivos que deben incluirse en la página para que el widget funcione. Si lo haces bien, puedes hacer que tu widget sea completamente autónomo y reutilizable. Ver django-markitup para un ejemplo de hacer esto (tiene un widget reutilizable para el markItUp! universal markup editor ).

Luego use formfield_callback (vea la respuesta de James Bennett) para aplicar fácilmente ese widget a todos los campos de datos en un formulario.

 1
Author: Carl Meyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-19 09:31:31

Algunos podrían fruncir el ceño, pero para reemplazar el selector de fecha con su widget personalizado, crearía una aplicación monkeypatch para su proyecto y parchearía a Django en tiempo de ejecución. El beneficio de esto es que cualquier aplicación de terceros también se efectuará y presentará una interfaz uniforme para el usuario final sin tener que modificar el código de terceros:

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

Lo anterior está inspirado en mi html5monkeypatch que uso como parte de mis proyectos, echa un vistazo a patch_widgets.py y patch_fields.py .

 1
Author: Daniel Sokolowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-29 03:42:32