Skip to main content

A different way to think about making websites in python

I'm not old (yet) and I've been using django for well over 5 years now and I must say it's a fantastic platform.  However I haven't seen much change since I started using it. This might be a good thing, since django doesn't need much to changed, however, maybe it's just me but django is starting to feel "dated".  Templates, in my opinion, are the "old" way of doing things vs web components.

Current method

The whole web development experience in python seems dated. Twisted, torando, django, flask, bottle, you name it... they all use the same general way for building pages using some formatting type template engine.



A request comes it, it's dispatched to some handler. The handler generates a "context" or set of variables to pass to a template engine, the template engine renders it and the handler writes it back to the client. It's a boatload of code (for django at least) and really difficult to follow and modify. More time is spent writing handlers and form processing than the actual pages.  It get's more complicated when you have to post a form. This is fine, it works, but is this the best way to do it?

Breakdown


Let's think about this a little. Say I have a simple page with a form like this.


A typical way to represent this is using a django model for a City. This makes perfect sense and is very pythonic.

However when it comes to rendering, we need to write a base template, a form template, a template for each field, and a handler that creates the context for each template, processes the forms, etc..  (or use a library that does this for us).

Templates are not really pythonic at all, they're more like a  giant format string with some alteration of "{% expr %}" blocks all over the place. The mapping from context to template isn't all that obvious for complex examples that are needed to build a page as shown above.  Try to go into a django project and figure out where the "is capital" field above is rendered? It takes forever.

Also each action on the page ex "Delete" and "Save" is mapped to a different url that when clicked processes a different handler that performs the desired action.

So for this page you have to write three handlers with a url for each, probably at least 8 templates (one for each field type (5), one for the form, one base template for the page, and one for the menu).

Why can't this be more like python?

 Different approach


After using enaml to build desktop applications and liking the experience so much I wanted to build websites in the same declarative way. All of the stuff above suddenly seemed extremely excessive.

Html is just a declaration of how the page should be rendered. Why do we need so many levels of stuff on top of html to get a working non-static website? Why can't I just define the html elements directly in python? Instead of passing context dicts all over the place why not just set the state of an element within a tree?

enaml happens to be the perfect tool for this. So I created enaml-web, an lxml based toolkit for enaml to build web pages using existing server platforms.  Instead of writing templates in your favorite template engine of choice, you do it declaratively using enaml.  It's like frontend web components, just in python, and on the backend.

I'm working on an asyncronous ecommerce site using enaml-web, twisted, and MongoDB. Here's the admin page.


from web.core.api import Conditional, Looper, Block
from web.components.api import *
from merch.views.blocks import Pagination, Card
from merch.views.forms import AutoForm
from merch.views.admin.base import Page
enamldef FlatButton(Button):
    type = 'submit'
    cls = 'btn-flat teal-text right'
enamldef View(Page): root:
    attr model << load_model(request)
    attr mode << 'Add' if request.path.decode().endswith('add/') else 'Edit'
    attr object << model() if mode=='Add' else model.objects.fetch({'id':request.path.decode().split("/")[3]})
    Block:
        block = root.content
        Form:
            method = 'post'
            Card: card:
                title.text << '{} {}'.format(mode, model.__name__)
                Block:
                    block = card.content
                    AutoForm: form:
                        tag = 'div'
                        method = "post"
                        model << root.object
                Block:
                    block = card.actions
                    Div:
                        cls = 'row'                        A:
                            href << "../" if mode=="Add" else "../../"                            text = "Cancel"                            cls = 'btn-flat teal-text'                        FlatButton:
                            text = "Save and continue editing"                            clicked::
                                print("Save and continue clicked")
                                object.save()
                        FlatButton:
                            text = "Save and add another"                            clicked::
                                print("Save and add clicked")
                                object.save()
                                root.object = root.model()
                        FlatButton:
                            text = "Save"
                            clicked::
                                print("Save clicked")
                                object.save()
                                request.redirect("../")

And here's what it looks like.


It's easier to read. You just import whatever component you need and set the attributes to populate it (or define your own components). You can see exactly what attributes are being set and its in one file and they respond to changes in the model (such as when the form is submitted with updates).

