2009-03-19 15 views
39

dato un insieme di modelli tipici:Come si modifica il widget predefinito per tutti i campi data di Django in 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() 

... 

Come potrebbe un cambiare il widget di default per tutti i DateFields ad un MyDateWidget personalizzato?

Sto chiedendo perché voglio che la mia applicazione abbia un datapicker jQueryUI per l'inserimento delle date.

Ho considerato un campo personalizzato che estende django.db.models.DateField con il mio widget personalizzato. È questo il modo migliore per implementare questo tipo di cambiamento trasversale? Tale modifica richiederà specificamente l'importazione di uno speciale MyDateField in ogni modello, che è laborioso, soggetto a errori dello sviluppatore (ad esempio alcuni modelli.DateField supererà), e nella mia mente sembra una inutile duplicazione degli sforzi. D'altra parte, non mi piace modificare quella che potrebbe essere considerata la versione canonica dei modelli.DateField.

È apprezzato il pensiero e l'input.

risposta

54

È possibile dichiarare un attributo nella classe ModelForm, denominato formfield_callback.Questa dovrebbe essere una funzione che accetta un'istanza di modello Django Field come argomento e restituisce un'istanza di modulo Field per rappresentarla nel modulo.

Quindi tutto quello che dovete fare è vedere se il campo del modello passato è un'istanza di DateField e, in tal caso, restituire il campo/widget personalizzato. In caso contrario, il campo del modello avrà un metodo denominato formfield che è possibile chiamare per restituire il campo modulo predefinito.

Quindi, qualcosa di simile:

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 se questo è un comportamento comune necessario su più moduli con DateTimeFields, questo è il modo DRY per farlo –

+0

Grandi cose Dove è documentato formfield_callback –

+0

Grazie per questa grande idea! Dentro la classe SomeForm ho _se lf.instance_ proprietà con oggetto modello del modulo corrente. Qualcuno sa come posso ottenere questo oggetto all'interno della funzione _make_custom_datefield_? – ramusus

6

This article mi ha aiutato numerose volte.

La carne di esso comporta l'override del metodo __init__ di ModelForm, quindi chiama il super metodo '__init__, quindi regola singolarmente i campi.

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 

Questo metodo può sembrare più complicato di Vasil di, ma offre l'ulteriore vantaggio di poter ignorare precisamente qualsiasi attributo su un campo senza ripristinare altri attributi da ri-dichiarando esso.

UPDATE: approccio consigliata potrebbe essere generalizzato a cambiare tutti i campi data senza digitare ogni nome rigorosamente:

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 

che ha lavorato per me su python3 e django 1.11

+0

Piacevole riferimento. Potrebbe essere fatto in un modo che non richiede la riga "self.fields ['domanda']" inserita manualmente per ogni modulo? (ad es. per campo in self.fields: if isinstance (field, models.DateField): field.widget = mywidget? Cheers –

6

Beh, rendendo un campo modello personalizzato solo per cambiare il suo modulo di forma di default non è davvero il posto ovvio da cui iniziare.

È possibile creare il proprio widget modulo e sovrascrivere il campo nel modulo, specificando il proprio widget come nella risposta di Soviut.

C'è anche una via più breve:

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

    class Meta: 
     model = Article 

V'è un esempio di come scrivere widget del form, è da qualche parte nel pacchetto forme di Django. È un datepicker con 3 menu a discesa.

Quello che faccio di solito quando voglio aggiungere un po 'di JavaScript a un elemento di input HTML standard è lasciarlo così com'è e modificarlo facendo riferimento al suo id in seguito con JavaScript. Puoi facilmente catturare la convenzione di denominazione per gli ID dei campi di input generati da Django.

È anche possibile fornire la classe per il widget quando si esegue l'override nel modulo. Poi prendili tutti con jQuery dal nome della classe.

+1

Grazie per la risposta. La soluzione che suggerisci, lasciando l'HTML standard, è interessante ma ancora laboriosa, soggetto per errore dello sviluppatore e richiede un sacco di duplicazione del codice. Sto puntando a una soluzione che elimini quei problemi.Tutti i pensieri? –

+0

Beh, non ho avuto bisogno di qualcosa sulla scala che stai provando (per molti input nel progetti) ma django admin lo fa con il widget datepicker e si può dare un'occhiata al codice per django.contrib.admin per vedere come funziona – Vasil

+0

Buona chiamata, grazie –

1

Non vuole definire un widget personalizzato, e utilizzare il widget inner Media class per definire il JS file che devono essere inclusi nella pagina per la (e CSS?) widget per funzionare. Se lo fai bene, puoi rendere il tuo widget completamente autonomo e riutilizzabile. Vedere django-markitup per uno example of doing this (ha un widget riutilizzabile per MarkItUp! universal markup editor).

Quindi utilizzare formfield_callback (vedere la risposta di James Bennett) per applicare facilmente quel widget a tutti i DateField in un modulo.

3

Io uso JQuery. Basta cercare il 'id' dei campi che si desidera associare con il selettore data e legatela con JQuery e il diritto formato di visualizzazione:

models.py

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

in cima nella pagina si esegue il rendering con la vostra 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

Qualcuno potrebbe cipiglio a questo, ma per sostituire il selettore data con il vostro widget personalizzato vorrei cassa un'applicazione monkeypatch per il progetto e la patch stessa Django in fase di esecuzione. Vantaggio di questo è qualsiasi applicazioni terze parti saranno effettuati come bene e così presente un'interfaccia uniforme per l'utente finale, senza dover modificare il terzo codice di partito:

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 
È possibile che questo

è ispirata dal mio html5monkeypatch uso come parte dei miei progetti, date un'occhiata a patch_widgets.py e patch_fields.py.

Problemi correlati