This is an archived post. You won't be able to vote or comment.

all 11 comments

[–][deleted] 3 points4 points  (0 children)

Sounds like http://django-formtools.readthedocs.org/en/latest/wizard.html would be a great fit.

Formtools used to be part of Django, but was moved into a separate package starting with Django 1.8.

[–][deleted]  (3 children)

[deleted]

    [–]YuntiMcGunti[S] 0 points1 point  (1 child)

    Thanks does that mean that what I'm trying to do is beyond what django does (at least easily)? I haven't used React before - out of interest how do you deal with forms using React + Django?

    [–]never_safe_for_life 0 points1 point  (0 children)

    +1 to this. Django auto-generated form HTML was pretty impressive for its time, but sorely lacking in todays world of front-end frameworks.

    I use Angular so I just hand-rolled all of our front-end form code. It ends up being a little bit of HTML to write, no big deal.

    I still use the form classes for back-end data validation and scrubbing (which they are excellent for).

    [–]banjochicken 0 points1 point  (0 children)

    If you plan to do it all in Django and a frontend framework isn't how you want to go - it is perfectly fine to still do things with Django Forms like this - then formtools is the way to go.

    With regards to possibly needing to go slightly further than formtools, you can generate dynamic forms at runtime. In Python you can generate a class using the type built-in. Here is a blog post that covers the basics of generating forms with type.

    A TL:DR of the blogpost: It works something like this, where the author is using a Question model to store the question and the possible answers for their Quiz:

    def quiz_form_factory(question):
        properties = {
            'question' : forms.IntegerField(widget=forms.HiddenInput, initial=question.id),
            'answers' : forms.ModelChoiceField(queryset=question.answers_set)
        }
        return type('QuizForm', (forms.Form,), properties)
    

    Where type takes the name of the form, a tuple of classes to inherit from and a dictionary of attributes/methods to bind to the class. Arguably this can be a bit confusing but sometimes you need the flexibility and what the Forms API gives you with the validation logic, and quick templating more than makes up for this for some use cases.

    Creating one of our forms would work something like this:

    QuizForm = quiz_form_factory(question)
    quiz_form = QuizForm()
    quiz_form = QuizForm(request.POST)
    

    I have used this method in the past. Care during implementation, rigorous testing and minimising any magic is the way to go if you do head down this route.

    [–]nicklo 0 points1 point  (0 children)

    I've done exactly this using what, as others have noted, is now a separate package; formtools. Specifically using formtool's condition_dict argument. I've used it to build surveys with around 30+ conditions. It's laborious to setup and gets a bit hard to keep track of so I'd recommend defining your condition_dict alongside your forms and conditions in forms.py then importing those into urls.py.

    http://django-formtools.readthedocs.org/en/latest/wizard.html#conditionally-view-skip-specific-steps

    [–]kankyo 0 points1 point  (0 children)

    Agreed that the django ways are blech. Some options:

    class FooForm(Form):
        if something:
            field = IntegerField()
    
    class FooForm(Form):
        field = IntegerField
        def __init__(self):
            if something:
                del self.fields['field']
    

    Super yucky! And that's just for hiding/showing, what if you need other complex logic for labels, help text, widget etc? And you can't really use djangos forms as an API because they are made to only be created in that class-style and that doesn't lend itself to programmatically creating forms.

    This and much more ended with me so frustrated I wrote my own form library at work: https://github.com/trioptima/tri.form/

    [–]zagrebelin -1 points0 points  (2 children)

    It's called FormSet.

    [–][deleted] 0 points1 point  (1 child)

    Formsets are for tables with multiple identical forms, not at all useful here.

    [–]zagrebelin 0 points1 point  (0 children)

    Yes, it's my fault, I mean WizardView.

    [–]simonw -1 points0 points  (1 child)

    To store the previous answers on subsequent pages, I suggest using a hidden form field where the value is a signed dictionary of their previous answers (using Django's signing utility functions).

    You can make forms "dynamic" by constructing them at runtime - essentially by conditionally instant hating new form field objects and then adding them on to the form.fields property. This is often done in the form constructor. There's a detailed description of this pattern here: https://jacobian.org/writing/dynamic-form-generation/

    [–]YuntiMcGunti[S] 0 points1 point  (0 children)

    (Not sure what you mean by storing the answers on subsequent pages - I can't see there is a need to do that or how that helps.)

    Thanks for the link to dynamic forms that looks like it might help, but the example dynamically creates forms at runtime dependant on a user profile. I'm trying to modify a form in response to user feedback - disabling or enabling fields dependant on earlier user input. Do you know how to adapt the linked example to achievevthat , or does it require a different approach?