Added sphinx format documentation matching the current README file with a few layout changes, no real change to the content.

This commit is contained in:
Chris Franklin 2013-02-28 00:28:46 +00:00
parent f965bb333d
commit 1777b4f8ab
7 changed files with 595 additions and 0 deletions

88
docs/Makefile Normal file
View File

@ -0,0 +1,88 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf _build/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
@echo
@echo "Build finished. The HTML pages are in _build/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml
@echo
@echo "Build finished. The HTML pages are in _build/dirhtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in _build/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in _build/qthelp, like this:"
@echo "# qcollectiongenerator _build/qthelp/project.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile _build/qthelp/project.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex
@echo
@echo "Build finished; the LaTeX files are in _build/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes
@echo
@echo "The overview file is in _build/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in _build/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in _build/doctest/output.txt."

195
docs/conf.py Normal file
View File

@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
#
# dinnertime documentation build configuration file, created by
# sphinx-quickstart on Wed Aug 19 10:27:46 2009.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
import datetime
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.append(os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'django-tenant-schema'
copyright = u'%d, myauthor' % datetime.date.today().year
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
#unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_use_modindex = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'tenantschemasdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'tenantschema.tex', u'tenantschemaDocumentation',
u'myauthor', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True

53
docs/index.rst Normal file
View File

@ -0,0 +1,53 @@
Welcome to django-tenant-schemas documentation!
===============================================
.. toctree::
:maxdepth: 2
install
use
test
involved
About the project
==================
This application enables `Django <https://www.djangoproject.com/>`_ powered websites to have multiple tenants via `PostgreSQL schemas <http://www.postgresql.org/docs/9.1/static/ddl-schemas.html>`_. A vital feature for every Software-as-a-Service website.
Django provides currently no simple way to support multiple tenants using the same project instance, even when only the data is different. Because we don't want you running many copies of your project, you'll be able to have:
* Multiple customers running on the same instance
* Shared and Tenant-Specific data
* Tenant View-Routing
What are schemas?
------------
A schema can be seen as a directory in an operating system, each directory (schema) with it's own set of files (tables and objects). This allows the same table name and objects to be used in different schemas without conflict. For an accurate description on schemas, see `PostgreSQL's official documentation on schemas <http://www.postgresql.org/docs/9.1/static/ddl-schemas.html>`_.
Why schemas?
------------
There are typically three solutions for solving the multinancy problem.
1. Isolated Approach: Separate Databases. Each tenant has it's own database.
2. Semi Isolated Approach: Shared Database, Separate Schemas. One database for all tenants, but one schema per tenant.
3. Shared Approach: Shared Database, Shared Schema. All tenants share the same database and schema. There is a main tenant-table, where all other tables have a foreign key pointing to.
This application implements the second approach, which in our opinion, represents the ideal compromise between simplicity and performance.
* Simplicity: barely make any changes to your current code to support multitenancy. Plus, you only manage one database.
* Performance: make use of shared connections, buffers and memory.
Each solution has it's up and down sides, for a more in-depth discussion, see Microsoft's excelent article on `Multi-Tenant Data Architecture <http://msdn.microsoft.com/en-us/library/aa479086.aspx>`_.
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

155
docs/install.rst Normal file
View File

@ -0,0 +1,155 @@
==================
Installation
==================
Assuming you have django installed, you'll have to make the following modifcations to your `settings.py` file.
Basic Settings
==============
Your `DATABASE_ENGINE` setting needs to be changed to::
DATABASES = {
'default': {
'ENGINE': 'tenant_schemas.postgresql_backend',
# ..
}
}
Add the middleware `tenant_schemas.middleware.TenantMiddleware` to the top of `MIDDLEWARE_CLASSES`, so that each request can be set to use the correct schema.::
MIDDLEWARE_CLASSES = (
'tenant_schemas.middleware.TenantMiddleware',
#...
)
Don't forget to add `tenant_schemas` to your `INSTALLED_APPS`.::
INSTALLED_APPS = (
#...
'tenant_schemas',
#...
)
Configure Tenant and Shared Applications
========================================
By default all apps will be synced to your `public` schema and to your tenant schemas. If you want to make use of shared and tenant-specific applications, there are two additional settings called `SHARED_APPS` and `TENANT_APPS`. `SHARED_APPS` is a tuple of strings just like `INSTALLED_APPS` and should contain all apps that you want to be synced to `public`. If `SHARED_APPS` is set, then these are the only apps that will be to your `public` schema! The same applies for `TENANT_APPS`, it expects a tuple of strings where each string is an app. If set, only those applications will be synced to all your tenants. Here's a sample setting::
SHARED_APPS = (
'tenant_schemas', # mandatory!
# everything below here is optional
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.admin',
)
TENANT_APPS = (
# your tenant-specific apps
'myapp.hotels',
'myapp.houses',
)
INSTALLED_APPS = SHARED_APPS + TENANT_APPS
The Tenant Model
================
Now we have to create your tenant model. To allow the flexibility of having any data in you want in your tenant, we have a mixin called `TenantMixin` which you *have to* inherit from. This Mixin only has two fields (`domain_url` and `schema_name`) and both are required. Here's an example, suppose we have an app named `customer` and we want to create a model called `client`.
from tenant_schemas.models import TenantMixin
class Client(TenantMixin):
name = models.CharField(max_length=100)
paid_until = models.DateField()
on_trial = models.BooleanField()
created_on = models.DateField(auto_now_add=True)
# default true, schema will be automatically created and synced when it is saved
auto_create_schema = True
Going back to `settings.py`, we can now set `TENANT_MODEL`.::
TENANT_MODEL = "customer.Client" # app.Model
Now run `sync_schemas`, this will create the shared apps on the `public` schema. Note: your database should be empty if this is the first time you're running this command. *Never use* `syncdb` as it would sync *all* your apps to `public`!::
python manage.py sync_schemas
Lastly, you need to create a tenant whose schema is `public` and it's address is your domain URL. Please see the section on [Using django-tenant-schemas](#using-django-tenant-schemas).
South Migrations
================
This app supports `South <http://south.aeracode.org/>`_ so if you haven't configured it yet and would like to:
For Django 1.1 or below::
#SOUTH_DATABASE_ADAPTER = 'south.db.postgresql_psycopg2'
For Django 1.2 or above::
SOUTH_DATABASE_ADAPTERS = {
'default': 'south.db.postgresql_psycopg2',
}
You can list `south` under `TENANT_APPS` and `SHARED_APPS` if you want.
Optional Settings
=================
By default `PUBLIC_SCHEMA_URL_TOKEN` is set to `None`, which means you can't serve different views on the same path. To be able to have tenant URL routing see the section below.
Tenant View-Routing
-------------------
We have a goodie called `PUBLIC_SCHEMA_URL_TOKEN`. Suppose you have your main website at `example.com` and a customer at `customer.example.com`. You probably want your user to be routed to different views when someone requests `http://example.com/` and `http://customer.example.com/`. Because django only uses the string after the host name, this would be impossible, both would call the view at `/`. This is where `PUBLIC_SCHEMA_URL_TOKEN` comes in handy. If set, the string `PUBLIC_SCHEMA_URL_TOKEN` will be prepended to the request's `path_info` when the `public` schema is being requested. So for example, if you have::
PUBLIC_SCHEMA_URL_TOKEN = '/main'
When requesting the view `/login/` from the public tenant (your main website), this will be translated to `/main/login/`. You can now edit your `urls.py` file to use another view for a request incoming at `/main/login/`. Every time a call is made at the public's hostname, `/main` will be prepended to the request's path info. This is of course invisible to the user, even though django will internally see it at as `/main/login/`, the user will still be seeing `/login/`. When receiving a request to a tenant using the `public` schema, this token is added automatically via our middleware. Here's a suggestion for a `urls.py` file.::
# settings.py
PUBLIC_SCHEMA_URL_TOKEN = '/main'
# urls.py
urlpatterns = patterns('',
url(r'^main/$', 'your_project.public_urls'),
url(r'^', include('your_project.tenant_urls')),
)
Where `public_urls.py` would contain the patterns for your main website, which is not specific to any tenant and `tenant_urls.py` would contain all your tenant-specific patterns.
As you may have noticed, calling `reverse` or the `{% url %}` template tag would cause the wrong URL to be generated. This app comes with it's own versions for `reverse <https://github.com/bcarneiro/django-tenant-schemas/blob/master/tenant_schemas/urlresolvers.py>`_, `reverse_lazy <https://github.com/bcarneiro/django-tenant-schemas/blob/master/tenant_schemas/urlresolvers.py>`_ and `{% url %} <https://github.com/bcarneiro/django-tenant-schemas/blob/master/tenant_schemas/templatetags/tenant.py>`_ but don't worry, they don't do anything magical, they just remove `PUBLIC_SCHEMA_URL_TOKEN` from the beginning of the URL.
Import the `reverse` and `reverse_lazy` methods where needed.::
from tenant_schemas.urlresolvers import reverse, reverse_lazy
To use the template tag, add the following line to the top of your template file.::
{% load url from tenant %}
This should not have any side-effects on your current code.
Building Documentation
======================
Documentation is available in ``docs`` and can be built into a number of
formats using `Sphinx <http://pypi.python.org/pypi/Sphinx>`_. To get started::
pip install Sphinx
cd docs
make html
This creates the documentation in HTML format at ``docs/_build/html``.

19
docs/involved.rst Normal file
View File

@ -0,0 +1,19 @@
=============
Get Involved!
=============
Suggestions, bugs, ideas, patches, questions
--------------------------------------------
Are *highly* welcome! Feel free to write an issue for any feedback you have. :)
Multi-Threading
---------------
This is being used right now in production on a small project and I have made an attempt to make it thread-safe, but I'm a complete beginner at this subject. Any help on this would be *HIGHLY* appreciated. Can someone please check if the custom `postgresql_backend <https://github.com/bcarneiro/django-tenant-schemas/blob/master/tenant_schemas/postgresql_backend/base.py>`_ is thread-safe? If there is a way to write a test for this, it would be awesome. Please send in your feedback at `issue #2 <https://github.com/bcarneiro/django-tenant-schemas/issues/2>`_.
To-Do
-----
Take a look at `tenant_schemas/tests/schemas.py <https://github.com/bcarneiro/django-tenant-schemas/blob/master/tenant_schemas/tests/tenants.py>`_ and search for the string `todo`. Please send in your feedback at `issue #4 <https://github.com/bcarneiro/django-tenant-schemas/issues/4>`_.

