/
application-framework.txt
249 lines (200 loc) · 10.1 KB
/
application-framework.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
The Application framework
=========================
Molly extends Django by formalising the concept of an application. This is
acheived by instantiating :class:`~molly.conf.settings.Application` objects
and adding then to :data:`~settings.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
:doc:`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 :class:`~molly.conf.settings.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
.. method:: __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.
:param application_name: a Python package path to the application to be
used, e.g. ``'molly.apps.places'``.
:type application_name: str
:param local_name: a local unique identifier for this instance of the
:class:`~molly.conf.settings.Application`. In most cases this will
be identical to the last part of ``application_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.
:type local_name: str
:param title: A descriptive title for the application instance to be
shown to the user.
:type title: unicode or str
.. method:: get()
Used internally. Creates a configuation object and dereferences all the
paths provided to it. Where a ``urls`` module exists it will call
:meth:`~molly.conf.settings.Application.add_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 :meth:`~molly.conf.settings.Application.__init__`.
The urlconf will be exposed as a ``urls`` attribute.
:rtype: ApplicationConf subclass
.. method:: add_conf_to_pattern(pattern, conf, bases)
Used internally. Maps the ``conf`` and ``bases`` onto the views
contained in ``pattern``. This method creates a new view, with ``conf``
in the class dictionary and the view and ``bases`` 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 :func:`type`.
When given a :class:`~django.core.urlresolvers.RegexURLPattern` it will
return a new :class:`~django.core.urlresolvers.RegexURLPattern`
with its callback replaced as above. When given a
:class:`~django.core.urlresolvers.RegexURLResolver` it will descend
recursively before returning a new
:class:`~django.core.urlresolvers.RegexURLResolver` instance.
:type pattern: RegexURLResolver or RegexURLPattern instance
:type conf: ApplicationConf subclass
:type bases: tuple of BaseView subclasses
:returns: A copy of the first argument with views replaced as described
above.
:rtype: 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 :class:`~molly.conf.settings.Provider` instances to
load providers from.
``provider`` (or any keyword ending with provider)
A shorthand for ``providers = [`provider`]``.
``display_to_user``
A :class:`bool` used by :mod:`molly.apps.home` to determine whether a link
should be rendered for the application on the home page.
``extra_bases``
An iterable of :class:`~molly.conf.settings.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 :class:`bool` which if :const:`True` will add
:class:`~molly.auth.views.SecureView` as a base class of all views in the
application. :class:`~molly.auth.views.SecureView` forces all requests to
be made over HTTPS, and provides a ``secure_session`` attribute on
:class:`~django.http.HttpRequest` 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 to
``application_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.
.. versionadded:: 1.4
Providers now all subclass ``molly.conf.provider.Provider``
.. Extra base classes
.. ------------------ TODO?
Task Processing
---------------
.. versionadded:: 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.
__ http://docs.celeryproject.org/en/latest/getting-started/index.html
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
@task()
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.