The checkout process is almost done, no validation is done yet, and I didn't implement submit (so you could see the nice error pages twisted creates) but it's not bad for a few evenings of work.




All of the pricing information here is updated in the models in maybe 10 lines of code (also very simple).  The models are defined using the atom framework which stores it's properties in c thus lowering the memory footprint. You can test without the database if you like and there's no need for migrations (with MongoDB anyways).

The interesting part is this entire website is all done with one handler class.  It's about 150 lines and updates the view elements in place since Enaml handles keeping the page and models in sync when one or the other changes.

Also, instead of having a separate url and handler for each action it simply fires the clicked handler of the Button that was clicked within the view which makes sense logically and keeps the code easy to read.  You don't have to read through handler code, it's right where the button is visually. To top it off, it works with  standard HTTP posts or with websockets.

It's also asynchronous (with a modification to enaml) and will easily scale up to as may processes as you need.  I'm working on a way to seamlessly serialize and deserialize the models into and out of MongoDB so once that's done it'll be ready to ship.

I had worked on a similar website in django a few years ago and after several months gave up because it was just too complicated to get the layout and models to work without having to rewrite everything.

The code will be released once I get a few more pieces done, but I'm very happy so far. Maybe it's just me, but this is way nicer than other methods I've tried.

Hope you'll have a look!

Cheers!

Comments

  1. This comment has been removed by a blog administrator.

    ReplyDelete

Post a Comment

Popular posts from this blog

Kivy vs React-Native for building cross platform mobile apps

I've built three apps now using Kivy and one with React-Native, just wanted to share my thoughts on both. Just a warning, I am strongly biased towards python and this is all based on opinion and experience and is thus worth what you pay for it. I don't claim to be an expert in either of these, just have worked with each for several months.  If something is incorrect I'd love to hear advice. Kivy Demo of one of the apps Pros: Nice to be able to run natively on the desktop WITHOUT a simulator Python is easy to work with Use (almost) any python library Very easy to create custom widgets Kivy properties and data binding just work. Way nicer than React's "state" / flux / redux whatever you want to call it (stupid?).  Native interfaces (pyjnius) and (pyobjc) Runs and feels pretty smooth Cons: Default widget toolkit looks like Android 4.4. Requiring you use your own widgets or a theming kit like KivyMD  if styling bothers you Creating dy

Control Systems in Python - Part 1 - Bode and Step Response

I hate matlab with passion, yet sadly, nearly everyone uses it.  I'm a fan of Python and open source stuff so here's a simple article on how to do some common control systems stuff in Python. First we need to make sure the environment is setup. Install IPython (or you can use any other python shell, but a unicode supported shell is preferred) Install python-control (numpy, scipy) Install sympy These should do if your on Ubuntu/debian: sudo apt - get install python - sympy python-numpy python-scipy python-matplotlib ipython Then you need to install python control, see How to download and install python-control Intro to using Sympy Open ipython and run the following: import sympy from sympy import * sympy.init_printing() s = Symbol('s') Now we can do things like define transfer functions using the symbolic variable s. We can expand the bottom using the .simplify() method and we can do something more complex like... which is really nice because it

Control Systems in Python - Part 2 - Routh Hurwitz

In my last post Control Systems in Python Part 1 , i described how to setup and use Python for doing some basic plotting of transfer functions. One of the biggest benefits of using sympy vs numeric packages like matlab/numpy/scipy is the fact that you can use symbolic variables. This post includes a function for computing the Routh Hurwitz table (Note: It does not work for row's of zeros). Lets do my control systems design homework problem together :) (Warning: I have not verified if this answer is right so please correct me if it’s not!) The Problem The problem is DP 9.11 from Dorf & Bishop’s Modern Control Systems. ISBN 0136024580. Basically we have to design a controller to compensate for a system with a time delay. The controller is: And the system is: First we approximate the exponential term with a 2nd order polynomial using pade(0.4,2) such that: Thus the approximated system is: Using frequency response methods, design the controller so that th