Package: django-zodb
Django-ZODBDjango-ZODBis a simpleZODBdatabase backend forDjangoFramework.
It’s strongly inpired inrepoze.zodbconn.InstallationDjango-ZODB requires the following packages:Django1.2.3 or newerZODB3.10.0a2 or newerIf you need to store your data in a RDBMS system you will need to install the
following packages too:RelStorage1.5.0a1 or newer - ZODB storage system that store pickles in a
relational database (in a non-relational format).MySQLdb1.2.3 or newer - required to connectMySQLdatabase.psycopg22.3.0-beta1 or newer - required to connectPostgreSQLdatabase.cx_Oracle5.0.3 or newer - required to connectOracledatabase.NoteNot tested withpsycopg2andcx_Oraclebut we believe that
everything will work as expected because we useRelStorageto connect to
the database.Install from sources:$ python setup.py installOr from PyPI (using easy_install):$ easy_install -U django-zodbRunning testsInstallcoverageif you need test coverage informations:$ easy_install -U coverageTo run tests:$ python manage.py testConfigurationYou need to configure yoursettings.pylike this:ZODB = {
    'default': [
        'mysql://user@passwd:localhost/relstorage_db?database_name=app',
        'postgresql://user@passwd:pg_test:5678/app1_db',
    ],
    'test':      [ 'mem://', 'mem://?database_name=catalog' ],
    'legacy_db': [ 'zconfig:///srv/www/zodb_media.conf' ],
    'user_dir':  [
        'zeo://main_db.intranet:7899?database_name=main',
        'zeo://catalog.intranet:7898?database_name=catalog'
    ],
    'old_app':   [
        'file:///var/lib/sitedata.db?blob_dir=/var/lib/blob_dir'
    ],
}You can find a list of schemes and connection adapters inConnection Schemes.Creating sample applicationI strongly believe in “learn by doing” strategy, so, let’s create a sample
Wiki application that stores their pages in ZODB.I suggest the reading of the following tutorials and articles if you don’t know
ZODB or the Traversal Algorithm (that we will use in our tutorial):ZODB TutorialZODB Programming GuideTraversalchapter atRepoze.BFG documentation.NoteRepoze.BFG is now known asPyramid.Starting Django Project and ApplicationWe will start a project calledintranetwith a Django application calledwiki:$ django-admin.py startproject intranet
$ cd intranet
intranet $ python manage.py startapp wikiNow we need to modify oursettings.pyto include this new application and
configure our database connections:#!/usr/bin/env python
# settings.py

import os
ROOTDIR = os.path.dirname(os.path.realpath(__file__))

# No relational database...
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = ':memory:'

# append the following lines:
ZODB = {
    'default': ['file://' + os.path.join(ROOTDIR, 'wiki_db.fs')],
}

# ... other Django configurations ...

MIDDLEWARE_CLASSES = (
    # ... other middlewares ...

    # If everything is ok (aka no exception raised) this middleware will
    # run a transaction.commit() on response.
    'django_zodb.middleware.TransactionMiddleware',
)

INSTALLED_APPS = (
    'django_zodb',  # enable manage.py zshell command
    'wiki',
)Let’s create our model classes. We will need a “root” object that will store our
objects (let’s name itWiki) and a model to store the wiki pages itself
(Page):#!/usr/bin/env python
# wiki/models.py

import markdown  # http://pypi.python.org/pypi/Markdown
from django_zodb import models

# models.RootContainer - Define a 'root' object for database. This class
#                        defines __parent__ = __name__ = None
class Wiki(models.RootContainer):
    def pages(self):
        for pagename in sorted(self):
            yield self[pagename]

    def get_absolute_url(self):
        return "/wiki"

    # It's possible to change models.RootContainer settings using Meta
    # configurations. Here we will explicitly define the default values
    class Meta:
        database = 'default'  # Optional. Default: 'default'
        rootname = 'wiki'     # Optional. Default: RootClass.__name__.lower()

