Code

Opened 7 years ago

Closed 7 years ago

Last modified 6 years ago

#4284 closed (wontfix)

forms containing MultiWidgets cannot be reconstructed with clean_data

Reported by: jfindlay@… Owned by: nobody
Component: Forms Version: master
Severity: Keywords: MultiValueField MultiWidget clean_data decompress
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

Description

This seems to be a design issue that may not be easy to resolve, but there is no easy way to reconstruct a MultiValueField with its own clean data. Whereas a normal field will accept data from clean_data, a MultiValueField expects it to be 'decompressed' first.

>>> from datetime import *
>>> from django.newforms import *
>>> 
>>> class MonthYearWidget(MultiWidget):
...   def __init__(self,months=(),years=(),attrs=None):
...     widgets = (Select(choices=months),Select(choices=years))
...     super(MonthYearWidget,self).__init__(widgets,attrs)
...   def decompress(self,value):
...     if value:
...       return value.split('/')
...     return ['', '']
... 
>>> class MonthYearField(MultiValueField):
...   def __init__(self,months=(),years=(),required=True,widget=None,label=None,initial=None):
...     fields = (ChoiceField(choices=months),ChoiceField(choices=years))
...     widget = MonthYearWidget(months=months,years=years)
...     super(MonthYearField,self).__init__(fields,required,widget,label,initial)
...   def compress(self,data_list):
...     if data_list:
...       return '/'.join(data_list)
...     return None
... 
>>> month_list = [x + 1 for x in xrange(12)]
>>> months = [(m,m) for m in month_list]
>>> year_list = [(date.today() + timedelta(days=366)*x).strftime("%Y") for x in xrange(10)]
>>> years = [(y,y) for y in year_list]
>>> 
>>> class PaymentForm(Form):
...   ccn = RegexField('^\d{4}[ .-]?\d{4}[ .-]?\d{4}[ .-]?\d{4}$',widget=TextInput(attrs={'maxlength':19,'size':19}))
...   exp_date = MonthYearField(months=months,years=years)
... 
>>> POST = {'ccn':'1234 5678 9012 3456','exp_date_0':'5','exp_date_1':'2007'}
>>> pf = PaymentForm(POST)
>>> pf.is_valid()
True
>>> pf.clean_data
{'ccn': u'1234 5678 9012 3456', 'exp_date': u'5/2007'}
>>> pf1 = PaymentForm(pf.clean_data)
>>> pf1.is_valid()
False
>>> pf1.errors
{'exp_date': [u'This field is required.']}

Attachments (0)

Change History (2)

comment:1 Changed 7 years ago by SmileyChris

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Resolution set to wontfix
  • Status changed from new to closed

I don't think there's anywhere that says cleaned_data can be used to reconstruct another form.

If you wanted to do this, why wouldn't you just use data (in this case POST) again?

comment:2 Changed 6 years ago by dballanc@…

I realize this is a won't fix issue, but since I just ran into the same problem I wanted to comment anyway. As for why you wouldn't just use the data from POST again... maybe it wasn't posted! My need was to repopulate a stored-settings form based on values stored in the database. I could have used initial, but I wanted to trigger validation on the old data in case the previous settings were no longer valid. It made sense to me anyway, and I was surprised it wasn't default behavior.

I did find a workaround by overriding the widgets value_from_datadict method to decompress the value if it exists in 'compressed' form. Something like this:

class MonthYearWidget(MultiWidget):
    ...
    def value_from_datadict(self,data,name):
        raw_val = data.get(name,None)
        if raw_val:
            value=self.decompress(raw_val)
        else:
            value = super(MonthYearWidget,self).value_from_datadict(data,name)
        return value  

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.