High-Availability Django by Frankie Dintino
Published August 24, 2016
This video features Frankie Dintino at DjangoCon US 2015 in Austin, Texas, USA.
Building theatlantic.com homepage’s WYSIWYG admin with Django and Knockout by Frankie Dintino
While the front-end of theatlantic.com 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.
Help us caption & translate this video!
Frankie Dintino explains how The Atlantic replaced a rigid, nested-inline homepage editor with a responsive WYSIWYG admin built on Django, Django Nested Admin, Knockout.js, and Angular’s expression compiler. Django form field names are converted into a nested JSON view model, with two-way data binding keeping the admin form and a homepage-like preview synchronized. Knockout bindings then provide inline editing, drag-and-drop sorting, deletion and recovery through a stash, dynamic selects, image cropping, and other interactions while continuing to use Django’s existing form and save mechanisms. The same pattern can connect Django back ends to JavaScript layout and CMS tools to build flexible editorial interfaces.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
So first of all, I'd like to thank DjangoCon for selecting my presentation. Thank The Atlantic for helping to 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 life. com homepages, WYSIWYGAMIN 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 uh 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 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 story on the homepage. Um I ran the numbers and the 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 a representative for the brand to put forward 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 uh 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, but we found 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 thematically group articles and build a rhythm as the user scrolls down the page. Um so here's a sort of s mannering of 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 called 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 rebuilt 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 um you know, a lot more variety in how things are arranged. Uh so the cycle that they had been using previously where they would edit something, preview the homepage live,
uh then go back and edit it and save again and then go and preview would be really cumbersome uh 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 drag-and-drop functionality similar to Django.
Dango propellis that can run with or without propelling. Uh I rewrote Django nested admin for the purpose of this project uh to expose an API uh 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 Grapelli where it's based on event handlers on user input like clicking a button or dragging and dropping. So I contacted a plan for how I might achieve this WYSIWYG interface. I'm gonna 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. DjangoNet 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 serializer for 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 From there, uh 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 you know update the view model which would in turn called Django nested admin APIs, and then the cycle would repeat. So this was how I'm going to
dwell on this building in. Now I was gonna 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 um I'm just gonna skip these slides for now. Uh anyway, decision use knockout. Um spoiler alert. if uh you couldn't tell from the title of the talk. Um but I wanted to use Angular's expression compiler, which is definitely weird. I 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 is just the name of the field. Dead simple. You have a field called title, that's the name and 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, you know, we had a
an item that is a foreign key to a module. So the reverse foreign key is module underscore set. We have module underscore set dash and then an index uh 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 then 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 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 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 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 objects.
So obviously if there's no inlines, just map it to the property name. ID is 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, uh, and then array index zero, and then dot ID because that thing would be an object. Um the thing that Angular expressions 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 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. your ID name, your management form values, your module set array, and that contains objects that have the properties of those end lines. 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 point through the So we require the Angular expressions, which actually I don't really do.
I suppose it's a global object because Repar GX doesn't work in the Django Lab. That's beside the point for Clarity H to 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 convert them. But just for demonstration purposes I used one particular field. So you have module underscore the- zero-title, the Django version, uh the Angular version, and then the knockout version is identical to the Angular version except arrays need to have an open closed parentheses. because they're technically callable in knockout. So it it's a weird quirk. Seems like something that they could do away with, but it 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. Knock out functions just by the data bind attribute. Every way of finding 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 in a JSON object 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 bind attributes and add the magic to them. The end result on 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 coworker Chris Barna is really skilled with CSS and Flexbox and is able to take all 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 uh 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, then iterate over the items, and then have the HTML for the R And then that is sort of like a dynamic version now of what we were otherwise doing with Django templates. Once we had that, we could add knockout behavior bindings. Uh so integrations with things like CK editor for inline text editing.
Select two for dynamic drop-downs, sortable so that users can drag and drop to rearrange things. And it's a plug for another open source project of ours, but Django Crop Duster, so they can upload images and crop them however they see fit. So now I'm going to do a brief demo of uh the whole process So this is a demo version of the home page. I'm gonna mess around with it a little bit, sort of show the features that I was 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 who drop down. If I click on that Well, we'll just pretend that it happened. Uh there should be like a named rule for like whenever you give a demo, you'll uncover these ones. So I'm going to switch out the top module with
feature from the next magazine issue. So click on it. choose what type of content magazine article there we go save and uh we hooked it up so that um it'll pull the image field off of the article and then recrop it automatically to fit the art direction of the home page when it's imported. So that can take a little bit of time for import processing. White Dog 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 um hero image. Find my mouth. Last 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 the specific technique for hooking together libraries can make really neat user experience. 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.
The old nested-inline admin was cumbersome for an increasingly visual, responsive homepage with many layout variations. Editors needed to see and refine the homepage directly rather than repeatedly saving, previewing, and returning to the form.
Discussed at 3:22Serialize Django admin form fields into a JSON-like view model, bind that model bidirectionally to the original form fields, and render a homepage-shaped Knockout template whose controls call Django Nested Admin APIs for operations such as inserting, deleting, and rearranging content.
Discussed at 4:10Django’s inline names encode the related set, row index, and field name; those prefixes can be translated into JavaScript object and array paths. Angular-style expressions create the intermediate arrays and objects, while management-form fields are stored separately for each inline collection.
Discussed at 8:45Add Knockout `data-bind` attributes using the converted expressions, build the model with the Knockout mapping plugin, and apply bindings. Editing a form field then updates the view model, and programmatic model changes update the field automatically.
Discussed at 11:55The editor can render modules and articles dynamically and add inline editing, dynamic Select2 dropdowns, drag-and-drop sorting, image uploading and cropping, deletion and undeletion, and a stash for temporarily holding content.
Discussed at 13:28Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 14, 2026