# models.Container - We will use Container to add support to subpages.
class Page(models.Model):
    def __init__(self, content="Empty Page."):
        super(Page, self).__init__()
        self.content = content

    def html(self):
        md = markdown.Markdown(safe_mode="escape",
                extensions=('codehilite', 'def_list', 'fenced_code'))
        return md.convert(self.content)

    @property
    def name(self):
        return self.__name__

    def get_absolute_url(self):
        return u"/".join((self.__parent__.get_absolute_url(), self.name))We’ve a configured application and models. It’s time to map an URL to our view
function:#!/usr/bin/env python
# urls.py

# ... Django default URL configurations ...

urlpatterns = patterns('',
    # ... other URL mappings ...
    (r'^(?P<path>.*)/?$', 'wiki.views.page'),
)Andwiki/views.py:#!/usr/bin/env python
# views.py

import re

from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django import forms

import transaction
from django_zodb import views
from django_zodb import models

from samples.wiki.models import Wiki, Page

wikiwords = re.compile(ur"\b([A-Z]\w+([A-Z]+\w+)+)")


class PageEditForm(forms.Form):
    content = forms.CharField(widget=forms.Textarea)


class WikiView(views.View):
    def __index__(self, request, context, root, subpath, traversed):
        return HttpResponseRedirect("FrontPage")

    def add(self, request, context, root, subpath, traversed):
        try:
            name = subpath[0]
        except IndexError:
            return HttpResponseRedirect("/")

        if request.method == "POST":
            form = PageEditForm(request.POST)
            if form.is_valid():
                page = Page(form.cleaned_data['content'])
                root[name] = page
                return HttpResponseRedirect(page.get_absolute_url())
        else:
            form = PageEditForm()

        page_data = {
            'name': name,
            'cancel_link': "javascript:history.go(-1)",
            'form': form,
        }
        return render_to_response("edit.html", page_data)
views.registry.register(model=Wiki, view=WikiView())


class PageView(views.View):
    def __index__(self, request, context, root, subpath, traversed):
        content = context.html()

        def check(match):
            word = match.group(1)
            if word in root:
                page = root[word]
                view_url = page.get_absolute_url()
                return '<a href="%s">%s</a>' % (view_url, word)
            else:
                add_url = models.model_path(root, "", "add", word)
                return '<a href="%s">%s</a>' % (add_url, word)

        content = wikiwords.sub(check, content)

        page_data = {
            'context': context,
            'content': content,
            'edit_link': context.get_absolute_url() + "/edit",
            'root': root,
        }
        return render_to_response("page.html", page_data)

    def edit(self, request, context, root, subpath, traversed):
        context_path = models.model_path(context)

        if request.method == "POST":
            form = PageEditForm(request.POST)
            if form.is_valid():
                context.content = form.cleaned_data['content']
                return HttpResponseRedirect(context_path)
        else:
            form = PageEditForm(initial={'content': context.content})

        page_data = {
            'name': context.name,
            'context': context,
            'cancel_link': context_path,
            'form': form,
        }
        return render_to_response("edit.html", page_data)
views.registry.register(model=Page, view=PageView())


def create_frontpage(root):
    frontpage = Page()
    root["FrontPage"] = frontpage
    return root

def page(request, path):
    root = models.get_root(Wiki, setup=create_frontpage)
    return views.get_response_or_404(request, root=root, path=path)TraversalFromRepoze.BFG documentation:Traversal is a context finding mechanism. It is the act of finding a context
and a view name by walking over an object graph, starting from a root
object, using a request object as a source of path information.Django-ZODB implements the traversal algorithm in functiondjango_zodb.views.traverse()that receive two arguments:root- an instance of Root model.path- a string with the path to be traversed.And return aviews.TraverseResultobject with the following attributes:context- model object found by traversal.method_name- a method name if exists.subpath- aditional path arguments.traversed- path elements ‘traversed’.root- root object.We’ve created some shortcuts functions to interpret these results:get_response(request, root, path)->HttpResponseget_response_or_404(request, root, path)->HttpResponse or Http404These functions will traverse the model tree and call a registered view function
that handle the context model object found. For example:def handle_page_objects(request, result):
    # result is a TraverseResult object.
    # result.context is a Page object found by traverse
    return render_to_response(...)

