I have an application with a single method called member_create that
presents a form to collect membership information and save it to the
database.  Depending on the project configuration and user I have
different membership forms, so I have a line of code like this in
member_create():

    member_edit_form_class = get_member_edit_form_class
(membership_type)

Up until recently, get_member_edit_form would return either
GenericMemberEditForm or SpecialMemberEditForm, where the latter
inherited from the former.  All of that was relatively
straightforward.  Just to be clear, get_member_edit_form_class was
returning a class, not an instance, since member_create would
instantiate the class later as needed:

    if request.method == 'POST':
        form = member_edit_form_class(request.POST, request.FILES)
        ...
    else:
        form = member_edit_form_class()


Things got a little more interesting, though, when our "Special"
customer wanted further refinements in the form's behavior based on
membership type.  Trying to keep member_create() generic, I put logic
like this in the SpecialMemberEditForm class:

    def __init__(self, *args, **kwargs):
        membership_type = kwargs.pop('membership_type', None)
        super(SpecialMemberEditForm, self).__init__(*args, **kwargs)
        if membership_type.name == 'whatever':
             self.fields['options'].queryset = ...
        else:
             self.fields['options'].queryset = ...

I wanted the member_create() method not to have any knowledge of this
extra parameter, for a few reasons:

  1) I wanted to keep member_create() as generic as possible.
  2) I already had a factory method called get_member_edit_form_class
(), so I wanted the factory method to configure the classes as
needed.  (I am using "factory" in the loose sense--I don't know the
exact terminology for a method that returns a class, not an instance.)
  3) I didn't want member_create() to pass in the extra parameter to
any class other than SpecialMemberEditForm, since
GenericMemberEditForm had no use for that parameter, and it would be
brittle to have all member edit forms follow the contract of popping
off unused parameters.
  4) To elaborate on the prior point, the forms all have BaseModelForm
as an ancestor, and BaseModelForm's __init__ method cannot deal
gracefully with extraneous keyword arguments, perhaps for good reason.

In order to allow get_member_edit_form_class to configure
SpecialMemberEditForm as needed, I wrote code like this:

    def special_get_member_edit_form_class(membership_type):
        # we curry membership_type here so that other code never needs
to
        # deal with extra parms
        def curry(*args, **kwargs):
            kwargs['membership_type'] = membership_type
            return SpecialMemberEditForm(*args, **kwargs)
        return curry

Essentially, the method above is not returning a class--instead it's
returning a method, but that method generally provides the illusion of
being a class in a pythonic sort of way, since it has the same
behavior when used as a callable (it returns an instance).

This worked exactly as expected in member_create(), but then I got
around to updating member_update(), which works like this:

    member_edit_form_class = get_member_edit_form_class
(membership_type)

    return create_update.update_object(request,
                                       post_save_redirect=...,
 
form_class=member_edit_form_class,
 
template_name='member_form.html',
                                       **kwargs)

Here is where the illusion broke down.  The update_object method
really wants member_edit_form_class to be a subclass of Form, not just
a callable that produces a Form subclass instance.

In particular, this line of code fails inside get_model_and_form_class
(), which gets called by update_object():

        return form_class._meta.model, form_class

Basically what is happening at this point is that form_class is the
same as curry above, and curry does not have an attribute called
_meta, since curry is not exactly a subclass of Form; instead, it is a
method that returns an instance of Form.  So I added one line of code
to solve the problem:

        curry._meta = SpecialMemberEditForm._meta

So now everything works, but it still feels a little dirty and
brittle, and I'm wondering if others have solved similar problems
using different design patterns.  The situation is not as esoteric as
perhaps it looks--really, it's just a scenario where we are trying to
keep one method DRY with a couple different forms that can be plugged
in, and one form class needs to be configured before instantiation.

I know that django has some helpers related to partial functions, but
I haven't had luck tracking down documentation, so any pointers there
are welcome, even if they don't exactly solve my problem.  I think I
am doing something a little different from the normal partial/currying
use case, but I also bet somebody has solved my problem before.

Thanks,
Steve
http://www.djangosnippets.org/users/showell/




--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To post to this group, send email to django-users@googlegroups.com
To unsubscribe from this group, send email to 
django-users+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-users?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to