Building theatlantic.com homepage’s WYSIWYG admin with Django and Knockout
Dintino, Frankie
While the front-end of theatlantic was written in PHP up until its recent rewrite, we have relied on a robust Django-powered admin to manage content for nearly two years. At the time when we began coding the redesign we had already developed an adequate solution for curating content into modules on our site: a combination of Grappelli’s drag-and-drop sortable inline feature and django-nested-admin, a project we wrote for nested InlineModelAdmins. However, it soon became clear that our current system would not meet the needs of editors managing The Atlantic’s new responsive and visually-striking homepage. The workflow employed by the editors with our sortable nested inlines—edit, save, preview; adjust, save again, preview; …—would have been too burdensome. This challenge led me to propose we build a new tool that could “live-edit” the homepage in a WYSIWYG interface. It occurred to me that, if we could find a way to bind the ModelAdmin’s formsets to a javascript model, and used one of the many MVC javascript frameworks, we could build the interface using two-way data binding to sync changes with a hidden form. A project that would have taken months could, with the right framework, be built in just a few weeks. So why Knockout.js? I evaluated most of the popular options. Though I initially adopted AngularJS, I later abandoned it because, while it is a fine framework, it is not ideal for integrating with DOM elements that live outside angular. I stumbled through quite a few angular controllers and directives (violating their best practices every step along the way) before changing direction. Knockout, by comparison, turned out to be absolutely perfect for the task at hand. This talk will discuss what was involved in using Knockout to build two-way data binding with django formsets, and how we implemented sorting with drag-and-drop functionality, inline editing of html, and image uploads and cropping. It will also touch briefly on the challenges we faced making everything testable, and feature a live demo of updating theatlantic.com homepage using our new modular Django CMS.
 Music So first of all I'd like to thank DjangoCon for selecting a presentation. Thank you Atlantic for helping me send me here and thank all of you for coming to see my presentation. So today I'm going to be talking about building the in-line app.com homepages with the Wiggy Min with Django and KnockoutJS. For the purposes of demonstrating the techniques here, I created sort of bare bones repo that illustrates the techniques that you can check out and run at your leisure. So introductions. My name is Frankie Dantino as was mentioned. I work for the Atlantic. If you're not familiar with it, it's a 158 year old monthly publication. It's been a website for 20 years of those I've been working at for five. If you're not immediately familiar with the name, you may have seen one of our recent magazine features such as What ISIS Really Wants, The Case for Reparations, Why Women Still Can't Have It All, or The Christy the American Military. So in April of this year, we launched a redesign of the site, which had been in the works for a very long time. The front end was written in PHP, so the back end at this point had been migrated to Django. The site was not responsive and the homepage had a fixed layout. So you had a carousel module that rotated four items, then a row of five items, a list of three, and then a video and a grid, and that never changed. As a brief aside, almost everything you see on the Atlantic.com homepage, and this has been the case for quite a while, is hand selected or curated. There are two people whose job it is to select every sort of homepage. I ran the numbers and the main number of updates per day for the homepage is 33. Some of those are typo fixes and things like that, but it's still a fairly impressive number. On many days, the homepage is the most highly-tracked page on our site. We also feel that the homepage serves as a representative for the brand, but for the content that we think is the most compelling that we've written. Before the redesign, the admin interface looked like this. If you're familiar with Grapelli, it's basically Grapelli inlines, though using a module we built called Django-Invested Admin that allows you to drag and drop between inlines. And arbitrarily nest the inlines. Some people might characterize that as admin abuse, but we've got it to be extremely helpful. So with the new homepage, it was going to be fully responsive, rewritten in Django, and we were going for a flexible modular structure that would allow editors to format the group articles and build a rhythm as the user scrolls down the page. So here's a sort of mattering of modules that you might see on the homepage. And I've already used this term, but the operating concept of the homepage design is that there are rows of more, of one or more articles, which we call modules, that can be freely arranged in whatever order. With feedback from user testing, actually, we're adding even more modules. These are the ones that we launched with so that we can make the site more dense. We found this approach to be sufficiently flexible while still maintainable. So why rebuild the admin? Why would the old system of nested inlines not suffice for the new homepage? First of all, the new homepage is increasingly visual. They're really gorgeous, aren't selected for stories and, you know, a lot more variety in how things are arranged. So the cycle that they had been using previously where they would edit something, preview the homepage live, then go back and edit it and save again and go and preview would be really cumbersome for something that, particularly in a responsive situation, our editors are perfectionists. And if something doesn't wrap quite right at 450 pixel wide, they're going to go back and edit the copy. Also, I thought it would be a fun challenge. So the goal was to build a WYSIWYG interface to edit the homepage while we're using as much existing code as possible. So that existing code I mentioned briefly a moment ago, Django nested admin. You can find it on GitHub with the address here. As I said, it allows for arbitrarily deep nesting of inlines in the admin and provides a drive and drop functionality similar to Django propellities that can run with or without propelling. I rewrote Django nested admin for the purpose of this project to expose an API so that I could call methods that perform operations such as delete, insert, and splice programmatically rather than them working the way that they currently do in group L. Where it's based on event handlers on user input by clicking a button or tracking and dropping. So I can talk to the plan for how I might achieve this WYSIWYG interface. I'm going to dwell on this diagram for just a moment because while it's maybe a bit overwhelming, I think it really gets to the heart of the technique being discussed in this presentation. So you have Django nested admin, which let's say you visit the admin page to edit the homepage. Django nested admin renders all the inlines and form fields. What I wanted to do was take those form fields and build a big JSON object that's basically serializes the form data. Hook that into a knockout view model or Angular, though in this case it turned out to be knockout, and set up two-way data binding between the original form fields and the values that I serialized from there. Create a view that would be the template, and then that template would have buttons and drag and drop ability that the user could interact with, and that would update the view model which would in turn call Django nested admin APIs, and then the cycle would repeat. So this was how I'm going to dwell on this building. Now, I was going to talk about why I picked knockout over Angular, but I'm a little concerned for time. So if I have some extra time at the end and people are interested, I'm happy to talk about it, or if I don't, you know, run into me and ask me, but I'm just going to skip these slides for now. Anyway, decision use knockout spoiler alert, if you couldn't tell from the title at the top. But I wanted to use Angular's expression compiler, which is definitely weird. I had to recognize. Somebody had helped pull out that functionality from Angular and created an NPM module. I forked it because I wanted to add some newer Angular features to it, but I'll explain why in a moment. So how do we get from this top row where we go from form fields to the JSON object to the view model? A brief refresher, Django in the admin Django has a pretty simple convention for how it names form fields and inline fields. Form fields is just the name of the field. You have a field called title, that's the name in the form. Field called content is named content. For inlines, it's not that much more complicated. You have the related name, so in this case, we had an item that is a foreign key to a module. So the reverse foreign key is module underscore set. The module underscore set dash and then an index for which inline it is in the list, and then the field name on that inline. And this convention is followed everywhere. For Django in the admin, for the arbitrarily deep nesting, we just sort of continue to do more prefixes so that it would be like module underscore set dash zero dash item underscore set dash one dash field name. So here's like a really simple form submission of ridged of somebody saving a form with two inlines. So you have an ID and name field. You have total forms, initial forms and maximum forms. Those are management form fields. The management form are management form fields, are hidden fields that tell Django how many inlines were there originally and how many were added so that it knows which ones should be update operations and which ones should be insert operations. And then you have, you know, here module set dash zero dash ID. That's the ID of the first one, then the second one, module set one. Doesn't have an ID because it's new, and there's its title. So you can do a fairly straightforward conversion of the syntax of the Django input forms into an angular expression that would map with JSON object. So obviously if there's no inlines, just map it to the property ID as ID and name is name. But for an inline, so instead of module underscore set dash zero dash, you have module underscore set, which is now an array, and then array index zero and then dot ID because that thing would be an object. The thing that Angular Expressions gets us is that we can compile this expression, pass it an empty JavaScript object, and it'll build all the intermediate steps. So zero won't be an undefined error. Zero, it'll construct the array and then build an empty object and then assign the ID property with the value that you tell it to. And then just by convention, I needed something to do with the management forms. They couldn't go with the module set array because they're different properties and they apply to the inline as a whole rather than each individual row in the inline. So by convention, I call them underscore underscore mgmt, but there's nothing special about that. So with that initial form submission, the serialized data would look something like this. You have your in-eating, your management form values, your module set array, and that contains objects that have the properties of those inlines. So here's a brief code sample that sort of shows the process from start to finish of building the view model and finding the phone fields. So we require the Angular Expressions, which actually I don't really do. I suppose it's a global object because it requires you to have some work in Django at the end, but that's beside the point for Clare to actually do it this way. And then obviously, I would pull the name off of, I would iterate over the inputs, pull the names off of them and use regular expressions to invert them. But just for demonstration purposes, I used one particular field. So you have module underscore set dash zero dash title, the Django version, the Angular version, and then the knockout version is identical to the Angular version except arrays need to have an open-close parentheses because they're technically callable in knockout. So it's a weird quirk. It seems like something that they could do away with, but it is what it is. So I grab the input field, compile the expression, and assign the data. That, like I said, builds up the empty parts of the JSON object, so it'll build a module set object in the top level. That'll be an array. It'll fill in the first one with a new object and set the title to foo. After I do that, I add a data-bond attribute to the input. Knockout functions just by the data-bond attribute. Every way of binding behaviors or values to the model is done by the data-bond attribute. So data-bond and then value is the bind handler for form field values. Value colon, the knockout expression, build the view model. I'm using a plugin called knockout mapping, which it allows you to pass and adjust on objects and build a view model without all these intermediate steps. And then apply the bind name from the view model, which will find all the inputs with data-bond attributes and add the matchup to them. The end result in the form would be like an input element with its name and then data-bond value colon and then the knockout expression. And that would have two-way data binding. So if somebody edited that form field, it would update the view model. And vice versa, if we programmatically updated the view model, it would update the form field automatically. So now we have two-way data binding. What do we do from here? Well, we build a template that looks like a homepage. Luckily for me, my co-worker Chris Barna is really skilled in CSS and Flexbox and is able to take all of the different designs for the modules and use the exact same markup for everything. So every article has the same markup on the homepage. Every module is just nested ULs and LIs, which makes building the knockout version of the template really simple. So I use the for each bind, iterate over the modules and iterate over the items and then have the HTML for the articles. And then that is sort of like a dynamic version now of what we were otherwise doing with gender templates. Once we had that, we could add knockout behavior bindings. So integrations with things like CK Editor for inline text editing, select two for dynamic dropdowns, sortables so that users can drag and drop to rearrange things, and it's a plug for another open source project of ours, but DjangoCropDuster so they can upload images and crop them however they see fit. So now I'm going to do a brief demo of the whole process. So this is the demo version of the homepage. I'm going to mess around with it a little bit, sort of show the features that I feel will be using this technique. So you can delete things, undelete them. We have something called the stash which you can drop things into for safekeeping later. And then if you want to return them, just sort of drag them back in. You can rearrange things by dragging and dropping. Normally. Here we go. And you can click to just edit any of the text anywhere. And these little labels, pull down, select two, drop down, click on the right icon. Well, we'll just pretend that it happened. There should be a named rule for whenever you give a demo, you don't uncover these one by one. So I'm going to switch out the top module with a feature from the next magazine issue. So click on it, choose what type of content, magazine article. There we go. Save. And we hooked it up so that it'll pull the image field off of the article and then re-crop it automatically to fit the art direction of the homepage when it's imported. So that can take a little bit time for him to process it. White dogs still can't have it all written by me with a cover shoot of my dog. And we'll go ahead and save. And there's our homepage with the Hero Adventures. Last thoughts and questions. I think we can make really neat user experiences. For instance, you could take this technique and combine it with Masonry, which is like a JavaScript library for arranging grids and maybe Django CMS and build like a really fully featured, flexible dashboard builder. We got a lot of everybody. Let's thank Frankie for coming. Thank you.
 Music  So first of all I'd like to thank DjangoCon for selecting a presentation.  Thank you Atlantic for helping me send me here and thank all of you for coming to see my presentation.  So today I'm going to be talking about building the in-line app.com homepages with the Wiggy Min with Django and KnockoutJS.  For the purposes of demonstrating the techniques here, I created sort of bare bones repo that illustrates the techniques that you can check out and run at your leisure.  So introductions. My name is Frankie Dantino as was mentioned.  I work for the Atlantic. If you're not familiar with it, it's a 158 year old monthly publication.  It's been a website for 20 years of those I've been working at for five.  If you're not immediately familiar with the name, you may have seen one of our recent magazine features such as What ISIS Really Wants, The Case for Reparations,  Why Women Still Can't Have It All, or The Christy the American Military.  So in April of this year, we launched a redesign of the site, which had been in the works for a very long time.  The front end was written in PHP, so the back end at this point had been migrated to Django.  The site was not responsive and the homepage had a fixed layout.  So you had a carousel module that rotated four items, then a row of five items, a list of three, and then a video and a grid, and that never changed.  As a brief aside, almost everything you see on the Atlantic.com homepage, and this has been the case for quite a while, is hand selected or curated.  There are two people whose job it is to select every sort of homepage.  I ran the numbers and the main number of updates per day for the homepage is 33.  Some of those are typo fixes and things like that, but it's still a fairly impressive number.  On many days, the homepage is the most highly-tracked page on our site.  We also feel that the homepage serves as a representative for the brand, but for the content that we think is the most compelling that we've written.  Before the redesign, the admin interface looked like this.  If you're familiar with Grapelli, it's basically Grapelli inlines, though using a module we built called Django-Invested Admin that allows you to drag and drop between inlines.  And arbitrarily nest the inlines.  Some people might characterize that as admin abuse, but we've got it to be extremely helpful.  So with the new homepage, it was going to be fully responsive, rewritten in Django, and we were going for a flexible modular structure that would allow editors to format the group articles and build a rhythm as the user scrolls down the page.  So here's a sort of mattering of modules that you might see on the homepage.  And I've already used this term, but the operating concept of the homepage design is that there are rows of more, of one or more articles, which we call modules, that can be freely arranged in whatever order.  With feedback from user testing, actually, we're adding even more modules.  These are the ones that we launched with so that we can make the site more dense.  We found this approach to be sufficiently flexible while still maintainable.  So why rebuild the admin?  Why would the old system of nested inlines not suffice for the new homepage?  First of all, the new homepage is increasingly visual.  They're really gorgeous, aren't selected for stories and, you know, a lot more variety in how things are arranged.  So the cycle that they had been using previously where they would edit something, preview the homepage live, then go back and edit it and save again and go and preview would be really cumbersome for something that, particularly in a responsive situation, our editors are perfectionists.  And if something doesn't wrap quite right at 450 pixel wide, they're going to go back and edit the copy.  Also, I thought it would be a fun challenge.  So the goal was to build a WYSIWYG interface to edit the homepage while we're using as much existing code as possible.  So that existing code I mentioned briefly a moment ago, Django nested admin.  You can find it on GitHub with the address here.  As I said, it allows for arbitrarily deep nesting of inlines in the admin and provides a drive and drop functionality similar to Django propellities that can run with or without propelling.  I rewrote Django nested admin for the purpose of this project to expose an API so that I could call methods that perform operations such as delete, insert, and splice programmatically rather than them working the way that they currently do in group L.  Where it's based on event handlers on user input by clicking a button or tracking and dropping.  So I can talk to the plan for how I might achieve this WYSIWYG interface.  I'm going to dwell on this diagram for just a moment because while it's maybe a bit overwhelming, I think it really gets to the heart of the technique being discussed in this presentation.  So you have Django nested admin, which let's say you visit the admin page to edit the homepage.  Django nested admin renders all the inlines and form fields.  What I wanted to do was take those form fields and build a big JSON object that's basically serializes the form data.  Hook that into a knockout view model or Angular, though in this case it turned out to be knockout, and set up two-way data binding between the original form fields and the values that I serialized from there.  Create a view that would be the template, and then that template would have buttons and drag and drop ability that the user could interact with, and that would update the view model which would in turn call Django nested admin APIs, and then the cycle would repeat.  So this was how I'm going to dwell on this building.  Now, I was going to talk about why I picked knockout over Angular, but I'm a little concerned for time.  So if I have some extra time at the end and people are interested, I'm happy to talk about it, or if I don't, you know, run into me and ask me, but I'm just going to skip these slides for now.  Anyway, decision use knockout spoiler alert, if you couldn't tell from the title at the top.  But I wanted to use Angular's expression compiler, which is definitely weird. I had to recognize.  Somebody had helped pull out that functionality from Angular and created an NPM module.  I forked it because I wanted to add some newer Angular features to it, but I'll explain why in a moment.  So how do we get from this top row where we go from form fields to the JSON object to the view model?  A brief refresher, Django in the admin Django has a pretty simple convention for how it names form fields and inline fields.  Form fields is just the name of the field.  You have a field called title, that's the name in the form.  Field called content is named content.  For inlines, it's not that much more complicated.  You have the related name, so in this case, we had an item that is a foreign key to a module.  So the reverse foreign key is module underscore set.  The module underscore set dash and then an index for which inline it is in the list, and then the field name on that inline.  And this convention is followed everywhere.  For Django in the admin, for the arbitrarily deep nesting, we just sort of continue to do more prefixes so that it would be like module underscore set dash zero dash item underscore set dash one dash field name.  So here's like a really simple form submission of ridged of somebody saving a form with two inlines.  So you have an ID and name field.  You have total forms, initial forms and maximum forms.  Those are management form fields.  The management form are management form fields, are hidden fields that tell Django how many inlines were there originally and how many were added so that it knows which ones should be update operations and which ones should be insert operations.  And then you have, you know, here module set dash zero dash ID.  That's the ID of the first one, then the second one, module set one.  Doesn't have an ID because it's new, and there's its title.  So you can do a fairly straightforward conversion of the syntax of the Django input forms into an angular expression that would map with JSON object.  So obviously if there's no inlines, just map it to the property ID as ID and name is name.  But for an inline, so instead of module underscore set dash zero dash, you have module underscore set, which is now an array, and then array index zero and then dot ID because that thing would be an object.  The thing that Angular Expressions gets us is that we can compile this expression, pass it an empty JavaScript object, and it'll build all the intermediate steps.  So zero won't be an undefined error. Zero, it'll construct the array and then build an empty object and then assign the ID property with the value that you tell it to.  And then just by convention, I needed something to do with the management forms.  They couldn't go with the module set array because they're different properties and they apply to the inline as a whole rather than each individual row in the inline.  So by convention, I call them underscore underscore mgmt, but there's nothing special about that.  So with that initial form submission, the serialized data would look something like this.  You have your in-eating, your management form values, your module set array, and that contains objects that have the properties of those inlines.  So here's a brief code sample that sort of shows the process from start to finish of building the view model and finding the phone fields.  So we require the Angular Expressions, which actually I don't really do.  I suppose it's a global object because it requires you to have some work in Django at the end, but that's beside the point for Clare to actually do it this way.  And then obviously, I would pull the name off of, I would iterate over the inputs, pull the names off of them and use regular expressions to invert them.  But just for demonstration purposes, I used one particular field.  So you have module underscore set dash zero dash title, the Django version, the Angular version, and then the knockout version is identical to the Angular version except arrays need to have an open-close parentheses because they're technically callable in knockout.  So it's a weird quirk. It seems like something that they could do away with, but it is what it is.  So I grab the input field, compile the expression, and assign the data.  That, like I said, builds up the empty parts of the JSON object, so it'll build a module set object in the top level.  That'll be an array. It'll fill in the first one with a new object and set the title to foo.  After I do that, I add a data-bond attribute to the input.  Knockout functions just by the data-bond attribute.  Every way of binding behaviors or values to the model is done by the data-bond attribute.  So data-bond and then value is the bind handler for form field values.  Value colon, the knockout expression, build the view model.  I'm using a plugin called knockout mapping, which it allows you to pass and adjust on objects and build a view model without all these intermediate steps.  And then apply the bind name from the view model, which will find all the inputs with data-bond attributes and add the matchup to them.  The end result in the form would be like an input element with its name and then data-bond value colon and then the knockout expression.  And that would have two-way data binding.  So if somebody edited that form field, it would update the view model.  And vice versa, if we programmatically updated the view model, it would update the form field automatically.  So now we have two-way data binding. What do we do from here?  Well, we build a template that looks like a homepage.  Luckily for me, my co-worker Chris Barna is really skilled in CSS and Flexbox and is able to take all of the different designs for the modules and use the exact same markup for everything.  So every article has the same markup on the homepage.  Every module is just nested ULs and LIs, which makes building the knockout version of the template really simple.  So I use the for each bind, iterate over the modules and iterate over the items and then have the HTML for the articles.  And then that is sort of like a dynamic version now of what we were otherwise doing with gender templates.  Once we had that, we could add knockout behavior bindings.  So integrations with things like CK Editor for inline text editing, select two for dynamic dropdowns, sortables so that users can drag and drop to rearrange things,  and it's a plug for another open source project of ours, but DjangoCropDuster so they can upload images and crop them however they see fit.  So now I'm going to do a brief demo of the whole process.  So this is the demo version of the homepage.  I'm going to mess around with it a little bit, sort of show the features that I feel will be using this technique.  So you can delete things, undelete them.  We have something called the stash which you can drop things into for safekeeping later.  And then if you want to return them, just sort of drag them back in.  You can rearrange things by dragging and dropping.  Normally.  Here we go.  And you can click to just edit any of the text anywhere.  And these little labels, pull down, select two, drop down, click on the right icon.  Well, we'll just pretend that it happened.  There should be a named rule for whenever you give a demo, you don't uncover these one by one.  So I'm going to switch out the top module with a feature from the next magazine issue.  So click on it, choose what type of content, magazine article.  There we go.  Save.  And we hooked it up so that it'll pull the image field off of the article and then re-crop it automatically to fit the art direction of the homepage when it's imported.  So that can take a little bit time for him to process it.  White dogs still can't have it all written by me with a cover shoot of my dog.  And we'll go ahead and save.  And there's our homepage with the Hero Adventures.  Last thoughts and questions.  I think we can make really neat user experiences.  For instance, you could take this technique and combine it with Masonry, which is like a JavaScript library for arranging grids and maybe Django CMS and build like a really fully featured, flexible dashboard builder.  We got a lot of everybody. Let's thank Frankie for coming.  Thank you.
DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_0_155.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_156_287.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_288_407.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_408_4367.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_4368_5603.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_5604_10967.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_10968_11015.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_11016_11195.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_11196_12803.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_12804_15887.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_15888_15959.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_15960_16007.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_16008_21767.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_21768_21911.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_21912_22127.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_22128_22379.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_22380_23387.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_23388_23723.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_23724_24683.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_24684_24791.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_24792_25667.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_25668_26735.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_26736_26855.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_26856_27011.png DjangoCon_US_2015_-_Building_theatlantic_com_homepage_s_WYSIWYG_admin_withby_Frankie_Dintino_RWLQTCU_27012_27019.png
Music So first of all I'd like to thank DjangoCon for selecting a presentation. Thank you Atlantic for helping me send me here and thank all of you for coming to see my presentation. So today I'm going to be talking about building the in-line app.com homepages with the Wiggy Min with Django and KnockoutJS. For the purposes of demonstrating the techniques here, I created sort of bare bones repo that illustrates the techniques that you can check out and run at your leisure. So introductions. My name is Frankie Dantino as was mentioned. I work for the Atlantic. If you're not familiar with it, it's a 158 year old monthly publication. It's been a website for 20 years of those I've been working at for five. If you're not immediately familiar with the name, you may have seen one of our recent magazine features such as What ISIS Really Wants, The Case for Reparations, Why Women Still Can't Have It All, or The Christy the American Military. So in April of this year, we launched a redesign of the site, which had been in the works for a very long time. The front end was written in PHP, so the back end at this point had been migrated to Django. The site was not responsive and the homepage had a fixed layout. So you had a carousel module that rotated four items, then a row of five items, a list of three, and then a video and a grid, and that never changed. As a brief aside, almost everything you see on the Atlantic.com homepage, and this has been the case for quite a while, is hand selected or curated. There are two people whose job it is to select every sort of homepage. I ran the numbers and the main number of updates per day for the homepage is 33. Some of those are typo fixes and things like that, but it's still a fairly impressive number. On many days, the homepage is the most highly-tracked page on our site. We also feel that the homepage serves as a representative for the brand, but for the content that we think is the most compelling that we've written. Before the redesign, the admin interface looked like this. If you're familiar with Grapelli, it's basically Grapelli inlines, though using a module we built called Django-Invested Admin that allows you to drag and drop between inlines. And arbitrarily nest the inlines. Some people might characterize that as admin abuse, but we've got it to be extremely helpful. So with the new homepage, it was going to be fully responsive, rewritten in Django, and we were going for a flexible modular structure that would allow editors to format the group articles and build a rhythm as the user scrolls down the page. So here's a sort of mattering of modules that you might see on the homepage. And I've already used this term, but the operating concept of the homepage design is that there are rows of more, of one or more articles, which we call modules, that can be freely arranged in whatever order. With feedback from user testing, actually, we're adding even more modules. These are the ones that we launched with so that we can make the site more dense. We found this approach to be sufficiently flexible while still maintainable. So why rebuild the admin? Why would the old system of nested inlines not suffice for the new homepage? First of all, the new homepage is increasingly visual. They're really gorgeous, aren't selected for stories and, you know, a lot more variety in how things are arranged. So the cycle that they had been using previously where they would edit something, preview the homepage live, then go back and edit it and save again and go and preview would be really cumbersome for something that, particularly in a responsive situation, our editors are perfectionists. And if something doesn't wrap quite right at 450 pixel wide, they're going to go back and edit the copy. Also, I thought it would be a fun challenge. So the goal was to build a WYSIWYG interface to edit the homepage while we're using as much existing code as possible. So that existing code I mentioned briefly a moment ago, Django nested admin. You can find it on GitHub with the address here. As I said, it allows for arbitrarily deep nesting of inlines in the admin and provides a drive and drop functionality similar to Django propellities that can run with or without propelling. I rewrote Django nested admin for the purpose of this project to expose an API so that I could call methods that perform operations such as delete, insert, and splice programmatically rather than them working the way that they currently do in group L. Where it's based on event handlers on user input by clicking a button or tracking and dropping. So I can talk to the plan for how I might achieve this WYSIWYG interface. I'm going to dwell on this diagram for just a moment because while it's maybe a bit overwhelming, I think it really gets to the heart of the technique being discussed in this presentation. So you have Django nested admin, which let's say you visit the admin page to edit the homepage. Django nested admin renders all the inlines and form fields. What I wanted to do was take those form fields and build a big JSON object that's basically serializes the form data. Hook that into a knockout view model or Angular, though in this case it turned out to be knockout, and set up two-way data binding between the original form fields and the values that I serialized from there. Create a view that would be the template, and then that template would have buttons and drag and drop ability that the user could interact with, and that would update the view model which would in turn call Django nested admin APIs, and then the cycle would repeat. So this was how I'm going to dwell on this building. Now, I was going to talk about why I picked knockout over Angular, but I'm a little concerned for time. So if I have some extra time at the end and people are interested, I'm happy to talk about it, or if I don't, you know, run into me and ask me, but I'm just going to skip these slides for now. Anyway, decision use knockout spoiler alert, if you couldn't tell from the title at the top. But I wanted to use Angular's expression compiler, which is definitely weird. I had to recognize. Somebody had helped pull out that functionality from Angular and created an NPM module. I forked it because I wanted to add some newer Angular features to it, but I'll explain why in a moment. So how do we get from this top row where we go from form fields to the JSON object to the view model? A brief refresher, Django in the admin Django has a pretty simple convention for how it names form fields and inline fields. Form fields is just the name of the field. You have a field called title, that's the name in the form. Field called content is named content. For inlines, it's not that much more complicated. You have the related name, so in this case, we had an item that is a foreign key to a module. So the reverse foreign key is module underscore set. The module underscore set dash and then an index for which inline it is in the list, and then the field name on that inline. And this convention is followed everywhere. For Django in the admin, for the arbitrarily deep nesting, we just sort of continue to do more prefixes so that it would be like module underscore set dash zero dash item underscore set dash one dash field name. So here's like a really simple form submission of ridged of somebody saving a form with two inlines. So you have an ID and name field. You have total forms, initial forms and maximum forms. Those are management form fields. The management form are management form fields, are hidden fields that tell Django how many inlines were there originally and how many were added so that it knows which ones should be update operations and which ones should be insert operations. And then you have, you know, here module set dash zero dash ID. That's the ID of the first one, then the second one, module set one. Doesn't have an ID because it's new, and there's its title. So you can do a fairly straightforward conversion of the syntax of the Django input forms into an angular expression that would map with JSON object. So obviously if there's no inlines, just map it to the property ID as ID and name is name. But for an inline, so instead of module underscore set dash zero dash, you have module underscore set, which is now an array, and then array index zero and then dot ID because that thing would be an object. The thing that Angular Expressions gets us is that we can compile this expression, pass it an empty JavaScript object, and it'll build all the intermediate steps. So zero won't be an undefined error. Zero, it'll construct the array and then build an empty object and then assign the ID property with the value that you tell it to. And then just by convention, I needed something to do with the management forms. They couldn't go with the module set array because they're different properties and they apply to the inline as a whole rather than each individual row in the inline. So by convention, I call them underscore underscore mgmt, but there's nothing special about that. So with that initial form submission, the serialized data would look something like this. You have your in-eating, your management form values, your module set array, and that contains objects that have the properties of those inlines. So here's a brief code sample that sort of shows the process from start to finish of building the view model and finding the phone fields. So we require the Angular Expressions, which actually I don't really do. I suppose it's a global object because it requires you to have some work in Django at the end, but that's beside the point for Clare to actually do it this way. And then obviously, I would pull the name off of, I would iterate over the inputs, pull the names off of them and use regular expressions to invert them. But just for demonstration purposes, I used one particular field. So you have module underscore set dash zero dash title, the Django version, the Angular version, and then the knockout version is identical to the Angular version except arrays need to have an open-close parentheses because they're technically callable in knockout. So it's a weird quirk. It seems like something that they could do away with, but it is what it is. So I grab the input field, compile the expression, and assign the data. That, like I said, builds up the empty parts of the JSON object, so it'll build a module set object in the top level. That'll be an array. It'll fill in the first one with a new object and set the title to foo. After I do that, I add a data-bond attribute to the input. Knockout functions just by the data-bond attribute. Every way of binding behaviors or values to the model is done by the data-bond attribute. So data-bond and then value is the bind handler for form field values. Value colon, the knockout expression, build the view model. I'm using a plugin called knockout mapping, which it allows you to pass and adjust on objects and build a view model without all these intermediate steps. And then apply the bind name from the view model, which will find all the inputs with data-bond attributes and add the matchup to them. The end result in the form would be like an input element with its name and then data-bond value colon and then the knockout expression. And that would have two-way data binding. So if somebody edited that form field, it would update the view model. And vice versa, if we programmatically updated the view model, it would update the form field automatically. So now we have two-way data binding. What do we do from here? Well, we build a template that looks like a homepage. Luckily for me, my co-worker Chris Barna is really skilled in CSS and Flexbox and is able to take all of the different designs for the modules and use the exact same markup for everything. So every article has the same markup on the homepage. Every module is just nested ULs and LIs, which makes building the knockout version of the template really simple. So I use the for each bind, iterate over the modules and iterate over the items and then have the HTML for the articles. And then that is sort of like a dynamic version now of what we were otherwise doing with gender templates. Once we had that, we could add knockout behavior bindings. So integrations with things like CK Editor for inline text editing, select two for dynamic dropdowns, sortables so that users can drag and drop to rearrange things, and it's a plug for another open source project of ours, but DjangoCropDuster so they can upload images and crop them however they see fit. So now I'm going to do a brief demo of the whole process. So this is the demo version of the homepage. I'm going to mess around with it a little bit, sort of show the features that I feel will be using this technique. So you can delete things, undelete them. We have something called the stash which you can drop things into for safekeeping later. And then if you want to return them, just sort of drag them back in. You can rearrange things by dragging and dropping. Normally. Here we go. And you can click to just edit any of the text anywhere. And these little labels, pull down, select two, drop down, click on the right icon. Well, we'll just pretend that it happened. There should be a named rule for whenever you give a demo, you don't uncover these one by one. So I'm going to switch out the top module with a feature from the next magazine issue. So click on it, choose what type of content, magazine article. There we go. Save. And we hooked it up so that it'll pull the image field off of the article and then re-crop it automatically to fit the art direction of the homepage when it's imported. So that can take a little bit time for him to process it. White dogs still can't have it all written by me with a cover shoot of my dog. And we'll go ahead and save. And there's our homepage with the Hero Adventures. Last thoughts and questions. I think we can make really neat user experiences. For instance, you could take this technique and combine it with Masonry, which is like a JavaScript library for arranging grids and maybe Django CMS and build like a really fully featured, flexible dashboard builder. We got a lot of everybody. Let's thank Frankie for coming. Thank you.
have seen one of our recent magazine features The front end was written in PHP, so the back end at this point had been migrated to Django. The site was not responsive and the homepage had a and this has been the case for quite a while, is homepage is the most highly-tracked page on our written. Before the redesign, the admin interface might see on the homepage. And I've already used system of nested inlines not suffice for the new live, then go back and edit it and save again and go and preview would be really cumbersome for the goal was to build a WYSIWYG interface to edit mentioned briefly a moment ago, Django nested admin. You can find it on GitHub with the address drive and drop functionality similar to Django propelling. I rewrote Django nested admin for the rather than them working the way that they just a moment because while it's maybe a bit though in this case it turned out to be knockout, and set up two-way data binding between the features to it, but I'll explain why in a moment. from form fields to the JSON object to the view model? A brief refresher, Django in the admin hidden fields that tell Django how many inlines Django input forms into an angular expression something that they could do away with, but it is first one with a new object and set the title to to the input. Knockout functions just by the colon, the knockout expression, build the view expression. And that would have two-way data if we programmatically updated the view model, it we have two-way data binding. What do we do from everything. So every article has the same markup the for each bind, iterate over the modules and inline text editing, select two for dynamic to do a brief demo of the whole process. So this the features that I feel will be using this select two, drop down, click on the right icon. choose what type of content, magazine article. direction of the homepage when it's imported. So maybe Django CMS and build like a really fully