The first thing Moya does when handling a request from the browser is to look at the requested URL and invoke the code in your project that will generate a response.
A library may expose a number of mountpoints, which are collections of URLS to be handled by the application. The URLs defined in a mountpoint are relative to the location where the application has been mounted. For instance, if a blog application was mounted on the path /blog/
, it would be able to handle the path /blog/posts/
and all other paths under /blog/
.
Let's look at an example of a simple mountpoint:
<mounpoint><url route="/"><echo>Received a request for /</echo></url></mountpoint>
Assuming this was a part of a library called moyaproject.sushifinder
, it would be mounted with the following lines inside the project's <server> as follows:
<install name="sushifinder" lib="moyaproject.sushifinder"/><mount app="sushifinder" url="/sushifinder/" />
Now if you run the development server and visit /sushifinder/
, you should find that a line of text will be echoed in the console. The page returned will be a 404 not found response because although Moya has invoked the code inside the <url> tag, it hasn't generated any kind of response. We'll cover the various methods of generating a response later in this chapter.
Libraries can have more than one mountpoint, which gives the project author the opportunity to chose a different path for each mountpoint, or to decide which mountpoints should be mounted at all. For instance a library may have a separate mountpoint for administration functionality and user views. The name
attribute in <mountpoint> is used to name the mountpoint. This name is used by the <mount> tag to tell Moya which mountpoint to use.
Let's demonstrate this by adding a second mountpoint:
<mounpoint><url route="/"><echo>Received a request for /</echo></url></mountpoint><mountpoint name="shop"><url route="/"><echo>Handling Shop</echo></url></mountpoint>
Here we have added a second mountpoint with a name of 'shop'
. We can mount this new mountpoint by adding the following to the <server> tag as follows:
<install name="sushifinder" lib="moyaproject.sushifinder"/><mount app="sushifinder" url="/sushifinder/" /><mount app="sushifinder" url="/checkout/" mountpoint="shop" />
The second mountpoint has been mounted on /checkout/
, so visiting that URL will echo a different message to the console.
The default value for the name
attribute on <url> is 'main'
, which is also the default value for mountpoint
in the <mount> tag. This is why we didn't need the name
attribute to mount the first mountpoint we created.
It is very common to mount the default mountpoint immediately after installing the application. Because of this, Moya adds a shortcut to the <install> which will mount the default mountpoint. If you set the mount
attribute, Moya will mount the default mountpoint on that path. Here's how we would use this to simplify the code in <server>:
<install name="sushifinder" lib="moyaproject.sushifinder" mount="/sushifinder/"/>
This only works for the default mointpoint (named 'main'
). We would still need <mount> tags for other mountpoints.
The code in a <url> tag is invoked if it matches the supplied route, specified with the route
attribute. A route can be a simple URL, if it is designed to handle a single page, or can contain a wildcard in curly brackets ({}
) if it is to match a number of URLs in a particular format. Here's an example of a <url> with a wildcard route:
<url route="/list/{product}/"><echo>Listing product ${url.product}</url></url>
When moya sees text enclosed in curly brackets ({
and }
) it will match that part with any text in the requested URL. For instance, if the above URL was mounted in the sushifinder application, it would match /sushifinder/list/tunaroll/
and /suhifinder/list/sake-nigiri/
. Moya also extracts the matched text and stores it in a dictionary called url
(the text in the curly brackets is used as the key). So with the previous examples URLs, url.product
would be 'tunaroll'
or 'sake-nigiri'
. Moya also stores a copy of the url
dictionary in the root of the context, so that it is always accessible as .url
.
The route matching syntax also supports specifying the format of the text that matches. For instance, let's say we have a URL route as follows:
/blog/{year}/{month}/
This will match /blog/2014/7/
, but it will also match /blog/some-text/not-a-month/
, which might cause problems if we attempt to use text where we expect a number. You can tell Moya to only match a particular set of characters by specifying the type followed by a colon then the name. For example, we can re-write the above URL as the following:
/blog/{integer:year}/{integer:month}/
The integer:
prefix tells Moya to match only valid numbers. The URLs containing text would now not match and Moya would return a '404 not found' page (assuming they match no other URL that is).
integer:
prefix only tells Moya to match digits, the value of url.year
and url.month
will still be strings. If you do need integers values, you can convert the strings to a number in an expression with the int:
modifier.The following is a list of the route match types:
Normally Moya will only match only text between slashes. Occasionally it may be necessary to match text that includes forward slashes (typically to match paths). You can tell Moya to do this in the path matching syntax by prefixing it with an asterisk. Here's an example:
/serve/{*path}
This will match any URL that starts with /serve/
, and extract the remainder of the URL. For instance, it will match the URL /serve/media/images/sushi.png
, and url.path
with contain media/images/sushi.png
.
You can write URLs which match only particular http methods. The default for a view is to match both GET and POST requests, but it is possible to match any combination of methods by specifying a comma separated list in the methods
attribute of the <view>. Here's how we can define two <url> tags for the same route, one will handle GET requests, and the other will handle POST requests:
<url route="/product/{product}/" methods="GET"><echo>render a product page</echo></url><url route="/product/{product}/" methods="POST"><echo>handle a form on the product page</echo></url>
Separate handlers for GET and POST requests can be convenient. The default, however, is to handle both GET and POST in the same <url>. The value .request.method
contains the current method, so you could use the expression .request.method=='POST'
to detect a post request. Alternatively, the <if-post> tag will execute a block of code if the current request is a POST request.
When you define a <url>, you can supply an optional name
parameter which you can use to look up the URL. This is important because the final URL can change if the application is mounted in a different location. If we were to hard-code that URL (such as in template code) we would have to manually update every point where that URL was used if the application was mounted in a different location. For example, lets look at a URL in our fictitious sushifinder application:
<url route="/reviews/"><echo>Product reviews</url></url>
If we mount the app on /sushifinder/
then the final URL for the page would be /sushifinder/reviews/
. We could use this URL in a template, and it would appear to work just fine. A problem arises if we mount that application in a different location; all of a sudden any links to that page are broken and will likely return a 404 Not Found
response. The solution is to first give the URL a name. For example:
<url route="/reviews/" name="reviews_index"><echo>Product reviews</url></url>
We can now replace any references to that URL in our template with a {% url %} template tag, as follows:
<a href="{% url 'reviews_index' %}">Sushi Reviews</a>
Now, if the final URL changes, The links to it within templates will update automatically. You may do the same thing in Moya code with the <get-url> tag as follows:
<get-url name="reviews_index" dst="reviews_url" /><echo>The URL for reviews is ${reviews_url}</echo>
As we've seen, some URLs may contain parameters. Moya needs these extra parameters in order to generate the full URL. The following is an example of a URL with a parameter:
<url route="/reviews/{product}/" name="reviews_product"><echo>Product reviews</url></url>
The above <url> actually defines a number of URLs, depending on the value of product
. If we want to link to a particular product, we must let Moya know what that product is. Here's how we would link to a product called nigiri
in a template:
<a href="{% url 'reviews_product' with product='nigiri' %}">Go to Nigiri Reviews</a>
In Moya code we can generate the same URL by passing the URL parameters to <get-url> as a LET value. Here's how:
<get-url name="reviews_product" let:product="nigiri" dst="nigiri_reviews_url" /><echo>The URL for nigiri reviews is ${nigiri_reviews_url}</echo>
An alternative to <get-url> for looking up URLs is the URL container object in the context (.urls
). This object contains all URLs in the project in a hierarchical format. For instance .urls.auth.login
will return a URL to the login page in auth
application.
For URLs that contain parameters you will need to append values for those parameters in parenthesis. For example, the following gets the About page from the Pages application:
<echo>About URL is: ${.urls.pages.showpage(pagename='about')}</echo>
The (pagename='about')
syntax supplies the missing parameters. Without a parameter for pagename
Moya will be unable to generate a working URL, and will raise an error.
The URLs container object may be explored via the debugger. Probably the simplest way to do this would be to run the server with the --breakpoint
switch, as follows:
$ moya runserver --breakpoint
Then load any page in the browser (e.g. http://127.0.0.1:8000). Moya will immediately drop in to the debugger. If you inspect the value .urls
, you should see a table of mountpoints in each application. If you inspect a mountpoint (e.g. .urls.pages
) you will see a table of URLs.
We've seen how invoke code that matches a particular URL, but so far we've only written to the console when a URL is requested. To build a real web application we will need to generate a response, which will typically be a page of HTML.
When Moya executes the code inside a <url>, it treats the contents as a macro (or function). If we return (with <return>) something from a <url> tag, Moya will convert that return value to HTML and serve it to the browser. Here's an example of returning HTML from a <url> tag:
<!-- This should be inside a mountpoint tag --><url route="/test/"><return><html><![CDATA[<h1>Hello, World!</h1>]]></html></return></url>
Don't worry if this looks clumsy to you. Returning HTML like this is rarely done in a real project – there are much better ways of generating a response which we will cover next.
A more convenient (and powerful) way of generating a response is to render a template, which is a text file with a special markup. We can do this with the <render-template> tag. Here's an example of generating a page of HTML from a template:
<url route="/test2/"><return><render-template template="/hello.html" let:name="'World'"/></return></url>
Here is the template for use with the above code (saved as "hello.html"
in the templates directory):
<h1>Hello, ${name}!</h1>
Now when you request /test2/
, Moya will render "hello.html"
with the value for name
replaced with the text we supplied to the <render-template> tag. The resulting string is then served as a standard HTML response. See Templates for more information on writing templates.
In the preceding example we returned a string which Moya used to generate the response. Moya also offers the <serve-template> tag which renders a template and immediately returns the response, negating the need to return a value. The following code is equivalent to the preceding example:
<url route="/test3/"><serve-template template="/hello.html" let:name="'World'" /><echo>Moya will never get here</echo> <!-- see below --></url>
Note that because the <serve-template> generates and returns a response, the following <echo> line never gets called. This is generally useful because it is rare to need to do any more processing once you have a response.
The <serve-template> has an optional parameter withscope
which tells Moya to use the current local variables (anything variables created in the URL) when generating the template. This negates the need to specify the template data explicitly, which can save some time. Here's an example of rendering and serving a template with local variables, equivalent to the previous example:
<url route="/test4/"><str dst="name">World</str><serve-template template="/hello.html" withscope="yes"/></url>
When Moya renders hello.html
, the template will have access to the name
variable even though it wasn't explicitly specified. Serving templates with the current scope can save some time, but it is not always desirable to expose everything to the templates in this way. If you have something in the local scope that you don't want to be used in the template, it may be a better idea to explicitly list the variables you do want.
The <serve-file> tag may be used to serve a static file from any filesystem in the project. Here's an example of using <serve-file> to serve files from a filesystem called photos
:
<url route="/photos/{*path}"><serve-file fs="photos" path="${url.path}"/></url>
Note the asterisk in the url route, which tells Moya to match the remainder of the path, including forward slashes. When Moya receive a request for a URL beginning with /photos/
, such as /photos/menu/nigiri.jpg
, the portion after /photo/
is stored in url.path
. This result is the <serve-file> tag being called with a path of menu/nigiri.jpg
. If that file exists in the photos
filesystem it will be served, otherwise Moya will return a 404 Not Found
response.
Moya can serve a redirect response which tells the browser to request a new URL. There are two tags that do this; <redirect-to> which redirects to a relative path or a new URL entirely, and <redirect> which redirects to a new named url. Here's an example of redirecting to a new URL outside of the project:
<!-- can't sell sake to kids --><redirect-to url="http://google.com" if="not .request.POST.over_eighteen" />
The <redirect> tag is similar, but requires a named URL route in the project:
<redirect name="out_of_stock" if="product.stock == 0" />
A similar concept to redirecting is rewriting, which can serve content from a different URL without changing the URL in the browser's location bar.
You can serve a 404 Not Found
response with the <not-found> tag. Moya will generate a 404 automatically if no <url> handled the current request, but sometimes you want to explicitly generate the not found response if the URL refers to a resource that is missing. Here's an example:
<url route="/product/{product}"><not-found if="url.product not in ['nigiri', tuna-roll', 'fugu']" /><serve-template template="product.html" let:product="url.product" /></url>
The <not-found> tag in the above example checks that the value of url.product
equals one of three products. If it doesn't, Moya will serve a not found response.
Not that because a not found response is a valid response, Moya will not check any more URLs for the current request after a <not-found> tag. If you want skip processing the current <url>, but still look for other potential URL matches, you can use the <done> tag.
Moya can serve a 403 forbidden
response, which tells the browser that the current user may not access that URL or resource. For example, the following URL generates a forbidden response if the user is not currently logged in:
<url route="/order/{product}/"><forbidden if="not .user"/></url>
For more control over the type of response generated you can use the <response> tag, which constructs a response object. Return a response object from a <url> to serve it. Here's an example of generating a 418 I'm a teapot response:
<url route="/serve/tea/"><return><response status="im_a_teapot">Short and stout.</response></return></url>
When Moya is unable to return content due to an error or a 'not found' response, it will search the mountpoints for a handler for that URL. This is so that your project has an opportunity to generate a custom response which would otherwise result in Moya serving default content.
All that is required to define a custom handler is to set the handler
attribute on a <url> to the HTTP status code you want to handle. Here's how we might write a handler for a not found response:
<url route="/product/{product}/"><not-found if="url.product not in ['nigiri', tuna-roll', 'fugu']" /><serve-template template="product.html" let:product="url.product" /></url><url route="/product/{product}/" handler="not_found"><serve-template template="product_not_found.html" let:product="url.product" /></url>
The first <url> we have seen before; it will be invoked when the user requests /sushifinder/product/unagi/
, but because it is not one of the three products it will generate a 404 not found
response. Normally this will generate a standard response, but because the second <url> is a handler for not_found
, Moya will invoke that code, which serves a template called "product_not_found.html"
. This other template could generate a more specific message for the user, or possibly offer a search box so the user may find other products.
If your project doesn't explicitly define a handler for a status code, Moya will attempt to render and serve a template called <status code>.html
. For example, if your project generates a 404 not found
response or there is no matching <url>, Moya will serve the template 404.html
in the root of your templates directory.
It is recommended that you at least define templates 404.html
(not found) and 500.html
(internal error). If they are not present, Moya will generate a rather dull looking page without your site's branding.
Note that if you are running the server with debug enabled then the Moya Debug application will supply templates for 404.html
and 500.html
. These templates will generate a helpful response when you are debugging, but you will need to disable debug mode if you want to test your versions of these templates.
You can change the URL that Moya is currently processing with the <rewrite> tag which rewrites to a named url in the project, or with <rewrite-to> which can rewrite to an abitrary URL. When Moya sees either of these tags it will stop processing further URLs and start again with the new URL. This is a similar concept to redirects – the difference is that Moya will serve the content for the new URL in the current request without changing the URL in the user's browser.
Let's look at an example of where you might use a rewrite. If your site has been translated in to a number of languages, one way of switching between languages would be to have the languages code as the first component of the URL. For example /fr/about/
should show the about page in French. But we also want /about/
to show the default language of English. Here's how we could do that with the <rewrite-to> tag:
<url route="/{lang}/{*path}"><if test="lang in ['en', 'fr', 'es']"><set-language language="${lang}" /><rewrite-to path="${path}" /></if></url>
This <url> tag matches the first component of the a URL. If the first component is one of the supported languages, then the enclosed code will set the language and rewrite the URL to the remainder of the path. Otherwise the URL is served as normal.
We've seen that Moya runs the code <url> tags which match the requested URL. It is also possible to explicitly set a special callable tag called a view which is responsible for generating a response. Typically code inside <url> tags is reserved for simple things like permissions checks and redirects, the job of generating a page of HTML is delegated to a <view> tag. Let's look at how we might modify a previous example to use a view:
<mountpoint><url route="/product/{product}/" view="#view.product"><not-found if="url.product not in ['nigiri', tuna-roll', 'fugu']" /></url><mountpoint/><view libname="view.product"><serve-template template="product.html" let:product="url.product" /></view>
Here we can see that the <url> contains a conditional <not-found> tag, which will return a not found
response if the product in the url is not one of three products. Otherwise, the view specified in the view
attribute will be invoked.
The view
attribute of <url> is an element reference. It is set to #view.product
, which tells Moya to use the view with a libname of view.product
in the current library. The view.
part of the libname is a convention – it helps to keep your project organized if you name elements according to their type.
Moya invokes the code inside the <url> first. If that code doesn't generate a response, it will call the code inside the <view>. Views have a number of advantages which we will cover next.
Because rendering a template is by far the most common thing you will do in a view, the <view> tag has a shortcut for this. You can specify a template
attribute on the view. This template will be rendered with any variables created in the current scope (i.e. within the view itself). Here's how we could re-write the view to take advantage of this:
<view libname="view.product" template="product.html"><let product="url.product"/></view>
When the above view runs, Moya renders product.html
with the current scope (the value for product
in this example). This negates the need for the explicit <serve-template> tag.
If you don't want to pass everything in the current scope to the template (there may be intermediate data you don't want the template designer to reference), you can explicitly return the data which you want to render in the template. Here's how to do that:
<view libname="view.product" template="product.html"><return><dict><let product="url.product" /></dict></return></view>
Which method you use depends largely on how complex the view is. In a real project some views may do several database queries and calculations, resulting in a number of intermediate values that you don't want to be available to the template. For these, you should use the more explicit way of specifying template data. Leave the implicit template rendering of the scope for simpler views.
If you find the preceding example a little verbose, you can use <return-dict> which is a shortcut that returns a dictionary. Here's an example:
<view libname="view.product" template="product.html"><return-dict><let product="url.product" /></return-dict></view>
You can save on typing even further with the LET extension:
<view libname="view.product" template="product.html"><return-dict let:product="url.product"/></view>
Another keyboard saving option is the <return-scope>, which parses a list of values to return from the scope. Here's an example:
<view libname="view.product" template="product.html""><let product="url.product"/><return-scope>product</return-scope></view>
This is probably the best choice for complex views which calculate a lot of data.
There is a another attribute which you can specify on a <view>, called content
. This renders a content element rather than a template. See Content for how to work with content elements.
Middleware is code that runs outside of the usual URL handling process in order to implement features that should be applicable to all requests. This is how the Moya Auth library adds user and permission information to the context.
To create middleware, add a <middleware> tag inside your <mountpoint>. The <middleware> tag takes similar attributes to <url>; route
, methods
and name
all have the same meaning. In addition, there is a macro
attribute which should be a reference to a <macro> to call, and stage
which defines the point in the url handling process where middleware should be called. The following is a list of possible stages:
.response
.
Here's an example of adding middleware to a mountpoint:
<mountpoint><middleware route="/" macro="middleware.sushi" stage="request" /><url route="/product/{product}/" view="#view.product"><not-found if="url.product not in ['nigiri', tuna-roll', 'fugu']" /></url><mountpoint/><macro libname="#middleware.sushi"><echo obj=".request" /></macro>
With the above code, every request under the mountpoint will invoke the macro (which prints the request to the console).
If the middleware code generates any kind of response (including redirects) then that will be served, and no more processing of URLs will occur. In the case of middleware for the response
stage, a response has already been generated, and can be inspected as .response
– it is possible to modify this value, or issue a new response.