demo/demo/controllers/root.py | 4
docs/main/HelloWorld.rst | 211 ++++++++++++++++++++++++++++++++++--------
2 files changed, 175 insertions(+), 40 deletions(-)
New commits:
commit 701e95ae2c7ba5f5804eaece5d5135c7ef63e79b
Author: Luke Macken <lmacken(a)redhat.com>
Date: Tue Nov 30 17:19:41 2010 -0500
Lots of documentation updates for our Hello World demo
diff --git a/demo/demo/controllers/root.py b/demo/demo/controllers/root.py
index d31128c..9486d9d 100644
--- a/demo/demo/controllers/root.py
+++ b/demo/demo/controllers/root.py
@@ -22,7 +22,7 @@ class Root(object):
@expose('mako:demo.templates.model')
def model(self, *args, **kwargs):
from demo.model import DBSession, HelloWorldModel
- entries = DBSession.query(HelloWorldModel).all()
+ entries = DBSession.query(HelloWorldModel).limit(10).all()
return dict(entries=entries)
@expose('mako:demo.templates.model')
@@ -35,4 +35,4 @@ class Root(object):
def _get_entries(self, *args, **kwargs):
from demo.model import DBSession, HelloWorldModel
- return DBSession.query(HelloWorldModel).all()
+ return DBSession.query(HelloWorldModel).limit(10).all()
diff --git a/docs/main/HelloWorld.rst b/docs/main/HelloWorld.rst
index 2dc335d..5983190 100644
--- a/docs/main/HelloWorld.rst
+++ b/docs/main/HelloWorld.rst
@@ -10,9 +10,9 @@ basic "Hello World" components in one demo.
The Controller
--------------
-This is the most basic of controllers.
+This is the most basic of TurboGears controllers.
-`demo/controllers/root.py`
+``demo/controllers/root.py``
.. code-block:: python
@@ -25,15 +25,19 @@ This is the most basic of controllers.
return 'Hello World!'
-Ok, so here we have a trivial TurboGears controller. How do we plug this into
-Moksha and actually run it?
+Ok, so here we have a trivial TurboGears controller that simply renders "Hello
+World". So how do we plug this into Moksha and actually run it?
-Let's say we want this index method to be the root of our application. To
-accomplish this we add it to the `[moksha.root]` entry-point in our setup.py::
+Let's say we want this controller method to be the root of our application. To
+accomplish this we add it to the ``[moksha.root]`` entry-point in our ``setup.py``::
[moksha.root]
root = demo.controllers.root:Root
+.. note::
+
+ Any time you modify any entry-points in your setup.py/pavement.py, you must
regenerate
+ the egg info by running ``python setup.py egg_info``
.. seealso::
@@ -42,31 +46,34 @@ accomplish this we add it to the `[moksha.root]` entry-point in our
setup.py::
Running the Moksha stack
------------------------
+The Moksha Stack is comprised of a WSGI application, Orbited, and the Moksha
+Hub. These pieces can be deployed and run in a variety of ways (see the
+:doc:`Deployment` guide for more information), but the easiest way to get them
+running is with the :doc:`MokshaCLI`.
+
.. code-block:: bash
$ moksha start
+Now when we fetch the index page, Moksha will dispatch the request to our new
controller.
+
.. code-block:: bash
$ curl
http://localhost:8080/
Hello World
-.. seealso::
-
- :doc:`MokshaCLI`
-
Bringing a templating engine into the mix
-----------------------------------------
The previous example just returned a string from our controller. What if we
want to use one of the many powerful templating engines out there?
-TurboGears supports a variety of engines, including Genshi, Mako, Jinja, etc.
+TurboGears supports a variety of engines, including Genshi, Mako, Jinja, Cheetah, etc.
-So, lets create a dead simple Mako template.
+For this example let's just create a dead simple `Mako
<
http://makotemplates.org>`_ template.
-`demo/templates/template.mak`
+``demo/templates/template.mak``
.. code-block:: html
@@ -89,29 +96,47 @@ Now let's plug in our Mako template into our Root controller.
Building a basic Widget
-----------------------
+A "Widget" is simply a Python object that contains references to
CSS/JavaScript
+resources, a template, and server-side render-time logic.
+
.. image:: ../_static/widget.png
+In TurboGears, and thus Moksha, the widget framework of choice is `ToscaWidget
+<http://toscawidgets.org>`_, which allows you to create modular components that
+can be re-used throughout your application.
+
.. code-block:: python
from tw.api import Widget
class HelloWorldWidget(Widget):
- params = ['msg']
- msg = 'Hello World'
- template = '${msg}'
- engine_name = 'mako'
+ params = ['msg'] # The parameters that this widget takes
+ msg = 'Hello World' # The default message value
+ template = '${msg}' # The widget template, which has access to all of the
`params`.
+ # The template can be either a string or also an external
reference like,
+ # template =
'mako:myproject.templates.widgettemplate'
+ engine_name = 'mako' # The template engine. Unnecessary if referencing an
external template.
def update_params(self, d):
""" Render-time logic """
super(HelloWorldWidget, self).update_params(d)
+ # This code will be executed when the widget is rendering during each
request.
+ # The argument `d` contains the widget data and params.
+ # So d.msg would currently be be 'Hello World'
+
+You can then plug this widget into the ``[moksha.widget]`` entry-point.
-`setup.py`
+``setup.py``
.. code-block:: python
[moksha.widget]
- helloworld = demo.widgets:HelloWorldWidget
+ basic = demo.widgets:HelloWorldWidget
+
+
+Moksha will expose your widget on the /widgets/$NAME URL. Since we named this widget
'basic' on the
+entry-point, we can fetch it like so:
.. code-block:: bash
@@ -124,6 +149,9 @@ Building a basic Widget
<body>Hello World</body>
</html>
+
+You can also pass in different parameters to your widget via the URL.
+
.. code-block:: bash
$ curl
http://localhost:8080/widgets/basic?msg=foobar
@@ -139,11 +167,41 @@ Building a basic Widget
:doc:`Widgets`
+.. seealso::
+
+ `TG2 TW docs
<
http://turbogears.org/2.1/docs/main/ToscaWidgets/ToscaWidgets.html>`_
+
+
+Real-time Messaging
+-------------------
+
+Now that we've got the basics out of the way, we can finally move on to the fun stuff
-- *messaging*.
+
+One of the features that makes Moksha unique in the web framework world is that
+it encorporates a Message Broker into the mix, allowing you to create highly
+responsive and interactive web applications.
+
+Traditionally, the messaging world is full of acronyms (AMQP, STOMP) and
+complexity (queues, exchanges, binding keys, flow control). Moksha, on the
+other hand, aims to provide a high level abstraction on top of these concepts,
+while offering a trivial API for people to utilize them with ease.
+The primary messaging concepts that Moksha defines are **Producers** and
+**Consumers**. These are objects that produce messages, along with objects
+that consume them. Each of which communicate over specific **Topics**.
+
+.. seealso::
+
+ :doc:`Messaging`
Creating a message producer
---------------------------
+A Producer in Moksha does what you would expect, sends messages to the broker.
+Let's say you want a Producer that wakes up every 3 seconds, performs some
+task, and sends a message. Moksha provides a ``PollingProducer`` class that
+can do just this.
+
.. code-block:: python
from datetime import timedelta
@@ -155,9 +213,16 @@ Creating a message producer
def poll(self):
self.send_message('helloworld', {'msg': 'Hello
World!'})
-.. seealso::
+This ``HelloWorldProducer``, which will be initialized by the :doc:`MokshaHub`,
+wakes up every 3 seconds, and sends a 'Hello World!' message to the
+``helloworld`` :doc:`Topic`.
- :doc:`Messaging`
+.. note::
+
+ As with all of the other examples above, you must plug your object into a
+ moksha entry-point in your setup.py. For the case of producers, it is the
+ ``[moksha.producer]`` entry-point. This allows the ``moksha-hub`` to detect
+ your plugin and initialize/run it as necessary.
.. seealso::
@@ -166,7 +231,11 @@ Creating a message producer
Creating a message consumer
---------------------------
-`demo/consumer.py`
+The moksha Consumer API lets you create a simple Python object with a consume
+method that will be executed with each new message as it is received from the
+broker.
+
+``demo/consumer.py``
.. code-block:: python
@@ -179,6 +248,9 @@ Creating a message consumer
def consume(self, message):
self.log.info('Received message: ' +
message['body']['msg'])
+
+This example listens to the ``helloworld`` topic, and simply logs each message that it
receives.
+
.. seealso::
:doc:`Consumers`
@@ -186,9 +258,16 @@ Creating a message consumer
Running the Moksha Hub
----------------------
+The ``moksha-hub`` is a service that runs outside of the web application. It
+handles loading all of the producers and consumers, as well as communicating
+with the message broker.
+
.. image:: ../_static/moksha-hub.png
-<consumer output>
+.. note::
+
+ The Moksha Hub is automatically started when you run ``moksha start``, but you
+ can also start it by running ``moksha-hub``.
.. seealso::
@@ -197,12 +276,11 @@ Running the Moksha Hub
Creating a Live Widget!
-----------------------
-Ok, on to the fun stuff.
+So producers and consumers work inside of the moksha-hub. Moksha's Live Widgets, on
the other hand, can produce and consume messages **in the web browser**.
-Moksha provides an API for creating "live widgets". A widget is a re-usable
-bundle of HTML/JavaScript/CSS/Server-side logic Making it "live" entails
-having the widget "subscribe" to "topics" and perform some action
upon
-new messages as they arrive in the users web browser.
+Moksha provides an API for creating "live widgets". Making a widget
"live"
+entails having it "subscribe" to "topics" and perform some action
upon new
+messages as they arrive in the users web browser.
.. image:: ../_static/live_widgets.png
@@ -222,9 +300,21 @@ new messages as they arrive in the users web browser.
$('<li/>').text(json.msg).prependTo('#data');
"""
-<add to entry point>
+.. note::
-<rendering the widget>
+ To make moksha aware of this widget, you have to add it to the
+ ``[moksha.widget]`` entry-point in your setup.py
+
+This widget will automatically be subscribed to the ``helloworld`` topic, and
+the ``onmessage`` javascript callback will be run every time a new message
+arrives with the decode JSON data available in the ``json`` variable. Moksha
+handles all of the work behind the scenes subscribing to the appropriate
+message queues, decoding JSON data, and dispatching messages to the appropriate
+widgets.
+
+You can view this widget multiple ways. First being via the standard ``/widgets/`` URL.
If you place your widget on the ``[moksha.widget]`` entry-point named 'live', then
you can view your live widget by going to ``/widgets/live?live=True``. Passing in the
``live=True`` variable tells Moksha to inject the Moksha Live Socket along with the
widget. This is needed to setup the realtime pipes.
+
+If you want to integrate the widget in your controller, you can do something like the
following:
.. code-block:: python
@@ -234,13 +324,19 @@ new messages as they arrive in the users web browser.
tmpl_context.moksha_socket = moksha.get_widget('moksha_socket')
return dict(options={})
+Moksha provides a widget template that will render ``tmpl_context.widget`` with
+the provided ``options``. It will also inject the moksha_socket if that exists
+on the template context as well.
+
+From here you can view your widget by going to ``/livewidget``. You should see
+a new "Hello World!" message appear on the page every 3 seconds.
.. seealso::
:doc:`LiveWidget`
-Sending messages to from the Live Widget
-----------------------------------------
+Sending messages from the Live Widget
+-------------------------------------
You can send messages with Moksha's JavaScript API using the following function:
@@ -248,8 +344,8 @@ You can send messages with Moksha's JavaScript API using the
following function:
moksha.send_message('helloworld', {'foo': 'bar'});
-So let's add a simple little text field to our HelloWorldWidget that allows people
-to send messages to the `helloworld` topic:
+So let's add a simple little text field to our ``HelloWorldWidget`` that allows
+people to send their own messages to the `helloworld` topic:
.. code-block:: python
@@ -275,11 +371,16 @@ to send messages to the `helloworld` topic:
$('<li/>').text(json.msg).prependTo('#data');
"""
+TODO: <screenshot>
Creating a database model
-------------------------
-`demo.model.model.py`
+Let's say we want to store every new message on the ``helloworld`` topic in a SQL
database.
+
+Here is an example of a simple SQLAlchemy model that can be used to store our messages.
+
+``demo.model.model.py``
.. code-block:: python
@@ -294,13 +395,24 @@ Creating a database model
message = Column(Text)
timestamp = Column(DateTime, default=datetime.now)
+When you hook your controller up to moksha via the ``[moksha.application]``
+entry-point, Moksha will automatically detect your ``model`` module if it
+exists, and will try and initialize it.
+
.. seealso::
`Working with SQLAlchemy and your data model
<
http://turbogears.org/2.1/docs/main/SQLAlchemy.html>`_
Populating our database
~~~~~~~~~~~~~~~~~~~~~~~
-<via the consumer upon message arrival>
+
+Now let's plug our database model into our consumer and create a new entry for
+each message as it arrives.
+
+When you specify the name of your ``app`` in your Consumer, as it is defined on
+the ``[moksha.application]`` entry-point, Moksha will automatically hook up a
+SQLAlchemy engine connected to your model as ``self.engine``, and a SQLAlchemy
+ORM session as ``self.DBSession``.
.. code-block:: python
@@ -323,6 +435,9 @@ Populating our database
Querying our database
~~~~~~~~~~~~~~~~~~~~~
+Next up, we're going to create a controller method to query our database and
+display the last 10 entries in our database
+
.. code-block:: python
from demo.model import DBSession, HelloWorldModel
@@ -331,9 +446,25 @@ Querying our database
@expose('mako:demo.templates.model')
def model(self, *args, **kwargs):
- entries = DBSession.query(HelloWorldModel).all()
+ entries = DBSession.query(HelloWorldModel).limit(10).all()
return dict(entries=entries)
+
+Then we create a simple template that displays the entries.
+
+``demo/templates/model.mak``
+
+.. code-block:: html
+
+ <h1>Entries in the HelloWorld model</h1>
+
+ <ul>
+ % for entry in entries:
+ <li>${str(entry.id)} - ${entry.message} -
${str(entry.timestamp)}</li>
+ % endfor
+ </ul>
+
+
.. seealso::
`SQLAlchemy documentation <
http://www.sqlalchemy.org/docs>`_
@@ -341,6 +472,10 @@ Querying our database
Caching
-------
+The last step to our demo is to do some caching. As an example, we'll cache
+the previous controller method, so we don't query the database every time
+someone wants to view the latest entries.
+
.. code-block:: python
from pylons import cache
@@ -356,7 +491,7 @@ Caching
return dict(entries=entries)
def _get_entries(self, *args, **kwargs):
- return DBSession.query(HelloWorldModel).all()
+ return DBSession.query(HelloWorldModel).limit(10).all()
.. seealso::