diff options
| author | ianb <devnull@localhost> | 2005-09-27 04:58:55 +0000 |
|---|---|---|
| committer | ianb <devnull@localhost> | 2005-09-27 04:58:55 +0000 |
| commit | e937a4b3769d7a879926f88bd4d29f4201afec4e (patch) | |
| tree | 7fc05cccea06f7258fd74bac49b8ea47b3f78789 /docs/blog-tutorial.txt | |
| parent | 1ca42490423a1ab8f431732716eb038786e8a78b (diff) | |
| download | paste-e937a4b3769d7a879926f88bd4d29f4201afec4e.tar.gz | |
Refactored and culled documentation
Diffstat (limited to 'docs/blog-tutorial.txt')
| -rw-r--r-- | docs/blog-tutorial.txt | 374 |
1 files changed, 0 insertions, 374 deletions
diff --git a/docs/blog-tutorial.txt b/docs/blog-tutorial.txt deleted file mode 100644 index d0931e9..0000000 --- a/docs/blog-tutorial.txt +++ /dev/null @@ -1,374 +0,0 @@ -+++++++++++++ -Blog Tutorial -+++++++++++++ - -:author: Ian Bicking <ianb@colorstudy.com> -:revision: $Rev$ -:date: $LastChangedDate$ - -.. contents:: - -.. note:: - - This tutorial is not yet finished. What you see is what you get, - and yeah that's not a whole lot. - -Introduction -============ - -This tutorial will go through the process of creating a blog using -`Python Paste <http://pythonpaste.org>`_, SQLObject_, and `Zope Page -Templates`_. This blog will rely heavily on static publishing -- that -is, when at all possible flat HTML pages will be written to disk. For -some parts (e.g., posting a new item) this will of course be -infeasible, but for most of the site this should work fine. - -.. _SQLObject: http://sqlobject.org -.. _Zope Page Templates: http://www.zope.org/DevHome/Wikis/DevSite/Projects/ZPT/FrontPage - -As much as possible, this code will be accompanied by unit tests, and -test-driven methodologies. Doing test-driven documenting of the -incremental process of creating test-driven software may get a little -hairy, but wish me luck! - -This tutorial presupposes you are somewhat comfortable with the basic -stack -- the `To-Do Tutorial`_ is a better place to start for a -beginner. - -.. _To-Do Tutorial: TodoTutorial.html - -Setting Up The App -================== - -.. note:: - - We're doing all this in the Python interpreter, even though you'd normally do - some of this in the shell. This way the authors of this tutorial - can use something called doctest_, which allows this tutorial to - be tested Python in an automated way. - - .. _doctest: http://python.org/doc/current/lib/module-doctest.html - -.. comment: - - >>> from paste.tests.doctest_webapp import * - >>> BASE = '/var/www/example-builds/wwblog' - >>> import sys - >>> clear_dir(BASE) - >>> run("paster create --template=webkit_zpt %s" % BASE) - >>> os.chdir(BASE) - -:: - - $ export PYTHONPATH=/path/to/Paste:$PYTHONPATH - $ BASE=/var/www/example-builds/wwblog - $ paster create --template=webkit_zpt $BASE - $ cd $BASE - -The Model -========= - -Since we're using SQLObject, we'll be doing the complete model in -that. The predecessor of this blog used flat files, custom-written -indexes, and simple rfc822_ based files for structure. It did not -scale well at all. - -.. _rfc822: http://python.org/doc/current/lib/module-rfc822.html - -Here's the model: - -.. run: - - create_file('db.py', 'v1', r""" - from sqlobject import * - - class Article(SQLObject): - url = StringCol(notNull=True) - title = StringCol() - content = StringCol(notNull=True) - content_mime_type = StringCol(notNull=True) - author = ForeignKey('User') - parent = ForeignKey('Article', default=None) - created = DateTimeCol(notNull=True, default=DateTimeCol.now) - last_updated = DateTimeCol(default=None) - atom_id = StringCol() - hidden = BoolCol(notNull=True, default=False) - article_type = StringCol(notNull=True, default='article') - categories = RelatedJoin('Category') - - class Category(SQLObject): - name = StringCol(alternateID=True) - articles = RelatedJoin('Article') - - class User(SQLObject): - class sqlmeta: - table = 'user_info' - username = StringCol(alternateID=True) - email = StringCol() - name = StringCol() - homepage = StringCol() - password_encoded = StringCol() - role = StringCol(notNull=True, default='user') - """) - -.. raw:: html - :file: resources/blog-tutorial/db.py.v1.gen.html - -A few things to note: - -* All the columns allow ``NULL`` by default, unless we say - ``notNull=True``. - -* ``ForeignKey('User')`` is a join to another table (the ``User`` - table, of course). We have to use strings to refer to other class, - because in this case the ``User`` class hasn't even been created. - Generally all references between classes are by name. - -* ``created`` has a default. You can give a fixed default (like - ``True`` or ``3``), or you can pass in a function that is called. - In this case, if you don't indicate ``Article(..., - created=something)`` then ``created`` will be the current date and - time. Unless a default is explicitly given, it is an error to leave - a column out of the constructor. ``NULL`` (which is ``None`` in - Python) is *not* considered a default. - -* Some column types don't relate directly to database types. For - instance, though PostgreSQL has a ``BOOLEAN`` type, most databases - don't, so ``BoolCol`` translates to some kind of ``INT`` column on - those database. - -* ``RelatedJoin('Category')`` creates a mapping table - (``article_category``) and is a many-to-many join between articles - and categories. - -* ``user`` isn't a valid table name in many databases, so while the - class is named ``User``, the table actually is ``user_info``. This - kind of extra information about a class is typically passed in - through the ``sqlmeta`` inner class. - -These classes have lots of other *behavior*, but this should be a good -list of actual information. We'll add more behavior later. - -Now we'll create the database. First we configure it, adding these -lines to ``server.conf``:: - - import os - database = 'sqlite:%s/data.db' % os.path.dirname(__file__) - -You could also use:: - - database = 'mysql://user:passwd@localhost/dbname' - database = 'postgresql://user:password@localhost/dbname' - -.. comment (change server.conf) - - >>> change_file('server.conf', [('insert', 2, r"""import os - ... database = 'sqlite:%s/data.db' % os.path.dirname(__file__) - ... - ... """)]) - -Now we'll use ``sqlobject-admin`` to set up the tables: - -.. comment (do it) - - >>> run_command('sqlobject-admin create -f server.conf ' - ... '-m wwblog.db', 'create', and_print=True) - - -.. raw:: html - :file: resources/blog-tutorial/shell-command.create.gen.html - -Fixture Data ------------- - -To test things later we'll need a bit of data to make the tests -interesting. It's best if we write code to clear any data and put -known data in -- that way we can restore the database at any time to a -known state, and can write our tests against that data. - -We'll add some code to the end of ``db.py``: - -.. comment (change) - - >>> append_to_file('db.py', 'append-fixture', r""" - ... - ... from paste import CONFIG - ... def reset_data(): - ... sqlhub.processConnection = connectionForURI(CONFIG['database']) - ... for soClass in (User, Category, Article): - ... soClass.clearTable() - ... auth = User(username='author', email='author@example.com', - ... name='Author Person', password_encoded=None, - ... role='author', homepage=None) - ... user = User(username='commentor', email='comment@example.com', - ... name='Comment Person', password_encoded=None, - ... role='user', homepage='http://yahoo.com') - ... programming = Category(name='Programming') - ... family = Category(name='family') - ... a1 = Article(url='/2004/05/01/article1.html', - ... title='First article', - ... content='This is an article', - ... content_mime_type='text/html', - ... author=auth, parent=None, - ... last_updated=None, atom_id=None) - ... a2 = Article(url='/2004/05/10/article2.html', - ... title='Second article', - ... content='Another\narticle', - ... content_mime_type='text/plain', - ... author=auth, parent=None, - ... last_updated=None, atom_id=None) - ... c1 = Article(url='/2004/05/01/article1-comment1.html', - ... title=None, content='Nice article!', - ... content_mime_type='text/x-untrusted-html', - ... author=user, parent=a1, last_updated=None, - ... atom_id=None, article_type='comment') - ... a1.addCategory(programming) - ... a1.addCategory(family) - ... """) - -.. raw:: html - :file: resources/blog-tutorial/db.py.append-fixture.gen.html - -Test Fixture ------------- - -See `Testing Applications With Paste <testing-applications.html>`_ for -more on the details of how we set up testing. We'll be using `py.test -<http://codespeak.net/py/current/doc/test.html>`_ for the testing -framework. - -First, lets create our own test fixture. We'll create a directory -``tests/`` and add a file ``fixture.py``. - -.. comment (create files): - - >>> create_file('tests/__init__.py', 'v1', '#\n') - >>> create_file('tests/fixture.py', 'v1', r""" - ... from paste.tests.fixture import setup_module as paste_setup - ... from wwblog import db - ... - ... def setup_module(module): - ... paste_setup(module) - ... db.reset_data() - ... """) - -.. raw:: html - :file: resources/blog-tutorial/tests/fixture.py.v1.gen.html - -Now in each test we'll do:: - - from wwblog.tests.fixture import setup_module - -And that will give us a consistent state for the module (note that -data isn't reset between each test in the module, just once for the -module, so we'll have to be aware of that). - -Let's write a first test: - -.. comment (create test): - - >>> create_file('tests/test_db.py', 'v1', r""" - ... from fixture import setup_module - ... from wwblog.db import * - ... - ... def test_data(): - ... # make sure we have the two users we set up - ... assert len(list(User.select())) == 2 - ... # and get the first article for testing - ... a1 = list(Article.selectBy(title='First article'))[0] - ... # make sure it has categories, then make sure the - ... # categories contain this article as well - ... assert len(list(a1.categories)) == 2 - ... for cat in a1.categories: - ... assert a1 in cat.articles - ... """) - -.. raw:: html - :file: resources/blog-tutorial/tests/test_db.py.v1.gen.html - -For the most part, this stuff is already tested by SQLObject, but this -is a basic sanity check, and a test that we have set up the classes -properly. One problem, though, is that we have to make sure that -``sys.path`` is set up properly. We could set ``$PYTHONPATH``, but -that can be a bit annoying; we'll put it in a special file -``conftest.py`` that py.test loads up: - -.. comment (create): - - >>> create_file('conftest.py', 'v1', r""" - ... import sys, os - ... sys.path.append('/path/to/Paste') - ... sys.path.append(os.path.dirname(os.path.dirname(__file__))) - ... from paste.util.thirdparty import add_package - ... add_package('sqlobject') - ... """) - -.. raw:: html - :file: resources/blog-tutorial/conftest.py.v1.gen.html - -Now we should be able to run py.test: - -.. comment (do): - - >>> run_command('py.test', 't1', and_print=True) - inserting into sys.path: ... - =...= test process starts =...= - testing-mode: inprocess - executable: .../python (...) - using py lib: .../py <rev ...> - .../test_db.py[1] . - =...= tests finished: 1 passed in ... seconds =...= - -.. raw:: html - :file: resources/blog-tutorial/shell-command.t1.gen.html - -Very good! Alright then, on to making an application... - -Static Publishing ------------------ - -Remember I said something about static publishing? So... what does -that mean? - -Well, it means that when possible we should write files out to disk. -These files might be in their final form, though in some environments -it might be nice to write out files that are interpreted by `server -side includes <http://httpd.apache.org/docs/mod/mod_include.html>`_ or -PHP. - -By generating what is effectively code, the "static" files can contain -dynamic portions, e.g., a running list of recently-updated external -blogs. But more importantly, many changes can be made without -generating the entire site; changes to the look of the site, of -course, but also smaller things, like up-to-date archive links on the -sides of pages and other little bits of code. - -This tutorial will work with server-side includes, because they are -dumb enough that we won't be tempted to push too much functionality -into them (and we might be able to extract their functionality into -fully-formed pages); but they'll also save us a lot of work early on. -If you haven't used server-side includes you can read the document I -linked too, but you can also probably pick it up easily enough from -the examples. - -URL Layout ----------- - -The blog "application" will be available through some URL (which we -can figure out at runtime). But everything else gets written onto -disk with some URL equivalent, so that path and URL will have to be -configurable. - -The Front Page --------------- - -First we'll set up a simple front page. We'll write a new -``index.py``: - -.. comment (do so): - - >>> create_file('web/index.py', 'v1', r""" - ... """) - -.. raw:: html - :file: resources/blog-tutorial/web/index.py.v1.gen.html |
