My colleague Gijs Nijholt just posted his blog entry lessons learned from building a larger app with React.js, which is about the javascript/reactjs side of a django website we both (plus another colleague) recently worked on.
Simplified a bit, the origin is a big pile of measurement data, imported from csv and xml files. Just a huge list of measurements, each with a pointer to a location, parameter, unit, named area, and so on. A relatively simple data model.
The core purpose of the site is threefold:
- Import, store and export the data. Csv/xml, basically.
- Select a subset of the data.
- Show the subset in a table, on a map or visualized as graphs.
The whole import, store, export is where Django shines. The model layer with its friendly and powerful ORM works fine for this kind of relational data. With a bit of work, the admin can be configured so that you can view and edit the data.
Mostly "view" as the data is generally imported automatically. Which means you discover possible errors like "why isn't this data shown" and "why is it shown in the wrong location". With the right search configuration and filters, you can drill down to the offending data and check what's wrong.
Import/export works well with custom django management commands, admin actions and celery tasks.
Now on to the front-end. With the basis being "select a subset" and then "view the subset", I advocated a simple interface with a sidebar. All selection would happen in the sidebar, the main content area would be for viewing. And perhaps some view-customization like sorting/filtering in a table column or adjusting graph colors. This is the mockup I made of the table screen:
In the sidebar you can select a period, locations/location groups and parameters. The main area is for one big table. (Or a map or a graph component).
To quickly get a first working demo, I initially threw together three django views, each with a template that extended one base template. Storing the state (=your selection) as a dict in the django session on the server side. A bit of bootstrap css and you've got a reasonable layout. Enough, as Gijs said in his blog entry, to sell the prototype to the customer and get the functional design nailed down.
Expanding the system. The table? That means javascript. And in the end, reactjs was handy to manage the table, the sorting, the data loading and so on. And suddenly state started spreading. Who manages the state? The front-end or the back-end? If it is half-half, how do you coordinate it?
Within a week, we switched the way the site worked. The state is now all on the client side. Reactjs handles the pages and the state and the table and the graph and the map. Everything on one side (whether client-side or server-side) is handiest.
Here's the current table page for comparison with the mockup shown above:
Cooperation is simple this way. The front-end is self-contained and simply talks to a (django-rest-framework) REST django backend. State is on the client (local storage) and the relevant parameters (=the selection) are passed to the server.
Django rest framework's class based views came in handy. Almost all requests, whether for the map, the table or the graph, are basically a filter on the same data, only rendered/serialized in a different way. So we made one base view that grabs all the GET/POST parameters and uses them for a big django query. All the methods of the subclassing views can then use those query results.
A big hurray for class based views that make it easy to put functionality like this in just one place. Less errors that way.
Some extra comments/tips:
Even with a javascript front-end, it is still handy to generate the homepage with a Django template. That way, you can generate the URLs to the various API calls as data attributes on a specific element. This prevents hard-coding in the javascript code:
<body> <!-- React app renders itself into this div --> <div id="efcis-app"></div> <script> // An object filled with dynamic urls from Django (for XHR data retrieval in React app) var config = {}; config.locationsUrl = '{% url 'efcis-locaties-list' %}'; config.meetnetTreeUrl = '{% url 'efcis-meetnet-tree' %}'; config.parameterGroupTreeUrl = '{% url 'efcis-parametergroep-tree' %}'; config.mapUrl = '{% url 'efcis-map' %}'; window.config = config; </script> ... </body>
Likewise, with django staticfiles and staticfiles' ManifestStaticFilesStorage, you get guaranteed unique filenames so that you can cache your static files forever for great performance.
Lessons learned?
Splitting up the work is easy and enjoyable when there's a REST back-end and a javascript front-end and if the state is firmly handled by the front-end. Responsibility is clearly divided that way and adding new functionality is often a matter of looking where to implement it. Something that's hard in javascript is sometimes just a few lines of code in python (where you have numpy to do the calculation for you).
Similarly, the user interface can boil down complex issues to just a single extra parameter send to the REST API, making life easier for the python side of things.
When you split state, things get hard. And in practice that, in my experience, means the javascript front-end wins. It takes over the application and the django website is "reduced" to an ORM + admin + REST framework.
This isn't intended as a positive/negative value statement, just as an observation. Though a javascript framework like reactjs can be used to just manage individual page elements, often after a while everything simply works better if the framework manages everything, including most/all of the state.