This tutorial will cover the building of a fully functional website with Moya.
You can work through the tutorial without reading the rest of the documentation. Much of the detail will be glossed over for the sake of brevity, but will contain links to the Reference documentation if you want the details.
The website we are going to build is a microblogging service, not unlike Twitter. We're not going to attempt to replicate everything Twitter does (the Twitter developers have nothing to fear), just a single-user timeline.
Before we start, you will need to first install Moya if you haven't already. And as a bare minimum you will need to be familiar with using the command line, and have a text editor for editing code. It will also help if you are familiar with basic HTML.
The site we are going to build will be called Blether, which is a Scottish word, meaning to chat. We're aiming for a single timeline of 140 character messages – or blethers.
We're going to use the Moya command line application to create a project framework for us. This will give us a starting point for our project.
Run the following command from the command line to create a basic project:
$ moya start project --title Blether -a
The --title
option sets the title of the project. The -a
switch tells Moya to accept the defaults where possible. When you run this command, Moya will ask you for a few details it needs to build the project. When it finishes, you should see confirmation that the project files were written successfully.
The default project build by moya start
contains a simple content management system, comments, a blog, a contact page, a feedback form and an admin site.
Before we can have a look at our site, we must first create the database, which will store the site's dynamic data (users, posts, comments etc). Navigate to the blether
directory and run the following command:
$ moya db sync
You should see confirmation that the database has been synced (tables created). The site will actually be usable at this point, but we should first run the following command to create an initial user and set up some related objects in the database. Enter the following at the command line:
$ moya auth#cmd.init
This runs an interactive command from the Moya Auth library, it will ask you for the name of the superuser account. You may leave this as the default of superuser
, or enter a username of your own. Moya will also ask you for a password. Since the site is not accessible on the internet just yet, feel free to enter a memorable password here, such as 'password'. Moya will also ask you for an email address – you may leave this blank if you wish; if the site were live it is this address that would be used to send you mail.
You may now serve the site with the following command:
$ moya runserver
You should see a few pages of log messages followed by a message that lets you know that the development server is running. You can now open a browser on the same machine and navigate to http://127.0.0.1:8000.
The first page you will see on your site is a confirmation page that Moya is serving content. You will see a line in the console for every file the browser requests. There will be an entry for the page you are viewing, and other files it references, such as images, JavaScript and CSS.
Feel free to explore the default site. You can log in with your superuser account by clicking on the Log in link at the top right of the page. Be sure to have a look at the Admin site (/admin/
) which will let you manage the contents of your database.
When you run the server, Moya runs Moya Code, which is executable code stored in XML. The first such file Moya runs is server.xml
which tells Moya which libraries the project will use. You will find a server.xml
for your project in the logic
directory. It should look something like the following:
<moya xmlns="http://moyaproject.com"><!-- Initialize a server --><server docname="main"><log>Starting up!</log><!-- Import libraries for use in your project --><import py="moya.libs.debug" if=".debug"/><import py="moya.libs.auth"/><import py="moya.libs.session"/><import py="moya.libs.signup"/><import py="moya.libs.admin"/><import py="moya.libs.static"/><import py="moya.libs.favicon"/><import py="moya.libs.welcome"/><import py="moya.libs.links"/><import py="moya.libs.bootstrap"/><import py="moya.libs.forms"/><import py="moya.libs.widgets"/><import py="moya.libs.comments"/><import py="moya.libs.pages"/><import py="moya.libs.blog"/><import py="moya.libs.feedback"/><import py="moya.libs.jsonrpc"/><import py="moya.libs.wysihtml5"/><import py="moya.libs.recaptcha"/><!-- The 'site' library, for non reusable content --><import location="./site" priority="10"/><install name="site" lib="site.blether" mount="/"/><!-- Install applications (instances of a library) --><install name="forms" lib="moya.forms"/><install name="widgets" lib="moya.widgets"/><install name="auth" lib="moya.auth" mount="/auth/"/><mount app="auth" mountpoint="middleware" url="/"/><install name="session" lib="moya.session" mount="/"/><install name="signup" lib="moya.signup" mount="/signup/"/><install name="admin" lib="moya.admin" mount="/admin/"/><install name="media" lib="moya.static" mount="/static/"/><install name="debug" lib="moya.debug" mount="/debug/" if=".debug"/><install name="bootstrap" lib="moya.twitter.bootstrap"/><install name="welcome" lib="moya.welcome" mount="/"/><install name="links" lib="moya.links"/><install name="favicon" lib="moya.favicon" mount="/"/><install name="comments" lib="moya.comments" mount="/comments/"/><install name="pages" lib="moya.pages" mount="/"/><install name="blog" lib="moya.blog" mount="/blog/"/><install name="feedback" lib="moya.feedback" mount="/feedback/"/><install name="jsonrpc" lib="moya.jsonrpc"/><install name="wysihtml5" lib="moya.wysihtml5"/><install name="recaptcha" lib="moya.google.recaptcha"/></server></moya>
The server.xml
above was created when we ran moya start project
. Since we accepted all the defaults, there is a good amount of libraries already set up. You may add and remove features by editing this file, but for the purposes of this tutorial we will stick with the auto-generated code.
All functionality in Moya is contained within a library, which is a bundle of files (code, templates, CSS, Javascript, images etc.) responsible for handling URLs, or implementing features. Let's create a library for our Blether project. In the project directory, enter the following command:
$ moya start library --title Blether --mount / -a
The --title
option tells Moya the title of the library, --mount /
tells Moya to mount it on the root URL, and -a
accepts all defaults. When you run this command, Moya creates the files and directories for a working library and modifies server.xml
.
If you open up server.xml
, you should see that the start command has added two lines inside the <server> tag :
<!--Added by 'moya start library'--><import location="./local/moyaproject.blether"/><install name="blether" lib="moyaproject.blether" mount="/"/>
These two lines ensure that the library is loaded and can handle URLs.
If you run the server now, you will find that Moya is not yet serving our Blether library on /
. This is because the default project mounts a library which shows a simple page of welcome text on /
. Comment out (or remove) the two lines in your server.xml
that referencing the welcome library.
Now if you navigate to http://127.0.0.1:8000/ you should find a simple example page to demonstrate the new library is working. Next, we will replace this with microblogging service.
The start library
command has written the library files to a directory called local
(in the project directory). This directory will contain the libraries you have authored yourself. There's also a external
directory which will contain third party libraries.
The name of the library will be in the form of <your organization>.blether
where <your organization>
is replaced with your answer to the organization question in the start command. The files for the Blether library will be contained in a directory of the same name. For example in my project the new library would be in local/moyaproject.blether
– in your project the moyaproject
would be different.
If you navigate to the Blether library, you should see it contains the following files:
.|-- __init__.py|-- lib.ini|-- locale| `-- readme.txt|-- logic| |-- content.xml| |-- forms.xml| |-- models.xml| |-- mountpoints.xml| |-- readme.txt| |-- signals.xml| |-- tags.xml| |-- views.xml| `-- widgets.xml|-- media| `-- readme.txt`-- templates`-- moyaproject.blether|-- base.html`-- widgets`-- readme.txt
The directory structure is explained in detail in Creating a Library, but for now we are going to concentrate on the XML files in the logic
sub-directory – which contains executable code.
The start library command has created a number of XML files for us, such as models.xml, content.xml, widgets.xml etc. The filenames are merely a suggestion for how to organize a typical project. You are free to name your code in any way.
For this tutorial we will be putting everything in a single file, so go ahead and delete all the .xml files in the logic
directory. Then create a file called blether.xml
which should contain the following (feel free to cut and paste):
<moya xmlns="http://moyaproject.com"xmlns:let="http://moyaproject.com/let"xmlns:forms="http://moyaproject.com/forms"xmlns:html="http://moyaproject.com/html"xmlns:db="http://moyaproject.com/db"><!-- Code goes here --></moya>
This code is entirely boilerplate; the <moya> tag begins the Moya code file, and the XML namespace declations tell Moya which tags we will be using. We will be adding code to this file in the rest of this tutorial.
The first thing we want to do is to create a mountpoint. This is how Moya maps an incoming URL to the the view that will generate a response.
Open blether.xml
and insert the following inside the <moya> tag.
<mountpoint name="main"><url route="/" view="#view.front" /></mountpoint><view libname="view.front"><return-str>Ready to Blether!</return-str></view>
Now if you run the server and go to the root URL you should find that the text in the <view> tag above appears in your browser.
So what is happening here? Moya compares the requested url (/
) to the route
attribute on the <url> tags in the project – and it finds a match with <url> above. Moya then looks at the attribute called view
which contains the text #view.front
. This is an example of an element reference, which directs Moya to another tag; in this case the <view> tag we just wrote. Moya then executes the code in the view to generate a response for the browser.
We will learn how to generate a more interesting response later, for now we will move on to creating creating a database model to store blethers.
In order to store the blethers in the database we need to create a model definition, which describes the fields in a database table.
Add the following lines to blether.xml
:
<model libname="Blether" xmlns="http://moyaproject.com/db"><datetime name="posted_time" label="Blethered on" null="no" auto="yes"/><string name="text" length="140"/></model>
This creates a simple model with two fields; posted_time
is the date the blether was posted, and text
is the contents of the blether.
When you add a new model, you will need to sync the database again, in order to create the new tables. Run the following to create tables for the blether model:
$ moya db sync
With the model created, we can now work on the user interface to add new blethers to the timeline.
We need a simple form consisting of a text area and a button to submit a new blether. Moya's form library makes creating such forms relatively simple. Add the following to blether.xml
:
<form libname="form.new-blether" style="basic" xmlns="http://moyaproject.com/forms"><text-area name="text" maxlength="140" required="yes"/><submit-button text="Blether!" /><validate-field field="text"><fail if="len:value gt 140">140 characters is enough for anyone!</fail></validate-field></form>
This is an example of a form definition. It has two form fields; a <text-area> called text
, and a <submit-button>. It also has a <validate-field> tag which checks that fields have been entered correctly; in this case it detects if the user has entered a blether greater than 140 characters in length.
To add this to our web application, we will need to create a content definition, which we will cover in the next section:
Moya content is essentially a high level description of a page, which is used to assemble HTML from various components. We need a content definition to display a form and the list of blethers. Cut and paste the following to your blether.xml
:
<content libname="content.front"><title>Blether</title><section name="body"><html:div class="col-md-6 col-md-offset-3"><render src="form" if="permission:'admin'"/></html:div></section></content>
This code begins the content definition and sets the title to 'Blether'
. Content is divided in to sections which tell Moya what to render in various parts of the page (such as body, column, footer etc). Our content definition defines a single section called body
, which will contain the majority of the page.
Inside the <section> tag is a div
tag which wraps its children in a simple HTML <div>
with the class "col-md-6 col-md-offset-3"
. This is Bootstrap CSS which creates a narrow column for our blethers.
Inside the column is a render
tag which renders a form. Note that it has a condition (if
attribute] which skips the form if the current user is not an admin user (because we don't want regular visitors to create blethers).
Next we will cover how to use this content definition to render a page.
The simplest way to render content is to add a content
attribute to the <view>. This tells Moya to render the content with any data generated by the view.
To see the form, replace the <view> with the following:
<view libname="view.front" content="#content.front"><if test="permission:'admin'"><forms:get form="#form.new-blether" dst="form"/></if></view>
This will get our form and store it an a value called form
, which can then be rendered by the content.
If you run the server now – and you are logged in – you should see a text area and a button. The <if> tag ensures that the form is only shown if you are a logged in as an admin user. You may now enter text, but nothing will happen when you click the submit button as the code isn't yet processing the form. Replace the view with the following to do just that:
<view libname="view.front" content="#content.front"><if test="permission:'admin'"><forms:get form="#form.new-blether" dst="form"/><forms:validate src="form"><db:create model="#Blether" let:text="form.data.text" /><redirect-to path="." /></forms:validate></if></view>
This gets the form as before, but it also calls <validate> which checks the form for errors, and runs the enclosed code if the form was submitted successfully. Inside the <validate> we call <create> which adds a new blether to the database, then call <redirect-to> to reload the page.
If you enter a blether now and submit, you should find that the form resets, but the blethers wont be visible. We will cover how to render the blethers in the next section.
To render the blethers created by the user, we must read them back from the database. This can be done with the <query> tag as follows:
<db:query model="#Blether" orderby="-posted_time" dst="blethers" />
This queries the database for all the blethers, sorted by the time they were posted.
Add this line to the view, which should now be as follows:
<view libname="view.front" content="#content.front"><db:query model="#Blether" orderby="-posted_time" dst="blethers" /><if test="permission:'admin'"><forms:get form="#form.new-blether" dst="form"/><forms:validate src="form"><db:create model="#Blether" let:text="form.data.text" /><redirect-to path="." /></forms:validate></if></view>
Next, we need to modify the content to render the blethers. The value returned from <query> is a queryset which can be treated like a list. Replace the content definition with the following:
<content libname="content.front"><title>Blether</title><section name="body"><html:div class="col-md-6 col-md-offset-3"><render src="form" if="permission:'admin'"/><for src="blethers" dst="blether"><node template="blether.html" let:blether="blether"/></for></html:div></section></content>
Note the addition of the <for> loop. This iterates through the blethers and calls <node> to render an individual blether with a template called blether.html
. A template is a text file with a special markup to generate HTML.
The template should be the following:
<div class="alert alert-info"><div class="text-muted text-right">posted ${localize:blether.posted_time}</div><p>${blether.text}</p></div>
This template inserts the blether posted time and text in to a panel.
Save the template as blether.html
in the library's templates
directory.
If you have been following along with the code edits, your blether.xml
should look like the following:
<?xml version="1.0" encoding="UTF-8"?><moya xmlns="http://moyaproject.com"xmlns:let="http://moyaproject.com/let"xmlns:forms="http://moyaproject.com/forms"xmlns:html="http://moyaproject.com/html"xmlns:db="http://moyaproject.com/db"><mountpoint name="main"><url route="/" view="#view.front" /></mountpoint><view libname="view.front" content="#content.front"><db:query model="#Blether" orderby="-posted_time" dst="blethers" /><if test="permission:'admin'"><forms:get form="#form.new-blether" dst="form"/><forms:validate src="form"><db:create model="#Blether" let:text="form.data.text" /><redirect-to path="." /></forms:validate></if></view><model libname="Blether" xmlns="http://moyaproject.com/db"><datetime name="posted_time" label="Blethered on" null="no" auto="yes"/><string name="text" length="140"/></model><form libname="form.new-blether" style="basic" xmlns="http://moyaproject.com/forms"><text-area name="text" maxlength="140" required="yes"/><submit-button text="Blether!" /><validate-field field="text"><fail if="len:value gt 140">140 characters is enough for anyone!</fail></validate-field></form><content libname="content.front"><title>Blether</title><section name="body"><html:div class="col-md-6 col-md-offset-3"><render src="form" if="permission:'admin'"/><for src="blethers" dst="blether"><node template="blether.html" let:blether="blether"/></for></html:div></section></content></moya>
If you run the server now, you should be able to enter a 140 character blether in the text area, which will be added to the timeline when you click the submit button.
Our blether timeline is working now, but there is a flaw; the list of blethers could grow very large. We should split up the blethers in to more manageable pages, a process known as pagination.
One of the advantages of working with Moya's content system, is that it is simple to add new features to a page. Let's use a widget from the built in Moya Widgets library.
First of all, add the namespace xmlns:w="http://moyaproject.com/widgets"
to the start of the file. This makes a number of widgets available with the w:
prefix. Next, replace the <for> loop in the content with a <paginate> widget. The content should now look like the following:
<content libname="content.front"><title>Blether</title><section name="body"><html:div class="col-md-6 col-md-offset-3"><render src="form" if="permission:'admin'"/><w:paginate src="blethers" dst="blether"><node template="blether.html" let:blether="blether"/></w:paginate></html:div></section></content>
Now if you run the server and add a number of blethers, you should see a pagination control when the list is 10 items long. You can use this control to navigate forwards and backwards between each page, or jump to a particular page.
While not strictly necessary for this tutorial, now would be a good time to test Moya's debugger. Try running the development server with the following:
$ moya runserver --breakpoint
Now if you refresh the page on the browser, you should find that the terminal is displaying code from the start of the view, and waiting for your input. If you type s
and hit return, you can step through the code a line at a time. Enter help
to see a list of other commands you can use.
Rather than use the --breakpoint
switch, you may also insert a <breakpoint> in your code, which tells Moya to stop executing the code at that point and drop in to the debugger.
The debugger is a great way of analyzing your code when it isn't running as expected.
The project we have just created demonstrates a few of the core concepts of a Moya project. If you want to build on it, you should probably read the Reference and/or have a look at the example projects in the source code.
There is a much more feature-rich version of Blether in the examples directory. This Blether project implements more Twitter-like features, including multiple users/timelines and the ability to follow other users. You should find this a good example of a production-ready web application written in Moya.