Dynamic FormWizard

2019-01-29 05:18发布

问题:

I made a project that works like ifttt.com does.

To do so I use FormWizard.

Actually, that works fine with the only 2 services which are RSS and Evernote

I could set the FORMS and TEMPLATES like expected by the FormWizard, here is a peace of my urls.py and views.py :

urls.py

# wizard
url(r'^service/create/$', UserServiceWizard.as_view([RssForm, EvernoteForm,
     ServicesDescriptionForm]), name='create_service'),

views.py

from th_rss.forms import RssForm
from th_evernote.forms import EvernoteForm
from django_th.forms.base import ServicesDescriptionForm

FORMS = [("rss", RssForm),
     ("evernote", EvernoteForm),
     ("services", ServicesDescriptionForm), ]

TEMPLATES = {
    '0': 'rss/wz-rss-form.html',
    '1': 'evernote/wz-evernote-form.html',
    '2': 'services_wizard/wz-description.html'}


class UserServiceWizard(SessionWizardView):
    instance = None

    def get_form_instance(self, step):

        if self.instance is None:
            self.instance = TriggerService()
        return self.instance

    def done(self, form_list, **kwargs):
        trigger = self.instance
        trigger.provider = UserService.objects.get(
            name='ServiceRss',
            user=self.request.user)
        trigger.consummer = UserService.objects.get(name='ServiceEvernote',
                            user=self.request.user)
        trigger.user = self.request.user
        trigger.status = True
        # save the trigger
        trigger.save()
            #...then create the related services from the wizard
        for form in form_list:
            if form.cleaned_data['my_form_is'] == 'rss':
            from th_rss.models import Rss
            Rss.objects.create(
                name=form.cleaned_data['name'],
                url=form.cleaned_data['url'],
                status=1,
                trigger=trigger)
            if form.cleaned_data['my_form_is'] == 'evernote':
                from th_evernote.models import Evernote
            Evernote.objects.create(
                tag=form.cleaned_data['tag'],
                notebook=form.cleaned_data['notebook'],
                status=1,
                trigger=trigger)

        return HttpResponseRedirect('/')

    def get_template_names(self):
        return [TEMPLATES[self.steps.current]]

But as actually the project handles only 2 services, I dont want (and cant imagine) to create one dedicated CBV for each couple of new service like TwitterEvernoteWizard, RssTwitterWizard, FacebookTwitterWizard and so on.

So first of all, I will have to change the process by those steps :

  • 1rst page displays the services the user can choose
  • 2nd page asks to the user what datas he wants to grab from choosen service at step 1
  • 3rd page displays the services the user can choose without the one choosen un step1
  • 4th page asks to the user where the datas (that the system will grab) will go (in the choosen service at step3)
  • 5th (and last) page displays a description field to name the trigger.

With a concret exemple that will give :

  • page 1 I choose Twitter
  • page 2 I choose to grab data from timeline
  • page 3 I choose Facebook
  • page 4 I choose to put the data on the wall
  • page 5 I put "Here is my trigger from twitter to facebook" ;)

So with this process I need to be able to dynamically change the content of FORMS to populate it with the name of the FormWizard from the Service I chose one step earlier. Same for TEMPLATES dict.

As you can see, at the beggining of the Wizard, I'm unable to know, by advance, which service will be selected. This is why I need to dynamicallly populate FORMS and TEMPLATES

If anyone knows how to do this or can just suggest a way to proceed, I will appreciate.

regards

notice : I use Django 1.4

回答1:

here is how I finished to handle it

first, urls.py :

url(r'^service/create/$','django_th.views.get_form_list', name='create_service'),

then in views.py :

I did :

def get_form_list(request, form_list=None):
    if form_list is None:
        form_list = [ProviderForm, DummyForm, ConsummerForm, DummyForm, \
                     ServicesDescriptionForm]
    return UserServiceWizard.as_view(form_list=form_list)(request)

this permits to define 5 steps with :

  • 3 knowns Form (ProviderForm, ConsummerForm, ServicesDescriptionForm
  • 2 unknown ones (DummyForm twice in fact) that will be handle dynamically below

the forms.py which provides the DummyForm :

class DummyForm(forms.Form):
    pass

the next step is to get the data from ProviderForm, get the service I selected from it, and load the for of this selected service :

in my views.py :

class UserServiceWizard(SessionWizardView):

    def __init__(self, **kwargs):
        self.form_list = kwargs.pop('form_list')
        return super(UserServiceWizard, self).__init__(**kwargs)

    def get_form_instance(self, step):
        if self.instance is None:
            self.instance = UserService()
        return self.instance

    def get_context_data(self, form, **kwargs):
        data = self.get_cleaned_data_for_step(self.get_prev_step(
                                                    self.steps.current))
        if self.steps.current == '1':
            service_name = str(data['provider']).split('Service')[1]
            #services are named th_<service>
            #call of the dedicated <service>ProviderForm
            form = class_for_name('th_' + service_name.lower() + '.forms',
                  service_name + 'ProviderForm')
       elif self.steps.current == '3':
            service_name = str(data['consummer']).split('Service')[1]
            #services are named th_<service>
            #call of the dedicated <service>ConsummerForm
            form = class_for_name('th_' + service_name.lower() + '.forms',
                  service_name + 'ConsummerForm')
        context = super(UserServiceWizard, self).get_context_data(form=form,
                                  **kwargs)
    return context

here :

  • __init__ load the data from get_form_list function I defined in urls.py
  • in get_context_data I need to change the DummyForm in step 1 and 3 from the service I chose in the dropdown of ProviderForm and ConsummerForm. As the service is named 'FoobarService', I split 'Service' to make the call of the form of the service Foobar(Consummer|Provider)Form with class_for_name() below :

class_for_name :

def class_for_name(module_name, class_name):
   m = importlib.import_module(module_name)
   c = getattr(m, class_name)
   return c

Finally :

with all of this I'm able to dynamically change the form on the fly at any step, in fact I decide to do it for step 1 and 3 but that can be adapted for any step ;)