James
James

Reputation: 2518

Proper way to build Django form/class method

I have a Django form (Django non-rel v1.5.5) with a form/class method that works fine. However, now I've moved on to building unit tests for the application I have a problem testing a form/class method. I assume the form/class method is defined improperly.

forms.py

class TimeForm(forms.Form):
    def build_choices(start, stop, incr=1):
        # Form/class method that builds a list of tuples of identical i, i values for forms.ChoiceField
        choice_list = []
        temp_list   = [i for i in range(start, stop, incr)]

        for i in temp_list:
            choice_list.append(('%02d' % i, '%02d' % i))

        return choice_list

    hour_choices   = build_choices(1, 13) # E.g., [('01', '01'), ('02', '02') ... ]
    minute_choices = build_choices(0, 60, 15)
    hour     = forms.ChoiceField(choices=hour_choices)
    minute   = forms.ChoiceField(choices=minute_choices)

The above code generates forms fine, with the proper choices for the form fields. However, when I try to unit test the build_choices method, I hit a wall.

some tests

"""Testing the build_choices method."""
> form = TimeForm()
> form.build_choices(1, 10) # This will not work.
> *** TypeError: range() integer start argument expected, got TimeForm.
>
> TimeForm.build_choices(1, 10) # This will not work.
> *** TypeError: unbound method build_choices() must be called with TimeForm instance as first argument (got int instance instead)
>
> TimeForm.build_choices(form, 1, 10)
> *** TypeError: range() integer start argument expected, got TimeForm.

However,

> TimeForm.hour_choices
> [('01', '01'), ('02', '02') ... ]

Is there a better way/proper way to define a form method that isn't an instance method? E.g., TimeForm(self, start, end, inc).

Upvotes: 0

Views: 510

Answers (2)

dyeray
dyeray

Reputation: 1086

I think the same as Tomita, it looks like before initialization of the class, build_choices is a function, and after it, it is an instance method. I think another option, instead of putting build_choices outside of the class is:

class TimeForm(forms.Form):
    def build_choices(self, start, stop, incr=1):
        # Form/class method that builds a list of tuples of identical i, i values for forms.ChoiceField
        choice_list = []
        temp_list   = [i for i in range(start, stop, incr)]

        for i in temp_list:
            choice_list.append(('%02d' % i, '%02d' % i))

        return choice_list

    hour_choices   = build_choices(None, 1, 13) # E.g., [('01', '01'), ('02', '02') ... ]
    minute_choices = build_choices(None, 0, 60, 15)
    hour     = forms.ChoiceField(choices=hour_choices)
    minute   = forms.ChoiceField(choices=minute_choices)

In this case (like in your example) in the tests you will not be able to call build_choices as a class method (for that you would need to use the decorator @classmethod or @staticmethod but if you do you will not be able to call it from the class attributes).

You could call it on he tests like this:

form = TimeForm()
form.build_choices(1, 10)
TimeForm.build_choices(TimeForm(), 1, 10)
TimeForm.build_choices(form, 1, 10)

Upvotes: 1

This is some weird setup. You have a class method that's immediately invoked as a normal function until the class finishes initializing.

I'm not a deep python wizard, but it appears functions are converted to class methods only after class setup.

Bottom line: TimeForm.build_choices is a class method. Its first argument is always the class instance.

Solution:

Define build_choices outside the class.

Test the function object itself via TimeForm.hour_choices.__func__(1, 10)

Upvotes: 2

Related Questions