# Register handle_page_objects function to handle Page objects:
views.registry.register(model=Page, view=handle_page_objects)You can register aviews.View()instance to handle model objects:class PageView(views.View):
    # This is the 'default' handle (no method_name)
    def __index__(self, request, context, root, subpath, traversed):
        # ... context is a Page object ...
        return render_to_response(...)

    # called when method_name == "edit"
    def edit(self, request, context, root, subpath, traversed):
        # ... context is a Page object ...
        return render_to_response(...)

# Register a PageView *instance* to handle Page objects
views.registry.register(model=Page, view=PageView())Connection SchemesYou can specify a ZODB connection using a URI. This URI is composed of the
following arguments:scheme://username:password@host:port/path?arg1=foo&arg2=bar#fractionDepending on the chosen scheme some of these arguments are required and
others optional.Database and Connection settingsArguments related to database connection settings. These arguments are optional
and must be passed as query argument in URI (eg.?database_name=db&...).database_name-str- database name used by ZODB.connection_cache_size-int- size (in bytes) of database cache.connection_pool_size-int- size of connection pool.These arguments are passed toZODB.DB.DB()constructor.Memory Storagemem:(ZODB.MappingStorage)Returns an in-memory storage. It’s basically a Pythondict()object.Valid URIs:mem
mem:
mem://
mem?database_name=memoryOptional ArgumentsSeeDemo storage argument.SeeBlob storage arguments.File Storagefile:(ZODB.FileStorage)Returns a database stored in a file. You need to specify an absolute path to the
database file.Valid URIs:file:///tmp/Data.fs
file:///tmp/main.db?database_name=fileInvalid URIs:file://subdir/Data.fsRequired Argumentspath-str- absolute path to file where database will be stored.Optional Argumentscreate-bool- create database file if does not exist. Default:create=True.read_only-bool- open storage only for reading. Default:read_only=False.quota-int- storage quota. Default: disabled (quota=None).SeeDemo storage argument.SeeBlob storage arguments.zconfig:(ZODB.DB.DB)Returns database (or databases) specified in ZCML configuration file.NoteThis scheme has some small differences with other schemes because it
returns a DB object instead of a Storage. It’s a problem only in cases where
you are creating the connection ‘by hand’ instead of use a higher level API.URIs Examples:zconfig:///my/app/zodb_config.zcml
zconfig:///my/app/zodb_config.zcml#mainRequired Argumentspath(str) - absolute path to file where database will be stored.Optional Arguments (and default values)#fragment=''(str) - Get only an specific database. By default
('') get only the first database specified in configuration file. We
don’t use a query argument (&arg=...) to specify database name to
keep compatibility withrepoze.zodbconn.zeo:(ZEO.ClientStorage.ClientStorage)Returns a connection to a ZEO server.TODOmysql:(RelStorage)Returns a database stored in a MySQL relational server. This scheme usesRelStorageto establish connection with database server.URIs Examples:mysql://user:password@host:3306?compress=true#mysql_db_name
mysql:///tmp/mysql.sock#local_database
mysql://localhost#databaseArgumentsSeeRelational storage arguments.postgresql(RelStorage)Returns a database stored in a PostgreSQL relational server. This scheme usesRelStorageto establish connection with database server.URIs Examples:postgresql://user:password@host:5432#mysql_db_nameArgumentsSeeRelational storage arguments.Demo storage argumentdemostorage(bool) - Enable the ZODB’s demo storage wrapper.Blob storage argumentsblob_dir(str) - Directory where blob objects will be stored.Relational storage argumentsDjango-ZODB usesRelstorageto connect to RDBMS and we preserve the same
arguments used by RelStorage. The only difference between RelStorage`s arguments and Django-ZODB arguments is that we use “_” (underline) instead of “-” (dash). For example: the RelStorage’s argument “shared-blob-dir” becomes “shared_blob_dir”.ContributingHi, I’m accepting all kind of collaborations to this project. You can open issues in our issue tracker, send me a patch, an e-mail message with your questions, etc.All kind of collaboration will be welcome.TODOReview my ‘engrish’ in documentationCreate a new WebsiteRelease 0.2 version (and announce)Test Relstorage connections with Oracle and PostgreSQLCreate more manage.py commands for ZODB managementCreate a Django-ORM layer (wow!)Evaluate some fulltext-search, catalog, etc integrationsFix performance issues (?)… and fix (tons of) bugs! :D