2009-03-19 5 views
39

dado un conjunto de modelos típicos:¿Cómo se cambia el widget predeterminado para todos los campos de fecha de Django en un 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() 

... 

¿Cómo puede uno cambiar el widget por defecto para todos los DateFields a un MyDateWidget personalizada?

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

He considerado un campo personalizado que amplía django.db.models.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 especial en cada modelo, que requiere mucha mano de obra, es propenso a errores de desarrollador (es decir, algunos modelos.DateField se realizará), y en mi opinión parece una innecesaria duplicación de esfuerzos. Por otro lado, no me gusta modificar lo que podría considerarse la versión canónica de los modelos.DateField.

Se agradecen los comentarios y la entrada.

Respuesta

54

Puede declarar un atributo en su clase ModelForm, llamada formfield_callback.Esta debería ser una función que toma una instancia de Django modelo Field como argumento y devuelve una instancia de formulario Field para representarlo en el formulario.

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

Por lo tanto, algo así 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... 
+5

+1 si es un comportamiento común que necesitas en múltiples formas con DateTimeFields, esta es la manera SECA de hacerlo. –

+0

Cosas geniales. ¿Dónde se documenta formfield_callback? –

+0

Gracias por esta gran idea! Dentro de la clase SomeForm tengo _se Propiedad lf.instance con el objeto del modelo de la forma actual. ¿Alguien sabe cómo puedo obtener este objeto dentro de la función _make_custom_datefield_? – ramusus

6

This article me ha ayudado en numerosas ocasiones.

La esencia de esto es anular el método __init__ de ModelForm, luego llamar al método de superclase '__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 Vasil de, pero ofrece la ventaja adicional de ser capaz de anular cualquier atributo, precisamente en un campo sin restablecer cualquier otro atributo de volver a declararlo.

ACTUALIZACIÓN: enfoque sugerido podría generalizarse a cambiar todos los campos de la fecha sin tener que 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 trabajó para mí en python3 y django 1.11

+0

Buena referencia. ¿Podría hacerse de una manera que no requiera que se ingrese manualmente la línea "self.fields ['question']" para cada formulario? (por ejemplo, para el campo en self.fields: if isinstance (field, models.DateField): field.widget = mywidget? Cheers –

6

Bueno, por lo que un campo modelo personalizado solo para cambiar su widget de forma predeterminada no es realmente el lugar obvio para comenzar.

Puede hacer 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 

No es un ejemplo de la forma de escribir de forma widgets, es alguna parte del conjunto de formularios de Django. Es un datepicker con 3 desplegables.

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

También puede proporcionar la clase para el widget cuando lo sobrescribe en el formulario. A continuación, recójalas todas con jQuery por el nombre de la clase.

+1

Gracias por la respuesta. La solución que sugiere, dejando el HTML estándar, es interesante pero sigue siendo laboriosa, tema al desarrollador error, y requiere mucha duplicación de código. Estoy buscando una solución que elimine esos problemas. ¿Alguna idea? –

+0

Bueno, no he tenido la necesidad de algo en la báscula que está intentando (para muchas entradas en el proyectos) pero django admin lo hace con el widget datepicker y puedes echar un vistazo al código para django.contrib.admin para ver cómo lo hace. – Vasil

+0

Buena llamada; gracias –

1

Usted desea definir un widget personalizado, y el uso de la inner Media class del widget para definir la JS archivos que tienen que ser incluidos en la página de la (y CSS?) widget para trabajar. Si haces esto bien, puedes hacer que tu widget sea completamente autónomo y reutilizable. Ver django-markitup para un example of doing this (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 DateField en un formulario.

3

Uso JQuery. Sólo tiene que buscar la 'id' de los campos que desea asociar con el selector de fechas y se unen con el formato de pantalla derecha y jQuery:

models.py

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

en la parte superior de la página que rinde con su punto de 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}} 
1

Algunos podrían fruncir el ceño ante esto, pero para reemplazar el selector de fechas con su widget personalizado que lo haría cajón de una aplicación monkeypatch para su proyecto y el parche en sí Django en tiempo de ejecución. Beneficio de esto es los aplicaciones tercera partes se efectuarán así y así presentan 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 se inspiró de mi html5monkeypatch utilizo como parte de mis proyectos, eche un vistazo a patch_widgets.py y patch_fields.py.

Cuestiones relacionadas