added first example: an interactive tutorial for this app

This commit is contained in:
Bernardo Pires 2013-10-20 13:32:30 +02:00
parent a9241f2777
commit 4bbaf6fc9e
23 changed files with 653 additions and 0 deletions

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 2.7.3 (/usr/bin/python2.7)" project-jdk-type="Python SDK" />
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tenant_tutorial.iml" filepath="$PROJECT_DIR$/.idea/tenant_tutorial.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,5 @@
<component name="DependencyValidationManager">
<state>
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</state>
</component>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="tenant_tutorial/settings.py" />
<option name="manageScript" value="manage.py" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/templates" />
</list>
</option>
</component>
</module>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="" />
</component>
</project>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ChangesViewManager" flattened_view="true" show_ignored="false" />
<component name="CreatePatchCommitExecutor">
<option name="PATCH_PATH" value="" />
</component>
<component name="DaemonCodeAnalyzer">
<disable_hints />
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectReloadState">
<option name="STATE" value="0" />
</component>
<component name="RunManager">
<list size="0" />
</component>
<component name="ShelveChangesManager" show_recycled="false" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task" />
<servers />
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="VcsManagerConfiguration">
<option name="myTodoPanelSettings">
<TodoPanelSettings />
</option>
</component>
<component name="XDebuggerManager">
<breakpoint-manager />
</component>
</project>

View File

@ -0,0 +1,5 @@
from django import forms
class GenerateUsersForm(forms.Form):
pass

View File

@ -0,0 +1,8 @@
from django.db import models
from tenant_schemas.models import TenantMixin
class Client(TenantMixin):
name = models.CharField(max_length=100)
description = models.TextField(max_length=200)
created_on = models.DateField(auto_now_add=True)

View File

@ -0,0 +1,42 @@
from django.contrib.auth.models import User
from django.db.utils import DatabaseError
from django.views.generic import FormView
from customers.forms import GenerateUsersForm
from customers.models import Client
from random import choice
class TenantView(FormView):
form_class = GenerateUsersForm
template_name = "index_tenant.html"
success_url = "/"
def get_context_data(self, **kwargs):
context = super(TenantView, self).get_context_data(**kwargs)
context['tenants_list'] = Client.objects.all()
context['users'] = User.objects.all()
return context
def form_valid(self, form):
User.objects.all().delete() # clean current users
# generate five random users
USERS_TO_GENERATE = 5
first_names = ["Aiden", "Jackson", "Ethan", "Liam", "Mason", "Noah", "Lucas",
"Jacob", "Jayden", "Jack", "Sophia", "Emma", "Olivia", "Isabella",
"Ava", "Lily", "Zoe", "Chloe", "Mia", "Madison"]
last_names = ["Smith", "Brown", "Lee ", "Wilson", "Martin", "Patel", "Taylor",
"Wong", "Campbell", "Williams"]
while User.objects.count() != USERS_TO_GENERATE:
first_name = choice(first_names)
last_name = choice(last_names)
try:
User.objects.create_user(username=(first_name+last_name).lower(),
email="%s@%s.com" % (first_name, last_name),
first_name=first_name,
last_name=last_name).save()
except DatabaseError:
pass
return super(TenantView, self).form_valid(form)

