A Molly application is just a standard Django application, however, the Molly framework provides a few extra features to make applications within Molly consistent, and handling mobile browsers easier.
Note
For a more in-depth look at these features of the Molly framework, please see the documentation linked above, this is a simple overview of how to get started writing a Molly app.
See also
A Molly app is also a Django app. The Django tutorial is a good introduction to writing Django apps, and it may be beneficial to familiarise yourself with Django concepts before
On disk, a Molly app may look similar to this:
myapp/
├ migrations/
│ └ [...]
├ providers/
│ ├ __init__.py
│ └ myprovider.py
├ static/
│ └ myapp/
│ ├ css/
│ │ └ smart.css
│ ├ js/
│ │ └ smart.js
│ └ images/
│ ├ icon.png
│ └ [...]
├ templates/
│ └ myapp/
│ ├ base.html
│ ├ index.html
│ └ [...]
├ templatetags/
│ ├ __init__.py
│ └ molly_myapp.py
├ __init__.py
├ admin.py
├ forms.py
├ models.py
├ search.py
├ tests.py
├ urls.py
└ views.py
Note
Not all of these files may exist or are necessary in all apps.
Let’s break down the content of this folder. migrations are used to store migrations which are managed by South. The providers folder, unsurprisingly, contains the providers that come bundled with the application. __init__.py normally contains the base provider (i.e., an abstract class which demonstrates the signature expected of providers for that class), and then any other subfiles contain the concrete providers, following the signature of the base provider.
The static and templates folders each have a single folder underneath them, with the same name as the application which it provides. This is due to the way collating templates and media works, so adding an additional level avoids clashes with other applications during the collation process. In this folder are the media and templates for the app which are used for rendering. The media is exposed to the world under the STATIC_URL defined in the configuration, and can be referenced in your templates. For the apps that ship with Molly, we have followed a standard of having 3 subdirectories here: js, css, and images.
Note
JavaScript and CSS is automatically minified during the build process when installation is done in non-development mode.
The files css/smart.css, css/dumb.css and js/smart.js in the media folders have special meanings, and are included automatically on pages (when using the standard base template). smart.css is served to “smart” phones, dumb.css to “dumb” phones and smart.js to “smart” phones which Molly considers to have an acceptable level of JavaScript support.
Note
Technically js/dumb.js also has a special meaning, but “dumb” phones do not get served JavaScript, so the script will never be included by default.
templatetags is a standard implementation of Django’s template tags, which Molly gives no special meaning to. Molly applications themselves have standardised on a prefix of molly_ to the template tags tag name to prevent clashes with any external apps being used.
__init__.py typically provides utility functions within the application, and admin.py provides the functionality (if any) for this application in the Django admin view. Similarly, forms.py is where any Django forms live, models.py where the Django models are and tests.py where any unit tests for this application stay. This is the same layout as for a typical Django app.
views.py is typically where any views for this application are stored, however, unlike in typical Django apps, these views follow the Molly view format, which is documented below. Similarly, urls.py is a standard Django URL dispatcher, however in most cases an actual reference to the class, rather than a string, should be used, e.g.:
from django.conf.urls.defaults import *
from views import IndexView, FooView, BarView
urlpatterns = patterns('',
(r'^$', IndexView, {}, 'index'),
(r'^foo/(?P<arg>.+)$', FooView, {}, 'foo'),
(r'^bar/$', BarView, {}, 'bar'),
)
The first argument in each pattern is a regular expression. Any match groups in the regular expression are then passed to the methods of the view as arguments. Molly exclusively uses named match groups (which are passed as keyword arguments) to accomplish this.
search.py is a file which has special meaning within Molly. If there is a class called ApplicationSearch in here, then this is used within the site-wide search framework.
See also
Molly provides a powerful framework for writing class-based views by providing a class called BaseView. Writing a view generally consists of extending this class and then providing content for a few methods.
Available on each view is also an attribute conf, which contains the configuration of the application which this view belongs to. This contains all the configuration arguments specified in the configuration file, as well as:
When a view is called, then the initial_context method is called, along with the request object, as well as any arguments defined in the URL pattern. This function then sets up the context which is used for rendering.
Note
If this class inherits from ZoomableView or FavouritableView, then you should call the super function in initial_context in order to correctly setup the context.
This is done by populating a dictionary with various keys and values depending on what needs to be rendered, and then returning this dictionary, e.g.:
from molly.utils.views import BaseView
class FooView(BaseView):
def initial_context(self, request):
context = {}
context['rain'] = 'Mainly on the plain'
return context
...
When this method is called, then any match groups defined in the URL pattern are also presented alongside it, e.g., if the match group was: ^/(?P<id>\d+)/$, then this is how the initial_context could be written:
from molly.utils.views import BaseView
from models import Foo
class FooView(BaseView):
def initial_context(self, request, id):
context = {}
context['thing'] = Foo.objects.get(pk=id)
return context
...
Also of note, is the ability to raise `django.http.Http404 <http://docs.djangoproject.com/en/dev/topics/http/views/#the-http404-exception>`_, which will cause the view to render as a 404 error.
You will have to write a handle_* method for each HTTP method you wish to support. In most cases, this will just be GET, and sometimes POST, although you can support some of the rarer requests if you would like (HEAD by default is handled by rendering it as a GET and then stripping the content).
For whatever method you write, the method is called along with the request object, the context as set up by initial_context, as well as any arguments defined in the match group.
This function is expected to return a HttpResponse object, which can be done by calling 2 shortcut functions: render or redirect which are defined in BaseView.
These can be utilised like so:
from molly.utils.views import BaseView
class FooView(BaseView):
def handle_GET(self, request, context):
return self.render(request, context, 'myapp/template')
def handle_POST(self, request, context):
# Handle a form response, which is available in the request.POST
# dictionary
return self.redirect(context['uri'], request)
...
As with initial_context, raising `django.http.Http404 <http://docs.djangoproject.com/en/dev/topics/http/views/#the-http404-exception>`_, will cause the view to render as a 404 error.
Molly uses a “breadcrumb trail” approach to navigation across the site. In the default template, this is represented at the top of the screen. In order to generate a breadcrumb trail, each view defines a breadcrumb method, which returns a function which evaluates to a tuple providing the following members:
In order to simplify this, you can simply return a Breadcrumb object from your method, and then decorate it using the BreadcrumbFactory decorator.
A Breadcrumb object consists of the name of the application, the URL to the parent page, the title of this particular page, and the URL of this particular page. A typical example may look like:
from molly.utils.views import BaseView
from molly.utils.breadcrumbs import Breadcrumb, BreadcrumbFactory
from django.core.urlresolvers import reverse
class FooView(BaseView):
@BreadcrumbFactory
def breadcrumb(self, request, context, ...):
return Breadcrumb(
'myapp',
reverse('myapp:index'),
context['title'],
reverse('myapp:foo'),
)
...
Note
If the view is the top-level page in the app, the second argument should be None.
This assumes that initial_context adds a title key to the context. This could be static text, or some other method of deducing the name of this page. Also, if the pattern for matching this page includes any optional arguments, then these are passed as additional arguments at the end of the method.
In some circumstances, you will want to get information about a view, without actually rendering it. This is done, for example, when rendering a search result or displaying information about search results. To provide information for these uses, then views can define a get_metadata function, which returns a dictionary with the keys title, containing the title of the page being rendered, and an optional additional line, which contains additional information about the view:
from molly.utils.views import BaseView
from molly.utils.breadcrumbs import Breadcrumb, BreadcrumbFactory
from django.core.urlresolvers import reverse
class FooView(BaseView):
def get_metadata(self, request):
return {
'title': 'Foo Checker',
'additional': 'Check on the current status of foo',
}
...
Also, if the pattern for matching this page includes any optional arguments, then these are passed as additional arguments at the end of the function.
If you are rendering maps, and want the ability to make static maps zoomable, then you can instead inherit from molly.utils.views.ZoomableView, which will add the ability to zoom in and out of static maps.
Warning
If the device supports slippy maps, then all maps will be zoomable.
To use this, you must also set up the context in initial_context using a super() call. The context will then contain a key called zoom which can be passed to the Map constructor to build a map at the correct zoom level.
If you would like to specify a default zoom level, you can do this by adding an attribute to your class called default_zoom, e.g.,:
from molly.utils.views import ZoomableView
class FooView(ZoomableView):
default_zoom = 16
def initial_context(self, request):
context = super(FooView, self).initial_context(request)
...
return context
...
If you would like to make it so that a view can be marked as a favourite, then molly.favourites.views.FavouritableView is available as a base class, which when used as a base adds values to the context which are used to add the ability to add/remove favourites on those rendered pages:
from molly.favourites.views import FavouritableView
class BarView(FavouritableView):
def initial_context(self, request):
context = super(BarView, self).initial_context(request)
...
return context
...
Another view available is molly.auth.views.SecureView. When extending this view, then all requests to your view must be made over a HTTPS connection (unless DEBUG_SECURE is true).
Note
This tutorial was first given as a workshop at Dev8D 2011. The code from this workshop has been made available, and as of version 1.1 has been incorporated into the transport app.
Now we’ve covered the basics of a Molly view and the structure of an app, we can start building our first app. In this worked example, we will build an application to display the status of mass transit systems (particularly the London Underground and Glasgow Subway).
Django provides a simple method to start an app, which should be sufficient as the first step in making any new app for Molly. It doesn’t really matter where the code is stored, but it should be on your Python path. In most cases, a good place to put it is in your site (the ‘deploy’ directory by default).
To get started, we can use the django-admin function in your deploy directory to create the framework for your app:
django-admin.py startapp transit_status
The last argument here is the name of the folder (and subsequentally the app) to be created. Inside this folder, we see the structure of an app as described above, although with a few files missing. From here, we’re ready to start, so let’s put together a view which does nothing.
Creating a simple view in Molly is quite simple, you just need to extend BaseView and then provide at least one handle_* method - typically handle_GET for most pages, and handle_POST if you need to deal with forms, and a breadcrumb method.
Open up views.py in your favourite text editor, and then add the following:
from molly.utils.views import BaseView
from molly.utils.breadcrumbs import Breadcrumb, BreadcrumbFactory, lazy_reverse
class IndexView(BaseView):
@BreadcrumbFactory
def breadcrumb(self, request, context):
return Breadcrumb('transit_status', None, self.conf.title,
lazy_reverse('index'))
def handle_GET(self, request, context):
return self.render(request, context, 'transit_status/index')
Here, we have two methods: handle_GET, which simply renders the template ‘transit_status/index’ breadcrumb which returns the details for the breadcrumb navigation included in the default template.
The breadcrumb method here uses the standard way of generating breadcrumbs in Molly:
As our handle_GET method is rendering a template, we will now need to write a template to do this. The most minimal thing we can do here is to create new folders in your application folder called templates/transit_status, and then create a blank file called index.html. We can add some content to this file later.
The final step to produce a minimal view is to create a urlconf to requests to the view. Urlconf’s are standard Django fare, and a fairly standard one could be created which looks like:
from django.conf.urls.defaults import *
from views import IndexView
urlpatterns = patterns('',
(r'^$', IndexView, {}, 'index'),
)
With all that done, we now need to add the new app to your settings.py, and start up the development server to see our blank page in action.
See also
To do this, at the end of the APPLICATIONS list in settings.py, an Application definition needs to be added. In this case, the following will suffice:
Application('transit_status', 'transit_status', 'Tube Status'),
Now, start up a development server and browse to your development instance (typically http://localhost:8000). There should be a blank icon on the home screen at the end with your new application below it. Clicking on that should take you to a blank page.
Note
The installation guide contains information on how to install Molly in development mode, or to start a development server.
Note
Not seeing what you expect? Ask the Molly community, who will be able to help you.
Now we have a basic view working, we can start fleshing out our views and templates. One thing that needs adding to the templates is a get_metadata method, which allows for pages to appear in search results, as well any further future use, such as favouriting pages. In most cases, this simply needs to be something which returns the title of the current page, as well as any additional information about what the page does. On this view, we can simply add:
def get_metadata(self, request):
return {
'title': self.conf.title,
'additional': 'View the status of transit lines'
}
The next step is to add something to our template to make it a bit more than a blank screen, this can be done by adding:
{% extends "base.html" %}
to the template, which renders the base style of the site, with any additional content to be displayed.
Note
Most Molly apps actually extend app_name/base.html, which in turn extend base.html. This structure allows for entire apps to be styled consistently to each other, but different to the core styling, if so desired.
At this point, we need to decide on the format of the context to be presented to the template, as well as the format of the data provided by providers.
Note
Molly separates apps into “views” and “providers”. Providers should provide abstract interfaces to services which views can call to get the details about the configured services. Views should therefore be service agnostic.
Most applications supply a BaseProvider which provides a signature for concrete providers to follow. For this transit line app, a provider which implements a single get_status method should suffice. This method is then responsible for querying the service, and then returning the information. For our users, this information is in the form of a list of Python dictionaries, where each Python dictionary provides the name of the line (line_name), the current status (status) and an optional reason for disruption (disruption_reason).
With this decided, we can now define the context. Here, we can simply pass the results from the provider into the context. As the title of the app is configurable (e.g., a London university may set it to ‘Tube status’, a Mancunian one to ‘Metrolink status’, etc), we also want this in the context.
Once the context is defined, we can set up the template to render this. To display content when extending the base template, you have to define a block called content, and place your template code in there. For our template, with the context structure defined above, utilising Molly’s default CSS structure, we can edit templates/transit_status/index.html to look like so
{% extends "base.html" %}
{% block content %}
<div class="section">
<div class="header">
<h2>{{ title }}</h2>
</div>
<table class="content">
<thead>
<tr>
<th>Line</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for status in statuses %}
<tr>
<td>{{ status.line_name }}</td>
<td>{{ status.status }}
{% if status.disruption_reason %}<br/>{{ status.disruption_reason }}{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
See also
We now need to set up the context to actually provide this information to the template. We can do this by adding an initial_context method to IndexView which returns our context dictionary. In the context, we need to provide two things:
Our initial_context method should therefore look like this:
def initial_context(self, request):
return {
'title': self.conf.title,
'statuses': self.conf.provider.get_status()
}
At this point, we also need to write our base provider, and also alter the configuration of the application to include a provider attribute.
To create a base provider, then the following can be included in a new file, providers/__init__.py:
class BaseTransitLineStatusProvider(object):
def get_status(self):
# Return a list of dictionaries, where the dictionaries have keys
# of "line_name", "status" and optional "disruption_reason"
return []
See also
We can now alter our settings.py application configuration to point to this provider, and our app should now render the page as expected (with no line statuses showing quite yet). To do this, an argument to the Application contructor called provider should be added, which is itself is a Provider, constructed with the classpath of the provider. i.e.:
Application('transit_status', 'transit_status', 'Tube Status',
provider=Provider('transit_status.providers.BaseTransitLineStatusProvider')),
Now we have the basis of an app actually working, that’s all the Molly specific stuff over. All that remains is for us to add an actual provider. In a new file, providers/tfl.py, the following can be pasted:
import urllib
from xml.dom import minidom
from transit_status.providers import BaseTransitLineStatusProvider
class TubeStatusProvider(BaseTransitLineStatusProvider):
LINESTATUS_URL = 'http://cloud.tfl.gov.uk/TrackerNet/LineStatus'
def get_status(self):
statuses = []
status_xml = minidom.parse(urllib.urlopen(self.LINESTATUS_URL))
for node in status_xml.documentElement.childNodes:
if node.nodeType == node.ELEMENT_NODE and node.tagName == 'LineStatus':
line_status = {
'disruption_reason': node.getAttribute('StatusDetails'),
}
for child in node.childNodes:
if child.nodeType == child.ELEMENT_NODE and child.tagName == 'Line':
line_status['line_name'] = child.getAttribute('Name')
elif child.nodeType == child.ELEMENT_NODE and child.tagName == 'Status':
line_status['status'] = child.getAttribute('Description')
statuses.append(line_status)
return statuses
Then, the provider in the application configuration can be changed as below to use this new provider:
Application('transit_status', 'transit_status', 'Tube Status',
provider=Provider('transit_status.providers.tfl.TubeStatusProvider')),
We now have a complete application for displaying the status of the London Underground lines!
With this split of views and providers, it makes it very simple to adjust an app for use by others, in other contexts. The following provider, if placed in providers/spt.py, would allow access for status of the Glaswegian subway:
from transit_status.providers import BaseTransitLineStatusProvider
from lxml import etree
import urllib2
class SubwayStatusProvider(BaseTransitLineStatusProvider):
JOURNEYCHECK_URL = 'http://www.spt.co.uk/journeycheck/index.aspx'
def get_status(self):
statuses = []
xml = etree.parse(urllib2.urlopen(self.JOURNEYCHECK_URL), parser = etree.HTMLParser())
ul = xml.find(".//ul[@id='jc']")
for li in ul:
statuses.append({
'line_name': ''.join(li.itertext()).strip(),
'status': li.find(".//img").attrib['alt']
})
return statuses
And if the configuration of the app was changed as below, this app is now also suitable for a Glaswegian university:
Application('transit_status', 'transit_status', 'Subway Status',
provider=Provider('transit_status.providers.spt.SubwayStatusProvider')),
Of course, this is a very simplistic application, it doesn’t utilise the database, only has one view and doesn’t deal with forms, but those features are part of Django, which is well-documented, rather than particular to the Molly framework.