 I'd like all of you for coming to see my presentation. We're talking about building the Atlantic.com homepages, with Django and Knockout.js. 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 Dentino, 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 tragedy of 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, though 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 with 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. Granted, you know, 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 trafficked page on our site. And we also feel that the homepage serves as a representative for the brand and for the content that we think is the most compelling that we've written. So before the redesign, the admin interface looked like this. If you're familiar with Propelli, it's basically Propelli inlines, though, using a module we built called Django nested admin that allows you to drag and drop between inlines and arbitrarily nest the inlines. Some people might characterize that as admin abuse. It's kind of 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 dramatically 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, and the operating concept of the homepage design is that there are rows 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. Art selected for stories and a lot more variety in how things are arranged. So the cycle that they have been using previously where they would edit something, preview the homepage live, then go back and edit it and save again, then go and preview would be really cumbersome for something that particularly in a responsive situation, our editors are affectionate 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 propellies that can run with or without propellies. 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 working the way that they currently do in group L where it's based on event handlers on user input like clicking a button or dragging and dropping. So I conducted a 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 home page. 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 serialized as 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 their creative 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'd recognize. Somebody had helpfully pulled 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, it's just the name of the field. For example, you have a field called title, that's the name in the form. Field called content, it's 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. You have 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 nested 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 a really simple form submission abridged of somebody saving a form with two inlines. So you have an ID, a name field. You have total forms, initial forms, and maximum forms. Those are management form fields. The 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 here module set dash zero dash ID. That's the idea 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 to a 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 AngularExpressions gets us is that we can compile this expression, pass in 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 applied 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 the right evening, 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 form fields. So we require the angular expressions, which actually I don't really do. I suppose it's a global object because it requires that it doesn't work in the changle, but that's beside the point for clarity. I just take it this way. And then obviously I would pull the name off, I would iterate over the inputs, pull the names off of them and then I would 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-bind attribute to the input. Knockout functions just by the data-bind attribute. Every way of binding behaviors or values to the model is done by the data-bind attribute. So data-bind 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 an JSON object and build a view model without all these intermediate steps. And then apply the bind name for the view model, which will find all the inputs with data-bind attributes and add the magic to them. The end result in the form would be, like an input element with its name and then data-bind 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 the homepage. Luckily for me, my co-worker Chris Barna is really skilled with CSS and Flexbox and was 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 general 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. Sortable so that users can drag and drop to rearrange things. And it's a plug for another open-source project of ours, but DjangoCropDuster is like an 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 a demo version of the homepage. I'm going to mess around with it a little bit, sort of show the features that I've been able to build 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 then these little labels, pull down, select two dropdowns. If I 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 want to cover these one bug. 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. Here 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 invention. Best thoughts and questions. So the goal of this talk was not just to show off something neat that we did, although of course that was part of it, but to illustrate a specific technique for hooking together libraries to 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's CMS and build like a really fully featured, flexible dashboard builder. We got a lot of everybody.