View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tenant_tutorial.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en"><head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>{% block title %}{% endblock %}</title>
<style type="text/css">
html * { padding:0; margin:0; }
body * { padding:10px 20px; }
body * * { padding:0; }
body { font:small sans-serif; }
body>div { border-bottom:1px solid #ddd; }
h1 { font-weight:normal; }
h2 { margin-bottom:.8em; }
h2 span { font-size:80%; color:#666; font-weight:normal; }
h3 { margin:1em 0 .5em 0; }
h4 { margin:0 0 .5em 0; font-weight: normal; }
table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; }
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
thead th { padding:1px 6px 1px 3px; background:#fefefe; text-align:left; font-weight:normal; font-size:11px; border:1px solid #ddd; }
tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; }
ul { margin-left: 2em; margin-top: 1em; }
pre { background: white; padding: 10px; margin: 15px; border-left: 3px solid #006600; }
#summary { background: #EFFFE0; }
#summary h2 { font-weight: normal; color: #666; }
#footer { background:#eee; overflow: auto; }
#instructions { background:#f6f6f6; }
#summary table { border:none; background:transparent; }
</style>
</head>
<body>
<div id="summary">
{% block summary %}
{% endblock %}
</div>
<div id="instructions">
{% block instructions %}
{% endblock %}
</div>
<div id="footer">
<p style="float: left;">Current Tenant:
{% if request.tenant %}
<a href="http://{{ request.tenant.domain_url }}:8000">{{ request.tenant.name }}</a>
(<code>{{ request.tenant.schema_name }}</code>, <code>{{ request.tenant.domain_url }}</code>).
Available Tenants:
{% else %}
None.
{% endif %}
{% for tenant in tenants_list %}
<a href="http://{{ tenant.domain_url }}:8000">{{ tenant.name }}</a> &middot;
{% endfor %}</p>
<p style="float: right;">
<a href="https://github.com/bernardopires/django-tenant-schemas">django-tenant-schemas</a> &middot; <a href="https://django-tenant-schemas.readthedocs.org/en/latest/">Documentation</a>
</p>
</div>
</body></html>

View File

@ -0,0 +1,116 @@
{% extends 'base.html' %}
{% block title %}Tenant Tutorial{% endblock %}
{% block summary %}
<h1>Welcome to the Tenant Tutorial!</h1>
<h2>This interactive tutorial will teach you how to use <a href="https://github.com/bernardopires/django-tenant-schemas">django-tenant-schemas</a>.</h2>
{% endblock %}
{% block instructions %}
{% if need_sync %}
<h2>First Step: Sync your database</h2>
<p>Your database is empty, so the first step is to sync it. We only want to sync the <code>SHARED_APPS</code>.
For your convenience, here's the contents of <code>SHARED_APPS</code>:</p>
<ul>
{% for app in shared_apps %}
<li>{{ app }}</li>
{% endfor %}
</ul><br>
<p>Just run the command below on your shell to sync <code>SHARED_APPS</code>. Make sure your environment
has <code>Django</code> and <code>django-tenant-schemas</code> available.</p>
<pre>$ python manage.py sync_schemas --shared</pre>
<p>When you're done refresh this page.</p>
{% elif no_public_tenant %}
<h2>Second Step: Create a public tenant</h2>
<h3>So how does django-tenant-schemas work?</h3>
<p><code>django-tenant-schemas</code> uses the request's hostname to try to find a tenant.
When a match is found,
<a href="http://www.postgresql.org/docs/8.1/static/ddl-schemas.html">PostgreSQL's search path</a>
is automatically set to be this tenant.</p>
<br>
<p>For this request, <code>django-tenant-schemas</code> couldn't find any tenant for the current address (<code>{{ hostname }}</code>).
When no tenant is found, <code>django-tenant-schemas</code> normally returns a <code>404</code>, but since
this is a tutorial and no tenant exists yet, we let it proceed.
<h3>Recommended Tenant's URLs Structure</h3>
<p>Let's assume you have your main website at <code>trendy-sass.com</code>. The recommended structure is
to put your tenants at subdomains, like <code>tenant1.trendy-sass.com</code>,
<code>tenant2.trendy-sass.com</code> and so forth.</p>
<h3>Creating the public tenant</h3>
<p><code>django-tenant-schemas</code> requires a tenant for all addresses you use, including your main website,
which we will from now on refer to as the public tenant.</p>
<br>
<p>Our model is called <code>Customer</code> and looks like this (taken from <code>models.py</code>):</p>
<pre>
class Client(TenantMixin):
name = models.CharField(max_length=100)
description = models.TextField(max_length=200)
created_on = models.DateField(auto_now_add=True)</pre>
<p>Let's create a tenant for our main website, located at <code>{{ hostname }}</code>. Open up a shell and enter the <code>django</code> shell:</p>
<pre>$ ./manage.py shell</pre>
<p>To create a tenant run the following commands:</p>
<pre>from customers.models import Client</pre>
<pre>
Client(domain_url='{{ hostname }}',
schema_name='public',
name='Trendy SaSS',
description='Public Tenant').save()</pre>
<p>Done! <code>django-tenant-schemas</code> will now be able to locate our public tenant and won't return 404. Refresh this page to see the next step.</p>
{% elif only_public_tenant %}
<h2>Third Step: Create Tenants</h2>
<p>We've already created the public tenant, now it's time to create some tenants for subdomains. I assume you're running this on your local machine,
so the easiest way to simulate domains is to edit your <a href="http://en.wikipedia.org/wiki/Hosts_(file)"><code>hosts</code> file</a>.
<a href="http://www.rackspace.com/knowledge_center/article/how-do-i-modify-my-hosts-file">Here are instructions for all platforms</a>.
I'll assume you're on Linux.</p>
<pre>$ nano /etc/hosts </pre>
<p>Add the following lines:</p>
<pre>
127.0.0.1 tenant1.trendy-sass.com
127.0.0.1 tenant2.trendy-sass.com</pre>
<p>We're basically tricking our computer to think both <code>tenant1.trendy-sass.com</code> and <code>tenant2.trendy-sass.com</code> point to <code>127.0.0.1</code>.
Once you're done, try visiting <a href="http://tenant1.trendy-sass.com:8000">tenant1.trendy-sass.com</a>,
you should get a django <code>404</code>. As we have previously mentioned, we don't have a tenant there yet, so a <code>404</code> will be thrown.<br></p>
<br>
<p>We can now add tenants using these URLs and our project will be able to find them and identify them as our tenants. Back to the django shell:</p>
<pre>$ ./manage.py shell</pre>
<pre>from customers.models import Client</pre>
<pre>
Client(domain_url='tenant1.trendy-sass.com',
schema_name='tenant1',
name='Tenant1 - Awesome',
description='Our first real tenant, awesome!').save()</pre>
<p>Saving a tenant that didn't exist before will create their schema and sync <code>TENANT_APPS</code> automatically. You should see
the following lines as the result.</p>
<pre>=== Running syncdb for schema: tenant1
Creating tables ...
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)</pre>
<p>This means your tenant was installed successfully. Now create the second tenant.</p>
<pre>
Client(domain_url='tenant2.trendy-sass.com',
schema_name='tenant2',
name='Tenant2 - Even Awesome-r',
description='A second tenant, even more awesome!').save()</pre>
<p>Now try visiting <a href="http://tenant1.trendy-sass.com:8000">tenant1.trendy-sass.com</a> and
<a href="http://tenant2.trendy-sass.com:8000">tenant2.trendy-sass.com</a> or refresh this page.</p>
{% else %}
<h2>Tutorial Complete!</h2>
<p>Well done, you have completed the tutorial! Use the bottom menu to see your tenants.</p>
<h3>Where to go from here</h3>
<p>There are some interesting features that we did not cover.</p>
<ul>
<li><a href="https://django-tenant-schemas.readthedocs.org/en/latest/install.html#south-migrations">South Migrations</a>. This app supports <code>South</code> migrations.</li>
<li><a href="https://django-tenant-schemas.readthedocs.org/en/latest/install.html#tenant-view-routing">Tenant View-Routing</a>. Serve different views for the same path. (this tutorial makes use of this feature)</li>
<li><a href="https://django-tenant-schemas.readthedocs.org/en/latest/use.html#management-commands">Management Commands</a>. Run a command for a particular tenant.</li>
</ul>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,27 @@
{% extends 'base.html' %}
{% block title %}{{ request.tenant.name }}{% endblock %}
{% block summary %}
<h1>{{ request.tenant.name }}</h1>
<h2>{{ request.tenant.description }}</h2>
{% endblock %}
{% block instructions %}
<p>Each tenant is on a separate schema and contains different data. Take a look at the users list of this tenant.</p>
<h3>Users List</h3>
{% if users %}
<ul>
{% for user in users %}
<li>{{ user.first_name }} {{ user.last_name }}</li>
{% endfor %}
</ul>
{% else %}
<p>You don't have any users!</p>
{% endif %}
<h3>Generate Random Users</h3>
<p>Click the button below to generate five random users.</p><br>
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Generate Users" />
</form>
{% endblock %}

View File

@ -0,0 +1,32 @@
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db import connection
from django.http import Http404
from tenant_schemas.utils import get_tenant_model, remove_www_and_dev, get_public_schema_name
from django.db import utils
class TenantTutorialMiddleware(object):
def process_request(self, request):
connection.set_schema_to_public()
hostname_without_port = remove_www_and_dev(request.get_host().split(':')[0])
TenantModel = get_tenant_model()
try:
request.tenant = TenantModel.objects.get(domain_url=hostname_without_port)
except utils.DatabaseError:
request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
return
except TenantModel.DoesNotExist:
if hostname_without_port in ("127.0.0.1", "localhost"):
request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
return
else:
raise Http404
connection.set_tenant(request.tenant)
ContentType.objects.clear_cache()
if hasattr(settings, 'PUBLIC_SCHEMA_URLCONF') and request.tenant.schema_name == get_public_schema_name():
request.urlconf = settings.PUBLIC_SCHEMA_URLCONF

View File

@ -0,0 +1,173 @@
# Django settings for tenant_tutorial project.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@example.com'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'tenant_schemas.postgresql_backend', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'tenant_tutorial', # Or path to database file if using sqlite3.
'USER': 'postgres',
'PASSWORD': 'root',
'HOST': 'localhost', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
'PORT': '', # Set to empty string for default.
}
}
# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
ALLOWED_HOSTS = []
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/var/www/example.com/media/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://example.com/media/", "http://media.example.com/"
MEDIA_URL = ''
# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/var/www/example.com/static/"
STATIC_ROOT = ''
# URL prefix for static files.
# Example: "http://example.com/static/", "http://static.example.com/"
STATIC_URL = '/static/'
# Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'as-%*_93v=r5*p_7cu8-%o6b&x^g+q$#*e*fl)k)x0-t=%q0qa'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (
'tenant_tutorial.middleware.TenantTutorialMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.contrib.messages.context_processors.messages',
)
ROOT_URLCONF = 'tenant_tutorial.urls_tenants'
PUBLIC_SCHEMA_URLCONF = 'tenant_tutorial.urls_public'
# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = 'tenant_tutorial.wsgi.application'
import os
TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), '..', 'templates').replace('\\','/'),)
SHARED_APPS = (
'tenant_schemas', # mandatory
'customers', # you must list the app where your tenant model resides in
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
TENANT_APPS = (
# The following Django contrib apps must be in TENANT_APPS
'django.contrib.contenttypes',
'django.contrib.auth',
)
TENANT_MODEL = "customers.Client" # app.Model
INSTALLED_APPS = SHARED_APPS + TENANT_APPS
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}

