(window.webpackJsonp=window.webpackJsonp||[]).push([[167],{691:function(t,s,e){"use strict";e.r(s);var a=e(56),n=Object(a.a)({},(function(){var t=this,s=t.$createElement,e=t._self._c||s;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("h1",{attrs:{id:"create-a-custom-wtform-widget"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#create-a-custom-wtform-widget"}},[t._v("#")]),t._v(" Create a custom WTForm widget")]),t._v(" "),e("div",{staticClass:"custom-block tip"},[e("p",{staticClass:"custom-block-title"},[t._v("TIP")]),t._v(" "),e("p",[t._v("List of all code changes made in this lecture: "),e("a",{attrs:{href:"https://diff-store.com/diff/section14__13_create_custom_wtform_widget_multiline_input",target:"_blank",rel:"noopener noreferrer"}},[t._v("https://diff-store.com/diff/section14__13_create_custom_wtform_widget_multiline_input"),e("OutboundLink")],1)])]),t._v(" "),e("p",[t._v("We're now going to begin working on movie editing. To do this, we'll be creating a longer form where users can change all of the form data.")]),t._v(" "),e("p",[t._v("There are many ways to allow users to change list-style data (the genre, cast, and series info). These range from super simple to extremely complicated.")]),t._v(" "),e("p",[t._v("In the simple end of the scale, we can have a normal input field that sends data to Flask. We could ask users to type CSV data, and then in Flask we would separate the inputs on the comma.")]),t._v(" "),e("p",[t._v("For example, users could type "),e("code",[t._v("Keanu Reeves,Carrie-Anne Moss")]),t._v(" and we would turn it into two strings, and put that into a list and store it in the database.")]),t._v(" "),e("p",[t._v("In the complex side of the scale, we can have an individual input field for each actor, and then use buttons to add more actors or delete them.")]),t._v(" "),e("p",[t._v("The more complex your user interface becomes, and the more user interactions you want to support, the more likely you are to need JavaScript. That's because browsers run JavaScript code, so if you want to change the number of input fields without refreshing the page, then simply JavaScript is the only way to do it.")]),t._v(" "),e("p",[t._v("I don't want to bring in JavaScript at this point in the course, but I also don't want to use a solution as ugly as asking users to type CSV into a long input field.")]),t._v(" "),e("h2",{attrs:{id:"using-a-multi-line-text-area-for-list-style-inputs"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#using-a-multi-line-text-area-for-list-style-inputs"}},[t._v("#")]),t._v(" Using a multi-line text area for list-style inputs")]),t._v(" "),e("p",[t._v("My solution is to use a text area, and ask users to type one value per line. That could be one actor, one genre, or another movie in this series.")]),t._v(" "),e("p",[t._v("Now we have again a choice: to do all the processing of the textarea values in the Flask endpoint, or to do it in the "),e("code",[t._v("WTForm")]),t._v(" form itself.")]),t._v(" "),e("p",[t._v("If we only had one field that had to accept this type of data, I would do it in the endpoint. But since we have three fields, I think it's going to be more effective to do it using "),e("code",[t._v("WTForm")]),t._v(".")]),t._v(" "),e("p",[t._v("WTForms already has a "),e("code",[t._v("TextAreaField")]),t._v(" that we can use for displaying a textarea and getting the text written in it, but it doesn't handle splitting the written text into multiple lines and giving us back a list of strings.")]),t._v(" "),e("p",[t._v("Let's start with that. We'll write a Python class that extends "),e("code",[t._v("TextAreaField")]),t._v(" and modifies one of its methods:")]),t._v(" "),e("div",{staticClass:"language-py extra-class"},[e("pre",{pre:!0,attrs:{class:"language-py"}},[e("code",[e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("from")]),t._v(" wtforms "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" TextAreaField "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# among others")]),t._v("\n\n\n"),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("StringListField")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("TextAreaField"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("def")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("process_formdata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("self"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" valuelist"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# checks valuelist contains at least 1 element, and the first element isn't falsy (i.e. empty string)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" valuelist "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("and")]),t._v(" valuelist"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n self"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("line"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("strip"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("for")]),t._v(" line "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("in")]),t._v(" valuelist"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("split"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"\\n"')]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("else")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n self"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v("\n")])])]),e("p",[t._v("With this, when we use "),e("code",[t._v("StringListField")]),t._v(" in a WTForm, and the form is submitted, it will split the text inside the textarea and set the field's "),e("code",[t._v("data")]),t._v(" property to the list of strings.")]),t._v(" "),e("p",[t._v("We also need another method: one to go the other way round, from a list of strings into the textarea's content.")]),t._v(" "),e("p",[t._v("Why do we need this method?")]),t._v(" "),e("p",[t._v("Let's say the form is submitted but validation fails. We will re-display the form with the form data that the user already wrote. But by the time we do that, this class will already have set "),e("code",[t._v("self.data")]),t._v(" to the list of strings, and it won't be able to turn it back into text to put in the HTML field.")]),t._v(" "),e("p",[t._v("We can add a "),e("code",[t._v("_value()")]),t._v(" method to the class that takes care of formatting "),e("code",[t._v("self.data")]),t._v(" into a single string that can be placed inside the textarea:")]),t._v(" "),e("div",{staticClass:"language-py extra-class"},[e("pre",{pre:!0,attrs:{class:"language-py"}},[e("code",[e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("StringListField")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("TextAreaField"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("def")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("_value")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("self"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" self"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"\\n"')]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("join"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("self"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("else")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('""')]),t._v("\n\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("def")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token function"}},[t._v("process_formdata")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("self"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" valuelist"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# checks valuelist contains at least 1 element, and the first element isn't falsy (i.e. empty string)")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" valuelist "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("and")]),t._v(" valuelist"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n self"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("line"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("strip"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("for")]),t._v(" line "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("in")]),t._v(" valuelist"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),e("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("split"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"\\n"')]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v("\n "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("else")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n self"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v("\n")])])]),e("p",[t._v("That's it for our custom field! Those two methods do everything we need.")]),t._v(" "),e("h2",{attrs:{id:"create-an-extended-movie-form"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#create-an-extended-movie-form"}},[t._v("#")]),t._v(" Create an extended movie form")]),t._v(" "),e("p",[t._v("Now let's create our movie editing form, similarly to how we created the "),e("code",[t._v("MovieForm")]),t._v(":")]),t._v(" "),e("div",{staticClass:"language-py extra-class"},[e("pre",{pre:!0,attrs:{class:"language-py"}},[e("code",[e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("from")]),t._v(" wtforms "),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("import")]),t._v(" URLField "),e("span",{pre:!0,attrs:{class:"token comment"}},[t._v("# among others")]),t._v("\n\n\n"),e("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),e("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("ExtendedMovieForm")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("MovieForm"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v("\n cast "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" StringListField"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Cast"')]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n series "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" StringListField"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Series"')]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n tags "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" StringListField"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Tags"')]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n description "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" TextAreaField"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Description"')]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n video_link "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" URLField"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Video link"')]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n\n submit "),e("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" SubmitField"),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),e("span",{pre:!0,attrs:{class:"token string"}},[t._v('"Submit"')]),e("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])])]),e("div",{staticClass:"custom-block tip"},[e("p",{staticClass:"custom-block-title"},[t._v("TIP")]),t._v(" "),e("p",[t._v("By extending "),e("code",[t._v("MovieForm")]),t._v(", we get to re-use all the previously defined fields and their validation rules. By re-defining the "),e("code",[t._v("SubmitField")]),t._v(" with the same name, we can easily change its label.")])]),t._v(" "),e("p",[t._v("That's it for this lecture! In the next few lectures we'll handle rendering this form and validating it.")])])}),[],!1,null,null,null);s.default=n.exports}}]);