The Application framework¶
Molly extends Django by formalising the concept of an application. This is
acheived by instantiating Application
objects
and adding then to APPLICATIONS
in your settings
module.
This document explains how the Application framework works, and should not be a necessary part of your reading unless you intend to write a new Molly application, or have sufficient levels of curiosity.
If you don’t know whether you want to write an application or a provider, the following section may be useful. For information on writing providers, see topics/writing_providers.
Difference between applications and providers¶
Molly is intended to be relatively pluggable, allowing the deploying institution to take whatever provided functionality they choose and, if necessary, integrate it with their own applications.
This integration can be done at two levels, the application level, or the provider level.
An application is a Python package, usually containing urls
, models
and views
modules. An application provides a data model and interface for
a particular class of information, for example, PC availability or lecture
timetables.
A provider is usually a single Python class that connects an application to a data source. A provider might implement some well-known protocol or file format (e.g. RSS for the feeds application), or might connect to a local bespoke system.
It is intended that in the majority of cases the implementor should be able to take an already-existing application and need only write the provider that performs the interaction with other systems.
Overview of the Application framework¶
An Application
object is a wrapper around a
Python package which hooks in providers and configuration objects for easy
access by the application. The definition is as follows:
-
class
molly.conf.settings.
Application
¶ -
__init__
(application_name, local_name, title, **kwargs)¶ Instantiates an Application object. None of the module or package paths are dereferenced yet.
kwargs
are mostly left alone and attached to the ~molly.conf.settings.ApplicationConf class. Some, defined later, have special meanings.Parameters: - application_name (str) – a Python package path to the application to be
used, e.g.
'molly.apps.places'
. - local_name (str) – a local unique identifier for this instance of the
Application
. In most cases this will be identical to the last part ofapplication_name
. This will be used in almost all cases where an application needs to be referenced. It is also used by the default urlconf creator to determine the URL prefix this site is served under. - title (unicode or str) – A descriptive title for the application instance to be shown to the user.
- application_name (str) – a Python package path to the application to be
used, e.g.
-
get
()¶ Used internally. Creates a configuation object and dereferences all the paths provided to it. Where a
urls
module exists it will calladd_conf_to_pattern()
to walk the urlconf to attach the configuration object to the views.The ApplicationConf returned will have attributes reflecting the
kwargs
passed to__init__()
. The urlconf will be exposed as aurls
attribute.Return type: ApplicationConf subclass
-
add_conf_to_pattern
(pattern, conf, bases)¶ Used internally. Maps the
conf
andbases
onto the views contained inpattern
. This method creates a new view, withconf
in the class dictionary and the view andbases
as base classes. Specifically:new_callback = type(callback.__name__ + 'WithConf', (callback,) + bases, { 'conf': conf })
This dynamically creates a new class object. For more information, see the second definition of
type()
.When given a
RegexURLPattern
it will return a newRegexURLPattern
with its callback replaced as above. When given aRegexURLResolver
it will descend recursively before returning a newRegexURLResolver
instance.Returns: A copy of the first argument with views replaced as described above. Return type: RegexURLResolver or RegexURLPattern instance
-
In the vast majority of cases, you will only need to use the constructor, and
only in your settings
module.
There are a few keyword arguments with special meanings:
providers
- An iterable of
Provider
instances to load providers from. provider
(or any keyword ending with provider)- A shorthand for
providers = [`provider`]
. display_to_user
- A
bool
used bymolly.apps.home
to determine whether a link should be rendered for the application on the home page. extra_bases
- An iterable of
ExtraBase
instances, defining extra base classes to add to all views in the application. With suitably defined extra base classes one can override functionality. Application-level authentication may also be added in this manner. secure
- A
bool
which ifTrue
will addSecureView
as a base class of all views in the application.SecureView
forces all requests to be made over HTTPS, and provides asecure_session
attribute onHttpRequest
objects. urlconf
- A module path to the
urls
module to use for this application. May be useful if an application uses a non-standard naming, or if you want to override the application-provided urlconf. If not provided, defaults toapplication_name + '.urls'
to_email
- This is optional, and defaults to the admins setting, and refers to the default target for e-mails generated by this app.
from_email
- This is optional, and sets the e-mail address e-mails generated by this app appears from
Here’s an example:
APPLICATIONS = [
# ...
Application('example.apps.dictionary', 'dictionary', 'Dictionary',
provider = Provider('isihac.providers.apps.dictionary.uxbridge'),
max_results = 10,
),
# ...
]
Here we want to use a dictionary application with at most ten results from the Uxbridge English Dictionary. If we wanted to expose two different dictionaries we may wish to do the following:
APPLICATIONS = [
# ...
Application('example.apps.dictionary', 'uxbridge_dictionary', 'Uxbridge English Dictionary',
provider = Provider('isihac.providers.apps.dictionary.uxbridge'),
max_results = 10,
),
Application('example.apps.dictionary', 'oxford_dictionary', 'Oxford English Dictionary',
provider = Provider('oxford.providers.apps.dictionary.oed'),
max_results = 20,
),
# ...
]
Once hooked into the root urlconf, this would present two links on the home
page. Alternatively, if the example.apps.dictionary
application supported
multiple providers, we could do this:
APPLICATIONS = [
# ...
Application('example.apps.dictionary', 'dictionary', 'Dictionaries',
providers = (
Provider('isihac.providers.apps.dictionary.uxbridge',
slug='uxbridge',
title='Uxbridge English Dictionary'),
Provider('oxford.providers.apps.dictionary.oed',
slug='oed',
title='Oxford English Dictionary'),
),
max_results = 10,
),
# ...
]
Of course, this assumes that the application knows to pick the slug
and
title
from each of its providers. To determine the interface between
applications and providers, consult the application’s documentation.
Providers¶
A provider maps an external interface onto the model used by the application.
Most applications provide a providers.BaseProvider
class which specifies
an interface to be implemented by a provider for that application.
New in version 1.4: Providers now all subclass molly.conf.provider.Provider
Task Processing¶
New in version 1.4.
Celery is used to provide asynchronous task processing. For an introduction to the basics of Celery we recommend you take a look at the “Getting Started with Celery” guide.
Molly uses a modified version of the Celery task decorator located in
molly.conf.provider.task
this should be used in a similar the previous
@batch decorator to identify any methods on a provider to run async via celery.
See this (simplified) example from molly.apps.feeds.providers.rss
:
@task(run_every=timedelta(minutes=60))
def import_data(self, **metadata):
"""
Pulls RSS feeds
"""
from molly.apps.feeds.models import Feed
for feed in Feed.objects.filter(provider=self.class_path):
logger.debug("Importing: %s - %s" % (feed.title, feed.rss_url))
self.import_feed.delay(feed)
return metadata
# Override CELERY_RETRY_DELAY and CELERY_MAX_RETRIES
@task(default_retry_delay=5, max_retries=2)
def import_feed(self, feed):
from molly.apps.feeds.models import Item
feed_data = feedparser.parse(feed.rss_url)
# Do stuff with feed_data
We can iterate through all feeds and launch tasks to import them asynchronously
using task.delay()
. This convention has been applied through all the
standard providers packaged with Molly. Note the default_retry_delay and max_retries
are overridden on import_feed. This means each feed will only be retried 2 times, with
5 seconds between each of those retries.