views:

30

answers:

1

I'm trying to build a better/more powerful form class for Django. It's working well, except for these sub-forms. Actually, it works perfectly right after I re-start apache, but after I refresh the page a few times, my HTML output starts to look like this:

<input class="text" type="text" id="pickup_addr-pickup_addr-pickup_addr-id-pickup_addr-venue" value="" name="pickup_addr-pickup_addr-pickup_addr-pickup_addr-venue" />

The pickup_addr- part starts repeating many times. I was looking for loops around the prefix code that might have cause this to happen, but the output isn't even consistent when I refresh the page, so I think something is getting cached somewhere, but I can't even imagine how that's possible. The prefix var should be reset when the class is initialized, no? Unless it's somehow not initializing something?

class Form(object):
    count = 0
    def __init__(self, data={}, prefix='', action='', id=None, multiple=False):
        self.fields = {}
        self.subforms = {}
        self.data = {}
        self.action = action
        self.id = fnn(id, 'form%d' % Form.count)
        self.errors = []
        self.valid = True
        if not empty(prefix) and prefix[-1:] not in ('-','_'): prefix += '-'

        for name, field in inspect.getmembers(self, lambda m: isinstance(m, Field)):
            if name[:2] == '__': continue
            field_name = fnn(field.name, name)
            field.label = fnn(field.label, humanize(field_name))
            field.name = field.widget.name = prefix + field_name + ife(multiple, '[]')
            field.id = field.auto_id = field.widget.id = ife(field.id==None, 'id-') + prefix + fnn(field.id, field_name) + ife(multiple, Form.count)
            field.errors = []

            val = fnn(field.widget.get_value(data), field.default)

            if isinstance(val, basestring):
                try:
                    val = field.coerce(field.format(val))
                except Exception, err:
                    self.valid = False
                    field.errors.append(escape_html(err))

            field.val = self.data[name] = field.widget.val = val

            for rule in field.rules:
                rule.fields = self.fields
                rule.val = field.val
                rule.name = field.name

            self.fields[name] = field

        for name, form in inspect.getmembers(self, lambda m: ispropersubclass(m, Form)):
            if name[:2] == '__': continue
            self.subforms[name] = self.__dict__[name] = form(data=data, prefix='%s%s-' % (prefix, name))

        Form.count += 1  

Let me know if you need more code... I know it's a lot, but I just can't figure out what's causing this! I'm not even using any cache middleware.


Copying/cloning the fields first gives me this output instead:

<label for="None">None</label>
<input class="text" type="text" id="id-pickup_address-venue" value="" name="pickup_address-venue" />

field.name and field.label are set in exactly the same way... in fact, field.id is correctly displayed on the <input> but that same value is suddenly gone when I try to print the label.... the difference is that the <input> bit is printed by the Widget class, whereas the label is printed directly from my template... which I guess is.... oh I get it, that one is still referring to the unset class-level/static field, rather than the instance field...

+1  A: 

You're probably declaring your forms like this:

class SomeForm(Form):
    someField = Field(....)
    ...

Now, this means that one instance of someField will actually be shared among all your SomeForm instances. In your __init__ you're changing the attributes of the field, which will affect all forms, not just the current one, including ones created in the future.

To fix this, you can make a copy of the field for each instance:

field = copy(field)  #maybe you need deepcopy instead
setattr(self, name, field)   

And then change the attributes of the copy.

interjay
Yup, that was it. Thank you so much! Not sure if I'd ever have figured that one out!
Mark