25
docs/test.rst Normal file
View File

@ -0,0 +1,25 @@
==================
Tests
==================
Running the tests
-----------------
If you're using South, don't forget to set `SOUTH_TESTS_MIGRATE = False`.::
./manage.py test tenant_schemas
Updating your app's tests to work with tenant-schemas
-----------------------------------------------------
Because django will not create tenants for you during your tests, we have packed some custom test cases and other utilities. If you want a test to happen at any of the tenant's domain, you can use the test case `TenantTestCase`. It will automatically create a tenant for you, set the connection's schema to tenant's schema and make it available at `self.tenant`. We have also included a `TenantRequestFactory` and a `TenantClient` so that your requests will all take place at the tenant's domain automatically. Here's an example::
from tenant_schemas.test.cases import TenantTestCase
from tenant_schemas.test.client import TenantClient
class BaseSetup(TenantTestCase):
def setUp(self):
self.c = TenantClient(self.tenant)
def test_user_profile_view(self):
response = self.c.get(reverse('user_profile'))
self.assertEqual(response.status_code, 200)

60
docs/use.rst Normal file
View File

@ -0,0 +1,60 @@
===========================
Using django-tenant-schemas
===========================
Creating a Tenant
-----------------
This works just like any other model in django. The first thing we should do is to create the `public` tenant to make our main website available. We'll use the previous model we defined for `Client`.::
from customer.models import Client
# create your public tenant
tenant = Client(domain_url='my-domain.com', # don't add www here!
schema_name='public',
name='Schemas Inc.',
paid_until='2016-12-05',
on_trial=False)
tenant.save()
Now we can create our first real tenant.::
from customer.models import Client
# create your first real tenant
tenant = Client(domain_url='tenant.my-domain.com', # don't add www here!
schema_name='tenant1',
name='Fonzy Tenant',
paid_until='2014-12-05',
on_trial=True)
tenant.save() # sync_schemas automatically called, your tenant is ready to be used!
Management commands
-------------------
Every command runs by default on all tenants. To run only a particular schema, there is an optional argument called `--schema`. You can create your own commands that run on every tenant by inheriting `BaseTenantCommand`. There is also an option called `--skip-public` to avoid running the command on the public tenant.::
./manage.py sync_schemas
This is the most important command on this app. The way it works is that it calls Django's `syncdb` in two different ways. First, it calls `syncdb` for the `public` schema, only syncing the shared apps. Then it runs `syncdb` for every tenant in the database, this time only syncing the tenant apps. You should however never directly call `syncdb`. We perform some magic in order to make `syncdb` only sync the appropriate apps.
The options given to `sync_schemas` are passed to every `syncdb`. So if you use South, you may find this handy::
./manage sync_schemas --migrate
You can also use the option `--tenant` to only sync tenant apps or `--shared` to only sync shared apps.::
./manage.py migrate_schemas
This command runs the South's `migrate` command for every tenant in the database.
The options given to `migrate_schemas` are passed to every `migrate`. Hence
you may find::
./manage.py migrate_schemas --list
handy if you're curious. Or::
./manage.py migrate_schemas myapp 0001_initial --fake
in case you're just switching your `myapp` application to use South migrations.