View File

@ -0,0 +1,6 @@
from django.conf.urls import patterns
from tenant_tutorial.views import HomeView
urlpatterns = patterns('',
(r'^$', HomeView.as_view()),
)

View File

@ -0,0 +1,6 @@
from django.conf.urls import patterns
from customers.views import TenantView
urlpatterns = patterns('',
(r'^$', TenantView.as_view()),
)

View File

@ -0,0 +1,30 @@
from django.conf import settings
from django.db import utils
from django.views.generic import TemplateView
from tenant_schemas.utils import remove_www_and_dev
from customers.models import Client
class HomeView(TemplateView):
template_name = "index_public.html"
def get_context_data(self, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
hostname_without_port = remove_www_and_dev(self.request.get_host().split(':')[0])
try:
Client.objects.get(schema_name='public')
except utils.DatabaseError:
context['need_sync'] = True
context['shared_apps'] = settings.SHARED_APPS
context['tenants_list'] = []
return context
except Client.DoesNotExist:
context['no_public_tenant'] = True
context['hostname'] = hostname_without_port
if Client.objects.count() == 1:
context['only_public_tenant'] = True
context['tenants_list'] = Client.objects.all()
return context

View File

@ -0,0 +1,32 @@
"""
WSGI config for tenant_tutorial project.
This module contains the WSGI application used by Django's development server
and any production WSGI deployments. It should expose a module-level variable
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
this application via the ``WSGI_APPLICATION`` setting.
Usually you will have the standard Django WSGI application here, but it also
might make sense to replace the whole Django WSGI application with a custom one
that later delegates to the Django one. For example, you could introduce WSGI
middleware here, or combine a Django application with an application of another
framework.
"""
import os
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
# os.environ["DJANGO_SETTINGS_MODULE"] = "tenant_tutorial.settings"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tenant_tutorial.settings")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
# Apply WSGI middleware here.
# from helloworld.wsgi import HelloWorldApplication
# application = HelloWorldApplication(application)

View File

@ -20,6 +20,7 @@ setup(
'tenant_schemas.management',
'tenant_schemas.management.commands',
'tenant_schemas.templatetags',
'tenant_schemas.examples',
'tenant_schemas.test',
'tenant_schemas.tests',
],