The Django code in this post comes from the official Django tutorial.

I've tried not to be disingenuous with the comparison, and I'm only going to compare like with like, so I can show code from both frameworks and let you draw your own conclusions. I'll cover the areas where they differ in another post.


Both Moya and Django use models to map databases on to familiar data structures. In the case of Moya, the mapping is done with SQLAlchemy. Django uses its own ORM.

Here's the from the Django tutorial and a Moya version:
import datetime

from django.db import models
from django.utils import timezone

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    def __str__(self):
        return self.question_text

    def was_published_recently(self):
        return self.pub_date >= - datetime.timedelta(days=1)

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

    def __str__(self):
        return self.choice_text

     <model libname="Question" repr="question_text" xmlns="">
        <string name="question_text" length="200"/>
        <datetime name="pub_date" label="date published"/>
        <property name="was_published_recently" expression="pub_date gte .now - 1d"/>

    <model libname="Choice" repr="choice_text" xmlns="">
        <foreign-key name="question" model="#Question" backref="choices" owned="yes"/>
        <string name="choice_text" length="200"/>
        <integer name="votes" default="0"/>


You may have noticed that there is nothing comparable to Python's import statements in the Moya code. No imports means there are no circular imports, and the lack of ambiguity in Moya tags means that there is no equivalence of needing to know if a file does "import datetime" or "from datetime import datetime" (for instance).

The naming and syntax may vary but the same information is present in both files. Where they differ is that the Python code has a __str__ method and the Moya code has a repr attribute (actually an expression) that do the same thing.

Another difference is the property was_published_recently which is calculated with the following expressions:

Django / Python
self.pub_date >= - datetime.timedelta(days=1)
Moya / XML
pub_date gte .now - 1d

The reason the Moya expression uses gte rater then >= is that greater than symbols must be escaped in XML, and &gt;= is rather ugly.


Next up is the urls file which maps URLs on to the code that will generate a response.

Same as before, Django first, then Moya.

from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
    url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$',, name='vote'),


        <url route="/" view="#view.index" name="index"/>
        <url route="/{posinteger:pk}/" view="#view.detail" name="detail"/>
        <url route="/{posinteger:pk}/results/" view="#view.results" name="results"/>
        <url route="/{posinteger:pk}/vote/" view="" name="vote"/>

Other than the lack of imports, the Moya code has a similar layout to the Django code. Worthy of note is that Moya favours a simpler (and arguably more readable) syntax for parsing URLs, over regular expressions.

In the Django code, the views are specified with by a callable expression, whereas the Moya code uses an attribute containing a string such as "#view.detail". This attribute is an element reference which tells Moya to look for the view with a libname attribute of view.detail (example views below).

An advantage of Moya's method of referencing views is that it is independent of path and module hierarchy; if you were move the view to another file, or directory, nothing would break. A potential boon for refactoring.


The views are the meat and veg of writing web applications. Most custom functionality will take place within a view. Here is the views file from the tutorial and a Moya version.

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic

from .models import Choice, Question

class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        return Question.objects.order_by('-pub_date')[:5]

class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'

class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        selected_choice.votes += 1
        return HttpResponseRedirect(reverse('polls:results', args=(,)))


<moya xmlns:db="" xmlns:let="">

    <view libname="view.index" template="polls/index.html">
        <db:query model="#Question" orderby="-pub_date" maxresults="5" dst="latest_question_list"/>

    <view libname="view.detail"  template="polls/detail.html"/>
        <db:get-required model="#Question" let:id="" dst="question"/>

    <view libname="view.results" template="polls/results.html">
        <db:get-required model="#Question" let:id="" dst="question"/>

    <view libname="" template="polls/detail.html">
        <db:get-required model="#Question" dst="question" let:id=".url.question_id"/>
        <db:get model="#Choice" src="question.choices" let:id=".request.POST.choice" dst="selected_choice">
            <inc src="selected_choice.votes"/>
            <redirect name="results" let:question_id=""/>
        <str dst="error_message">You didn't select a choice.</str>


Of the four views in the tutorial, three of them are Django's generic views. Moya doesn't really have an analogue of generic views, although for this tutorial the regular Moya views above are equivalent.

The last view is more interesting for the purposes of this post. It has essentially the same function as the Python code. Without turning this in to a Moya tutorial, the tags do the following:

  • <db:get-required> gets a database object, or raises a 404 if it isn't found
  • <db:get> Gets a database object and executes the enclosed block if it exists, otherwise it skips the enclosed block
  • <inc> Increments a value
  • <redirect> Redirects to a named url
  • <str> sets a string


It's also worth comparing templates from both frameworks. The following templates render a form:

detail.html (Django)

<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />

detail.html (Moya)


{% if error_message %}<p><strong>${error_message}</strong></p>{% endif %}

<form action="{% url 'vote' with %}" method="post">
{%- for counter, choice in enumerate1:question.choices %}
    <input type="radio" name="choice" id="choice${counter}" value="${}" />
    <label for="choice${counter}">${choice.choice_text}</label><br />
{%- endfor %}
<input type="submit" value="Vote" />

You would have to look closely to see the differences beyond the syntax details (Moya prefers ${} over {{}} for substitution). The Moya template lacks a{% csrf_token %} tag as Moya's cross site request forgery protection is handled by a forms library, which the tutorial code doesn't have a direct comparison for. If we were using the forms library, and we wanted to render the form manually, this is how we could add the CSRF token:

<input type="hidden" name="_moya_csrf" value="${form.csrf_token}"/>

Another difference is that the Moya version creates a counter value explicitly with the expression enumerate1:question.choices, as there is no implicit forloop value.

Finally, the url tag is different in Moya; it only accepts keyword arguments, whereas Django's url tag accepts both positional and keyword arguments. There is also no need to specify the application in Moya, as it is assumed to be from the same application that is rendering the template.

I would argue that it is better to use keyword arguments in url tags, even with Django, as its more self-documenting and less likely to break if you re-arrange your url patterns.

Generally though, the template systems have a similar look and feel. Moya is around 30% faster -- not that template speed is much of an issue these days, and Jinja is faster than both Moya and Django templates. Where I do think Moya wins is that it has full expressions, and the error reporting is more advanced.


For the apples-to-apples code, Moya is comparable to Django and similar web frameworks in terms of lines of code. Not that number of keypresses is a good metric for any technology comparison, but the Moya doesn't suffer from the bloat often associated with XML.

Where Moya does save on typing is that a tag is a self contained little operation, independent of its context in the file. Take for instance, a redirect. The following lines from the above code are all involved in a redirect:

from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
return HttpResponseRedirect(reverse('polls:results', args=(,)))

There's nothing complex in these lines, but there is a tiny mental burden that can slow you down. By mental burden, I mean things like struggling to remember the import for 'reverse' or how HttpResponseRedirect is capitalized (should it not be HTTPResponseRedirect)? There are also a few things to go wrong; you may forget the comma to make the args parameter a tuple or not notice you're a brace short at the end. Such mistakes are easily noticed and fixed, but I find these things are 'a death of a thousand cuts' for development speed.

In Moya, the redirect is done as follows:

<redirect name="results" let:question_id=""/>

The complexity is hidden behind a relatively memorable tag interface. There are many such tags that cover a broad spectrum of web development tasks.

Non Apples

There are some features of Moya that aren't directly comparable with other Python frameworks. Such as content which describes a page with high level components. These components (or widgets) are code and template wrapped in a simple tag interface.

Lets look at a quick example of content. The following renders 'post.html' for each item in a query set of posts:

<for src="posts" dst="post">
    <node template="post.html" let:post="post"/>

Here's how we would modify the above to paginate the posts in to a maximum of 10 items per page:

<w:paginate src="posts" dst="post" auto="yes">
   <node template="post.html" let:post="post"/>

This single change enables the pagination used here blog (including the HTML). Such widgets are re-usable across projects and trivial to customize.


If you would like to have a look at more Moya code, a good place to start would be Moya Techblog, which powers this blog. In particular, this directory, which does the heavy lifting.

Use Markdown for formatting
*Italic* **Bold** `inline code` Links to [Google]( > This is a quote > ```python import this ```
your comment will be previewed here
Chris Warrick

Did you really just say XML is prettier and better than Python?

Because that’s complete nonsense. Especially in the Python world.

Will McGugan

No, Chris, I did not say that. Moya is after all a Python application.

Chris Warrick

XML is not favored by the Python community though. It’s often associated with Java enterprisey types and not the Python world.

That said, doesn’t this XML structure heavily limit applications? For example, some of my own Django code does “internal” routing like if request.POST['action'] == 'edit': return render('edit_template.html'). That’s useful for pretty URLs, for example — how would I do that in Moya?

(Also, there’s nothing wrong with promoting your own products over famous competitors. I do that myself.)

Will McGugan

It's not XML I'm comparing though. XML just happens to be the medium that Moya uses, just as Python use text files.

XML may not be used much for applications, that's true, but XML has been in the standard library since 2.0.

Your example would be something like this in Moya:

<serve-template template="edit_template.html" if=".request.POST.action=='edit'"/>
Jeff Langemeier

Between your post and Moya's website I really get the impression that Moya is to Python what the combination of handlebars & lodash are for javascript. Python is there more for extension than everyday use cases, which I think is more of the issue; since their and your examples are extremely XML laden.

Yes, I understand that the parsing is probably done with Python and the pretty presentation layer is XML, getting developers on board with the idea that they have the same fine tuned control with the XML as they would in a full Python dev stack like Django, or Flask with SQL Alchemy is a tough sell.

Will McGugan

I think that is a reasonable analogy, and you're right that it is a tough sell.

I've found that I rarely need to go to the Python level (there isn't any Python in this blog app for example).

That said, I really need to document how to extend Moya with Python. That might reassure those who get the impression its all automation without the control.

Asif Saifuddin Auvi

xml templating languages sucks in python world. better use jinja2 or flask or pyramid. even better django

Will McGugan

Moya doesn't use an XML templating language, Asif.


Why not? If you had an xml templating code, you code reimplement the whole thing in xquery, serve it out of an xml database and not need any python code at all.

Misha Behersky

Oh, that's the worst comparison ever. Go straight to java world with your xml. I do agree with all statements that xml sucks and it should not pretend to be the part any serious Python application

Matthew Schunckel

Django used to support dotted path-to-view functions in url patterns. It was removed because of a security risk.

Terrence Brannon

One aspect of Django not mentioned in this comparison is the automatic admin interface, which I find to be very powerful. Web2Py has one also.

Does moya recommend a 3rd party tool for such automatic admin? Or perhaps someone has developed something on top of SQLAlchemy that can be used regardless of what webapp framework you are using?

Will McGugan

Moya has a fairly fully-featured admin site. Mounted on /admin by default.