I'm looking for the most pythonic way to handle a complex form on a single-page. My "submit" page has a series of questions that, based on the user's input, will generate different forms/fields.
A simplified example:
--- Shift 1 --> Form #1
--- Yes -- What shift? -- |
| --- Shift 2 --> Form #2
|
Enter Date --- Did you work? ---|
|
| --- Yes --> Form #3
--- No -- Was this PTO? ---|
--- No --> Form #4
I'm trying to figure out most efficient/pythonic way to handle the above.
Possible Approaches:
Lots of jquery, ajax, and function based views. This is my current setup, but I'd like to move it to CBV and ModelForms if possible because my current code is about 2000 lines (with 10+ ajax functions, 10+ view-handling urls, and too many input fields to count) within my template/models/views just to load/process this form. It is a complete nightmare to maintain.
Single page with ModelForms embedded within a dynamic div. The form page calls a jquery load depending on your answers to the questions. For instance, if you answered "Today", "Yes", "Shift 1", I would call $("#form-embed").load("/forms/form1/") which contains the Form #1 ModelForm. A couple problems with this: 1. Some of the "questions" are actually form fields, and if I'm reloading a blank form, then that field would be blank. I could solve this with JS on the front-end or custom cleaning on the back-end, but this ends up being a lot of code as well. And 2. Form POSTing and error handling would be pretty complex with so many potential forms on one page.
Same as above but with an Iframe. This solves some of the above issues, but feels "icky" for some reason and makes me worry about responsive UI/compatibility across platforms.
Multiple pages, each with its own form. This eliminates the convenience of a single-page approach, which I'd really like to keep if possible.
Combine all my models into a single model. This gets pretty complex pretty quickly because the actual fields involved on each form are very different, and the logic handling would therefore get messy and bloated.
Create a new single model with a corresponding modelform for use only with this form that contains all possible fields, dynamically displayed via JS based on user input, then routes the fields to the appropriate models after submission.
Don't use modelforms, instead create my own custom Form Class that involves all possible fields, then route to the appropriate models after submission. Use JS to display/hide fields based on user input.
Is there an approach I'm missing, or is one of these the best? These would all theoretically work just fine, but none of them feels very pythonic. #7 maybe feels like the best option, but I'm still not super happy with it.
My hope for the front-end is to maximize ModelForm and Django's template management, while minimizing JS. My hope for the back-end is to maximize CBV/ModelForm customization while minimizing FBV. My overall goal is to reduce code volume and improve maintainability/modification.
Looks like the functionality I was looking for is the class-based view FormView and forms.Form (not forms.ModelFormView).
In forms.py, I created a forms.Form form which contained all possible fields (ended up being about 10, able to reuse several. Had to make a lot of them required=False), then loaded them all at the same time in individual divs within a FormView. Each div shared the name of the field, so I could easily hide/show each div using simple JS depending on what combination of fields I wanted to display based on user input.
I also included a hidden "form number" field which updates based on the final input, which helps to direct my form validation.
I then used the def clean(self) mixin in my forms.py to process each individual form.
Haven't finished the entire thing yet, but it's already looking like my code length will be cut from 2000+ down to a couple hundred or so. Plus this will be much easier to maintain, and keeps most of my logic on the serverside rather than in the html file.
Related
I've a source field:
source = SelectField("Source", choices=[
('option1', 'option1'),
('option2', 'option2'),
('option3', 'option3'),
validators=[InputRequired()])
Then an uri field which requires a different regex based on the above:
uri = StringField('URI', Regexp(regex=r'regex1'))
Eg. if option1 is selected regex will be regex1, if option2 regex2, and so on.
What would be the proper way to implement it with WTForms?
This functionality doesn't exist in WTForms. You'll have to tool this yourself. This quickly gets nontrivial, depending on your specific application logic/needs.
One way would be to have client-side JS send requests to a view that dynamically generates a new form, adding on the subsequent sections of the form based on inputs. For example, your fields could have an onChange or onBlur function that sends the inputs to a view that returns a new form with the next set of fields added on.
Another way to go about this would be with some clever form rendering, CSS and JS. For example, the form would be rendered with all possible fields, but the fields will be hidden and revealed conditionally based on previous selections through via JS/CSS. One concern with this method is that validation becomes complicated.
There are a few writeups out there on how this is accomplished with Django Forms, but not WTForms. Though, the same concepts should be readily applicable to WTForms.
Related docs: dynamic form composition.
I'm dynamically generating a form using WTForms with Flask (based off of a custom form schema), which I'd like to split for UX reasons over multiple pages / refreshes. I'm trying to figure out what is the best way to achieve this. My current thinking is the following options:
Set all not rendered questions to hidden widgets and somehow handle any validation on them.
Reimplement / steal django's form wizard and dynamic section up the form into many smaller forms.
I'm going to have to add some sort of concept of sections into the Meta of the form class to handle the layout abstraction but I'm perplexed as to which of the two options (or others I haven't thought of) might be the best way to achieve the web UX?
If these were hard coded forms, I'd just have one form per page and handle the interim logic in the view, but I need to be able to dynamically generate them unfortunately.
You're trying too hard to make HTTP into a stateful protocol when it was designed to be a stateless protocol
Your best bet is to hide the fact that there are two steps from the server. Just show two different pages to the user and keep the input values in memory until you have all of them and can send them to the server.
You haven't said which technology you're using for the UX btw, but this is a classic HTTP problem which is best solved on the frontend.
I have a form which is dynamically generated. I cannot predict how many text inputs or multiple choices will be included before hand. Thus, I cannot use ModelForm or anything similar.
I wonder if I can leverage the built-in validating code from Django fields and apply them manually somehow, because I can determine the type of user inputs (e.g. whether if it is text, radio button, checkbox, etc.). Something like this would be great:
if type == 'Text':
#perform validation
#get clean data
elif type == 'Checkbox':
#perform validation
#get clean data
UPDATE: My site allows users to generate a survey according to their needs. So for example, an user decides to create a survey with two questions:
what is your name?
what's the brand of your smartphone? A) iPhone B) HTC
The first question is <input type="text"> and the second one is <input type="radio">
The information about what types of questions are generated is stored in the database. So I would have rows like this in the DB to describe the question:
ID TYPE TEXT
1 TEXT What is your name?
2 RADIO what is your name?
When the survey goes online and an user fills it out. I need to validate their input. How can I utilize the built-in validation functionality of Django to deal with this situation?
I'd recommend you using model formsets, as they are here for exactly the reason you've said: dealing with the arbitrary count of fields. And that way you still will be able to use Django's validation (which is very convenient).
If you post a sample form you're dealing with, I would be able to draft a formset example.
Update: Ah, so you need a survey functionality? It's a totally different beast. Then I can't imagine a reason not to leverage one of the existing solutions. The ed-questionnaire
package seems to be the most featured and mature one. And as always with Django, you can relatively easily customize it away.
If I haven't convinced you yet, just take a look at its models.py file. Do you really have the time and will to re-implement all that stuff?
[Preamble: Whereas I realize there may be simpler ways to do this (i.e., just use Django built-in Admin, or use inlines to do all editing on one page, etc.), unfortunately, I am not in control of the designs, so I'm seeking help on how to work with what I've been tasked with implementing.]
I have 2 models, Deck and Slide. Slide has foreign key to Deck. (there is also an intermediate model--its a m2m relationship--but to simplify an already complex discussion, I'm going to pretend its a simpler many-to-one relationship.)
The interface i am implementing displays a page with a form to enter or edit Deck fields which also includes an embedded list of Slides in the Deck, with some fields (which I'm in the process of making text inputs) and also with an "edit" and a "remove" anchor link for each Slide. (see img) If you click an "edit" link, it takes you to a new page with a detailed form to input all the information representing the content for the corresponding slide. If you click submit in that form, it takes you back to the page for the deck.
As the title question proposes, I obviously don't want to commit any Deck or Slides to the DB until a user clicks Submit for the entire Deck, i.e. they can add or edit many slides in the interim and may decide to cancel the whole process.
What is the best, cleanest way of doing this?
I've looked at Django's FormWizard class ( http://docs.djangoproject.com/en/dev/ref/contrib/formtools/form-wizard/ ), but seems to be geared towards a multi-step linear flow, not my situation.
I already had to implement this for a demo, and I've gotten most of the way there by creating an inline FormSet for the Slides and a Form for the Deck, and then writing subclasses for my Forms that hide the form and the formset as I pass back and forth between the two pages. Here's some code demonstrating how I use a hidden form for the deck in the slide page:
class DeckForm(ModelForm):
class Meta:
#stuff here
class HiddenDeckForm(DeckForm):
def __init__(self, *args, **kwargs):
super(DeckHiddenForm, self).__init__(*args, **kwargs)
for name, field in self.fields.iteritems():
field.widget = field.hidden_widget()
field.required = False
So in my views, every time I go to the slide form page, I generate a HiddenDeckForm from the POST data and pass it in, and then in my view going back to the deck page, I regenerate the DeckForm (not hidden subclass) from the POST data. Not going to post all my Slide form code, since I'm basically asking whether there is a better way of doing it, but similarly, I have a HiddenSlideForm class, and pass a formset of those between my templates to save the state.
Then when the user clicks submit on the Deck page, the Deck form and Slides formset are all saved to the DB.
It works, but is this a good way to do it? Its been a good bit of code, and I've really had to dig into some Django internals--feel's like I'm using things in a way they weren't designed to be used. Or is there already a more standard way of handling this scenario?
I'll post more code if its helpful.
Thanks for the help!
PS As you can see, I'm implementing it in a jquery colorbox and using ajax for the transitions between the forms, but I am just using normal template/form rendering in my views and passing the rendered html back to the page with an ajax call . . . guess I could do something with json, but strongly prefer to stick with passing a rendered template, as such an implementation could be used without ajax or javascript if desired.
Idea A: Store the uncommitted data in the session until everything is ready for commit. Each form/roundtrip to the client just adds/updates the accumulating data. If they abandon it you only have crap lying around until the session is destroyed. You can save pretty much anything to the session.
Idea B: Add a boolean to the database table which indicates when a row has been committed (eg: "is_active" or "is_pending"). Means you never lose anything but a bit more of a nuisance to manage.
Having done the very similar requirement by working around the formset tools, I completely agree that going hoops and bounds to understand/customize the formset is totally not worth it.
For a screenshot like yours, I'd use just one form with all of the slides per deck. - Form, not formset.
You should handle the slide "edit/delete/new" all in ajax requests that include the deck you create when someone creates a "new deck". And then in the "Deck Form", you only change the Deck properties like name and association.
Or, if you are inlined to do all new elements in the page itself without Ajax and creating new "Slide" objects, you can use the formset and save the deck and all associated slides.
Can anyone point to an example written in Python (django preferred) with ajax for cascading forms? Cascading Forms is basically forms whose field values change if and when another field value changes. Example Choose Country, and then States will change...
This is (mostly) front-end stuff.
As you may have noticed Django attempts to leave all the AJAX stuff up to you, so I don't think you'll find anything built in to do this.
However, using JS (which is what you'll have to do in order to do this without submitting a billion forms manually), you could easily have a django-base view your JS could communicate with:
def get_states(request, country):
# work out which states are available
#import simplesjon as sj
return sj....
Then bind your AJAX request to the onchange event of the select (I can't remember if that's right for select boxes) and populate the next field based on the return of the JSON query.
10 minute job with jquery and simplejson.
I would also suggest considering getting a mapping of all data once instead of requesting subfield values one by one. Unless the subfield choices change frequently (states/cities change?) or huge in numbers (>1000) this should offer best performance and it is less complex.
You don't even need to create a seperate view, just include a chunk of JavaScript (a JSON mapping more precisely) with your response containing the form.