Django ComplexField i ComplexMultiWidget

Ostatnio przeglądając kod źródłowy Django, a mianowicie testy, natknąłem się na pole MultiValueField oraz widget - MultiWidget. Jak na razie nie zostało to udokumentowane w dokumentacji newforms. Można dzięki temu stworzyć sobie pole które będzie się składać z innych pól. Poniżej przykład:

Definicja klasy ComplexMultiWidget, która zawierać będzie widget'y pól składowych klasy ComplexField:

class ComplexMultiWidget(forms.MultiWidget):
    def __init__(self, attrs=None):
        widgets = (
            forms.TextInput(),
            forms.SelectMultiple(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
            forms.SplitDateTimeWidget(),
        )
        super(ComplexMultiWidget, self).__init__(widgets, attrs)

    def decompress(self, value):
        if value:
            data = value.split(',')
            return [data[0], data[1], datetime.datetime(*time.strptime(data[2], "%Y-%m-%d %H:%M:%S")[0:6])]
        return [None, None, None]
    def format_output(self, rendered_widgets):
        return u'\n'.join(rendered_widgets)

Definicja klasy pola ComplexField. W metodzie compress zapisujemy w jaki sposób wartości z pól składowych mają być zapisane w naszym polu typu ComplexField.

class ComplexField(forms.MultiValueField):
    def __init__(self, required=True, widget=None, label=None, initial=None):
        fields = (
            forms.CharField(),
            forms.MultipleChoiceField(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
            forms.SplitDateTimeField()
        )
        super(ComplexField, self).__init__(fields, required, widget, label, initial)

    def compress(self, data_list):
        if data_list:
            return '%s,%s,%s' % (data_list[0],''.join(data_list[1]),data_list[2])
        return None

Teraz tworząc instancje pola ComplexField, podająć jako widget nasz ComplexMultiWidget i wywołując metode clean dla poprawnych i nie poprawnych danych dostaniemy:

>>> f = ComplexField(widget=ComplexMultiWidget())
>>> f.clean(['some text', ['J','P'], ['2007-04-25','6:24:00']])
u'some text,JP,2007-04-25 06:24:00'
>>> f.clean(['some text',['X'], ['2007-04-25','6:24:00']])
Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. X is not one of the available choices.']

>>> f.clean(['some text',['JP']])
Traceback (most recent call last):

Aby wykorzystać nasze nowe pole tworzymy klasę formularza i dodaje definicje pola:

>>> class ComplexFieldForm(Form):
            field1 = ComplexField(widget=ComplexMultiWidget())

Po wydrukowaniu formularza dostaniemy:

>>> f = ComplexFieldForm()
>>> print f
<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" id="id_field1_0" />
<select multiple="multiple" name="field1_1" id="id_field1_1">
<option value="J">John</option>
<option value="P">Paul</option>
<option value="G">George</option>
<option value="R">Ringo</option>
</select>
<input type="text" name="field1_2_0" id="id_field1_2_0" /><input type="text" name="field1_2_1" id="id_field1_2_1" /></td></tr>

Formularz z wypełnionymi danymi:

>>> f = ComplexFieldForm({'field1_0':'some text','field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'})
>>> print f
<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" value="some text" id="id_field1_0" />
<select multiple="multiple" name="field1_1" id="id_field1_1">
<option value="J" selected="selected">John</option>
<option value="P" selected="selected">Paul</option>
<option value="G">George</option>
<option value="R">Ringo</option>
</select>
<input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr>

>>> f.cleaned_data
{'field1': u'some text,JP,2007-04-25 06:24:00'}

Dzięki takiemu polu MultiValueField w połaczeniu z MultiWidget możemy tworzyc skąplikowane pola, zawierające inne pola.

Komentarze

fisher
10 December, 2007, 11:47 p.m.:

W jaki sposob ustawic pole ComplexField jako wymagane? Dokladniej rzecz biorac chcialbym miec mozliwosc sterowania obligatoryjnoscią skladowych pol Field pola ComplexField.

Jest niby ustawiane required w: super(ComplexField, self).init(fields, required, widget, label, initial). ale mam wrazenie ze to dziala tylko dla pierwszego pola: forms.CharField()

Z gory dzieki za pomoc,

Ps. Fajny artykul, nie ma za wiele w sieci o tworzeniu Multi Widgetow.

Dominik Szopa
17 December, 2007, 12:01 a.m.:

Obligatoryjność dla pola ComplexField ustawiasz w init'cie klasy ComplexField. W takim wypadku wszystkie pola składowe ComplexField będą wymagane. Jeżeli chciałbyś ustawić żeby np. w polu ComplexField wymagane były dwa ostanie pola składowe, a pierwsze nie było wymagane to ustawiasz domyślenie żeby Twoje pole ComplexField było nie wymagane(equired=False) dla poszczególnych pól składowych ustawiasz juz konkretnie co ma być wymagane a co nie:

class ComplexField(forms.MultiValueField): def init(self, required=False, widget=None, label=None, initial=None): fields = ( forms.CharField(required=False), forms.MultipleChoiceField(required=True, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), forms.SplitDateTimeField(required=True) ) super(ComplexField, self).init(fields, required, widget, label, initial) . . . dalsza część klasy ....

Teraz gdy nie poda sie pierwszego pola składowego to zostanie wstawiony tam pusty string:

f = ComplexFieldForm({'field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'}) f.cleaned_data

{'field1': u',JP,2007-04-25 06:24:00'}

ps. Przepraszam że tak późno odpisuje, nie miałem ostatnio czasu żeby tu zaglądać, musze dodać jakiegoś rss'a :)

fisher
3 February, 2008, 10:29 p.m.:

Oj koniecznie, rss by sie przydał :) Dzięki za odpowiedź.

Dominik Szopa
3 February, 2008, 11:57 p.m.:

Mówisz, masz ;) RSS jest od tygodnia dostępny :)


Comments turned off