Giter Site home page Giter Site logo

bernardopires / django-tenant-schemas Goto Github PK

View Code? Open in Web Editor NEW
1.4K 65.0 425.0 663 KB

Tenant support for Django using PostgreSQL schemas.

Home Page: https://django-tenant-schemas.readthedocs.org/en/latest/

License: MIT License

Python 99.94% HTML 0.06%

django-tenant-schemas's Introduction

django-tenant-schemas

PyPi version PyPi downloads Python versions Travis CI PostgreSQL

This application enables django powered websites to have multiple tenants via PostgreSQL schemas. 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.

Why schemas

There are typically three solutions for solving the multitenancy 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 excellent article on Multi-Tenant Data Architecture.

How it works

Tenants are identified via their host name (i.e tenant.domain.com). This information is stored on a table on the public schema. Whenever a request is made, the host name is used to match a tenant in the database. If there's a match, the search path is updated to use this tenant's schema. So from now on all queries will take place at the tenant's schema. For example, suppose you have a tenant customer at http://customer.example.com. Any request incoming at customer.example.com will automatically use customer's schema and make the tenant available at the request. If no tenant is found, a 404 error is raised. This also means you should have a tenant for your main domain, typically using the public schema. For more information please read the setup section.

What can this app do?

As many tenants as you want

Each tenant has its data on a specific schema. Use a single project instance to serve as many as you want.

Tenant-specific and shared apps

Tenant-specific apps do not share their data between tenants, but you can also have shared apps where the information is always available and shared between all.

Tenant View-Routing

You can have different views for http://customer.example.com/ and http://example.com/, even though Django only uses the string after the host name to identify which view to serve.

Magic

Everyone loves magic! You'll be able to have all this barely having to change your code!

Setup & Documentation

This is just a short setup guide, it is strongly recommended that you read the complete version at django-tenant-schemas.readthedocs.io.

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',
    #...
)

Add tenant_schemas.routers.TenantSyncRouter to your DATABASE_ROUTERS setting, so that the correct apps can be synced, depending on what's being synced (shared or tenant).

DATABASE_ROUTERS = (
    'tenant_schemas.routers.TenantSyncRouter',
)

Add tenant_schemas to your INSTALLED_APPS.

Create your tenant model

from django.db import models
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)

Define on settings.py which model is your tenant model. Assuming you created Client inside an app named customers, your TENANT_MODEL should look like this:

TENANT_MODEL = "customers.Client" # app.Model

Now run migrate_schemas to sync your apps to the public schema.

python manage.py migrate_schemas --shared

Create your tenants just like a normal django model. Calling save will automatically create and sync/migrate the schema.

from customers.models import Client

# create your public tenant
tenant = Client(domain_url='tenant.my-domain.com',
                schema_name='tenant1',
                name='My First Tenant',
                paid_until='2014-12-05',
                on_trial=True)
tenant.save()

Any request made to tenant.my-domain.com will now automatically set your PostgreSQL's search_path to tenant1 and public, making shared apps available too. This means that any call to the methods filter, get, save, delete or any other function involving a database connection will now be done at the tenant's schema, so you shouldn't need to change anything at your views.

You're all set, but we have left key details outside of this short tutorial, such as creating the public tenant and configuring shared and tenant specific apps. Complete instructions can be found at django-tenant-schemas.readthedocs.io.

django-tenant-schemas's People

Contributors

acmeguy avatar alexandervaneck avatar aniav avatar anyefreer avatar bdauvergne avatar bernardopires avatar caioariede avatar chrisfranklin avatar danh91 avatar filipeximenes avatar goodtune avatar hubbubca avatar jacobh avatar jjimenezlopez avatar jorl17 avatar kissgyorgy avatar m4l avatar mcanaves avatar mikicz avatar nazarewk avatar rctay avatar roelvdboom avatar sandeepbol avatar shabda avatar stephane avatar tonyo avatar tuttle avatar uadnan avatar viatrak avatar wm3ndez avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-tenant-schemas's Issues

App compatibility issues

Thanks for the good work! This will be a very useful project.

I've been trying out tenant-schemas, but have some questions about compatibility with existing django apps. I know it's impossible to say without auditing a particular app, but it would be nice to have a place in the wiki for known apps, issues, and possible workarounds.

Specifically I'm looking at django-guardian, with auth/users as a tenant app. From a brief code review, I don't see anything that would keep it from being used as a tenant app and it seems to be working in my test project. Good so far, but better to see experience of others to put my mind at ease!

Also, it would be nice to mention or include a caching backend that allows for separating tenant caches easily. I would assume cache collisions to be the biggest issue with 3rd party apps. Something simple like

class TenantMemcachedCache(MemcachedCache):
    def make_key(self, key, version=None):
        tenant = connection.get_tenant()
        return force_str(super(TenantMemcachedCache, self).make_key('%s-%s' % (tenant,key), version))

That wouldn't fix code level caching like ContribTypes, but hopefully those instances are rare.

python manage.py sync_schemas

Hi everybody!

This project is cool! , but I have some troubles for now. When I do "python manage.py sync_schemas" my server responses "Unknown command: 'sync_schemas'" anybody has the same problem?
In my manage.py available subcommands doesn't appear sync_schemas , and my settings.py Is correctly like information in readthedocs(Last).

I appreciate your comments! , good luck

Add support for Shared Applications

An application is considered to be shared when it's table are in the public schema. Some apps make sense being shared. Suppose you have some sort of public data set, for example, a table containing census data. You want every tenant to be able to query it.

Right now, this is not possible, at least not in practical way. By default all models are being synced to every schema, including public. Please take a look at the tenant-schemas needs your help! section if you have an idea on how to do this.

django-appschema tries to solve this in a very hackish and dangerous way by altering django's app cache. This is not safe for on-the-fly creation of tenants, so this is not an option. django-schemata partially solves it by forcing you to move your shared tables to the public schema. When syncing the tables for tenant-specific applications, the search path is set to public plus the tenant's schema, which means all tables that already exist on public will not be created when syncing. This is not ideal because it doesn't allow you have to applications that are both shared and tenant-specific. For example, you may need to have a user system for your main domain and another for your tenants. Or you may want to have south in both.

To enable this, an idea would be to let all models, both shared and tenant-specific to be synced. After the sync, the unnecessary models can be deleted from the database. There would be three arrays, SHARED_APPS, TENANT_APPS and MUTUAL_APPS. For example, when syncing the public schema, we can just iterate over TENANT_APPS deleting their tables. When syncing tenants, we delete the SHARED_APPS tables. We can then enable the tenants to also see the public schema. This is of course not very elegant, but shouldn't present a big hit on performance (how often do you sync your models?) and doesn't involve hacking django's cache.

An ever simpler solution would be if it were possible to select which models have to be synced. AFAIK this is not possible, syncdb is called for all models on INSTALLED_APPS.

What do you think of this solution? Do you have a better idea? Please send in your feedback!

How to create base data in tables of new tenant?

I tried a south datamigration which calls call_command("loaddata", "default_cities.json") and this works fine for tables that need migrating. But when creating a new tenant (read: schema) from scratch, of course it doesn't get run (South thinks nothing to migrate) so the data doesn't get populated.

I can run loaddata directly, but this would have to be done manually for each tenant addition

Any advice?
Cheers

Multiple sites per tenant.

I would like to enable support for django.contrib.sites for tenants. To do this I propose adding an extra model. I propose we call the model ClientSite and have a ForeignKey relationship to Client (or the abstract model it inherits from). What do you reckon? Does this sound feasible?

PUBLIC_SCHEMA_URLCONF

I was thinking about this for quite some time, but now in connection with #43 I could finally draft this idea:
Get rid of PUBLIC_SCHEMA_URLCONF setting, and in the documentation, suggest a solution with a different wsgi_app for the public website which point to a different settings.py (settings_public).

# wsgi_main_website.py
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings_public")

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

This would need to set up a new wsgi instance, but I don't think that's a problem and this would have the following benefits:

  • We can remove ContentType.objects.clear_cache() from the middleware, because this case, every schema would have the same app in contenttypes in the same place.
    This causes +1 DB Query in every request, but if the cache is never cleared, Django (contenntypes) can nicely build up a cache for the objects I think.
    I can't come up with a solution where PUBLIC_SCHEMA_URLCONF is in use and it's not needed to modify Django core to get rid of this behavior.
  • as @bcarneiro put the question in #43 :
    "What does your code do when I have auth and admin both on public and on tenant?"
    Does the user want shared apps referenced in public? Does he want public schema for the public website only and tenant apps without referencing public?
    It would be more clear what it means putting an app into both of the SHARED_APPS and TENANT_APPS; the user made a mistake :) or what would be even better I described in #43, to have a SHARED_MODELS setting.
  • Sometimes for the public website you want to have a CMS ex. http://www.django-cms.org, which want to take some control of the url routing because of dynamic pages, etc, and it would be very hard to integrate with this app.

I will do it myself for sure, because I think overall this is the right way to do it but would be nice to get some comments on this one, and I think others could benefit from this also.

Domain aliases for flexibility in schema selection.

My particular use-case may be on the edge, but I have a situation where I need the dynamic schema selection to occur on two or more domain names. For example, subdomain1.example.com and subdomain2.example.net could both use the same postgres schema. The solution I implemented was to:

  • Add a domain_aliases field to the TenantMixin as a TextField. Aliases would be entered as space separated values.
  • Alter the middleware to first check for the domain_url, but then also check for domains_aliases__contains.
  • An Http404 is still raised if neither is found.

One could implement the domain_aliases as an external table, but it seems like this simpler approach would work, and saves an additional relationship. I suppose my solution could be reduced to a single database call by using filter() once instead of get() twice, but the result would be less precise.

At any rate, I pushed this change into production these evening and it works great.

Would anyone like to see the code? Or receive a pull request?

django-tenant-schemas

Hello,

Thank you for writing this app - it is fantastic - exactly what I am in need of.

I am having a bit of difficulty getting integration with South working. I have followed your simple tutorial at https://django-tenant-schemas.readthedocs.org/en/latest/use.html - after setting everything up as stated in https://django-tenant-schemas.readthedocs.org/en/latest/install.html.

However, I am still confused with how I can use South and this app. I see the commands in the tutorial but cannot figure out the order in which I run them.

For example, I wish to add an additional field, 'additional_field', to the Client model. I add in the field, then I run ./manage.py migrate. But I receive a DatabaseError:

DatabaseError: column customers_client.additional_field does not exist
LINE 1: ...ent"."paid_until", "customers_client"."on_trial", "customers...

Am I missing something? Can someone provide a simple example as to how this can be rectified?

Many thanks.

Make sure this app is thread-safe

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 is thread-safe? If there is a way to write a test for this, it would be awesome.

How does Admin work with tenant-schemas?

Hi - Great package! It's really clean and easy to use so far. As I'm playing with it, I'm having some trouble getting the Admin stuff to work. I'm trying to setup Admin on the main tenant, but having some trouble with tenant-schemas attaching /main/ to the url. Also, the main admin be able to view data in the other schemas, or am I supposed to setup a superuser for each new tenant?

New architecture does not seem to allow centralized django.contrib.auth

It's been awhile since I inspected tenant_schemas. I've been using a branch successfully in my SaaS. I wanted to check out the latest commit. In doing so, however, the latest software seems to not work with my requirements.

I require that django.contrib.auth exists in public, since I want centralized authorization and billing (my stripe-based billing app needs to join in with the auth_user table). However, it seems that the latest tenant_schema code requires that auth also be placed in TENANT_APPS (in addition to optionally in SHARED_APPS). Otherwise, I get a DB error when doing sync_schemas. However, when I do that and successfully do a sync_schemas, my users (for which there exists an auth table in public) are not authorized to login into a tenant. I suppose I would need to somehow synchronize the user in each tenant with auth_user in public. But that would be too unwieldy.

I would like to use the latest commit. With my current branch of tenant_schemas (forked 9 months ago), whenever I do a syncdb, I need to delete tables in public that should only be in each tenant. Your latest commit seems to have a solution for that using sync_db.

Any thoughts?

Python 3 support

Hi,

Do you intend to porting the app to Python 3?

I have a Python 3 / Django 1.6 project and I decided to use your app. As I didn't find Python 3 port, I did it myself.

I only had to run: 2to3 -w django-tenant-schemas

Then, I changed setup.py:
from .version import get_git_version
to
from version import get_git_version

And version.py (get_git_version function):
return version[1:]
to
return str(version[1:]

And that was it! I'm using it without problems so far (at least I don't get errors caused by the port)

I suppose it won't be so much of a work to port to Python 3.

Alex

Can't update tenant outside the public schema.

There is a check if current schema is public in models.py:
http://github.com/bcarneiro/django-tenant-schemas/blob/master/tenant_schemas/models.py#L30

I wonder what is the purpose of this check?
Is it safe to simply:

if connection.get_schema() != get_public_schema_name():
    connection.set_schema_to_public()

The problem is when I create a new tenant from shell, it always set the same to the schema for that Tenant, but doesn't change it back, so I cannot create a new Tenant from that shell session. Is it ok to simply set schema to public?

How to get tenant id of loged-in user from shared-app ?

Dear bcarneiro
I really appreciate your masterpiece.

Dear All.

My Plan to use django-tenant-schema is to manage B2B relation, communication, transaction between tenants.

MyApproach is that the only Tenant-Specific will the standard auth-*
All other part will Shared-app.

This way I will need to flag each record on public model with tenant ID of loged-in user,

i.e, based on https://github.com/bcarneiro/django-tenant-schemas:
Let's say I want tenant will able to rent each other cars ... so 'car' will be in public app with :
class Car(models.Model):
client = models.ForeignKey(Client)
badge = models.CharField(max_length=10, blank=False, unique=True)

My Quetion is :

  1. How to get Client.id of loged-in user ?
  2. How to protect 'Client' model from being Create/Change/Delete by tenant's super-user ?

Kindly please give me any your enlightment

Sincerely
-bino-

Template tag url needs refactoring

Basically 100% of the code was copied from Django's source, just to be able to remove settings.TENANT_URL_TOKEN from the URL. There should be a smarter way to do this.

How to start discussion?

Bernardo -

This is not an issue perse, but more a question about communications about this project. What you are doing is very important for SaaS apps, and I have forked the code and started to explore it for my own SaaS development. There does not seem to be a place for communications on django-tenant-schemas, like a forum. Has there been one set up? I think some sort of forum could really generate a lot of interest and positive contributions.

I'm interested in using the code, but would like to get it to work with Django's built-in authorization/authentication. I've looked at Django simple multi-tenant and this blog: http://www.loggly.com/blog/2009/12/django-middleware-munging/ , so I think with a little effort, we can get some good authorization/authentication supported leveraging existing stuff.

Also, it would be great to use it with Django contrib.admin. With the current setup using your public token, the code will need some small work to enable that.

Thanks,

Ted ([email protected])

management commands missing

With a fresh install of 1.3.1, some management commands are missing.

I discovered this when issuing a tenant_command:

(venv)vagrant@precise64:/vagrant/club_sites$ ./manage.py tenant_command createsuperuser --schema=club2
Unknown command: 'tenant_command'

Running manage.py help:

(venv)vagrant@precise64:/vagrant/club_sites$ ./manage.py help

...

[tenant_schemas]
    migrate_schemas
    sync_schemas

'ImportError: No module named signals'

Hi

after getting the latest version, starting my project results in this error:

File "/var/www/simulator-build/activeliner/models/init.py", line 4, in
from client import Client
File "/var/www/simulator-build/activeliner/models/client.py", line 6, in
from tenant_schemas.models import TenantMixin
File "/srv/environments/simulator-build/local/lib/python2.7/site-packages/tenant_schemas/models.py", line 5, in
from tenant_schemas.signals import post_schema_sync
ImportError: No module named signals

I cannot find signals.py or a signals directory here, so I'm guessing some files have gone missing. Is that so?

And in other news, thank you for this great project!

'DatabaseWrapper' object has no attribute 'set_schema_to_public'

Hey,

Sorry to bother you, getting this error now with the latest version from pip:

Environment:


Request Method: GET
Request URL: http://localhost:8000/admin/

Django Version: 1.4.5
Python Version: 2.7.3
Installed Applications:
('tenant_schemas',
 'tart.core.accounts',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.admin',
 'tart.core.main_app',
 'tart.contrib.facebookext',
 'helpdesk',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.admin',
 'tart.contrib.facebookext',
 'helpdesk',
 'cms',
 'mptt',
 'menus',
 'sekizai',
 'cms.plugins.file',
 'cms.plugins.picture',
 'cms.plugins.teaser',
 'cms.plugins.video',
 'cms.plugins.flash',
 'cms.plugins.googlemap',
 'cms.plugins.link',
 'cms.plugins.text',
 'cms.plugins.twitter')
Installed Middleware:
('tenant_schemas.middleware.TenantMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.locale.LocaleMiddleware',
 'django.middleware.doc.XViewMiddleware',
 'django.middleware.common.CommonMiddleware',
 'cms.middleware.page.CurrentPageMiddleware',
 'cms.middleware.user.CurrentUserMiddleware',
 'cms.middleware.toolbar.ToolbarMiddleware',
 'tart.core.main_app.middleware.ListSitesMiddleware')


Traceback:
File "/home/chris/.virtualenvs/tartng/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  89.                     response = middleware_method(request)
File "/home/chris/.virtualenvs/tartng/local/lib/python2.7/site-packages/tenant_schemas/middleware.py" in process_request
  26.         connection.set_schema_to_public()
File "/home/chris/.virtualenvs/tartng/local/lib/python2.7/site-packages/django/db/__init__.py" in __getattr__
  34.         return getattr(connections[DEFAULT_DB_ALIAS], item)

Exception Type: AttributeError at /admin/
Exception Value: 'DatabaseWrapper' object has no attribute 'set_schema_to_public'

settings.py

DATABASES = {
    'default': {
        'ENGINE': 'tenant_schemas.postgresql_backend', 
        'NAME':  'dbname',                     
        'USER': 'user',                     
        'PASSWORD': 'REDACTED',       
        'HOST': 'localhost',                      
        'PORT': '',                   
    }
}

MIDDLEWARE_CLASSES = (
    'tenant_schemas.middleware.TenantMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.doc.XViewMiddleware',
    'django.middleware.common.CommonMiddleware',
    'cms.middleware.page.CurrentPageMiddleware',
    'cms.middleware.user.CurrentUserMiddleware',
    'cms.middleware.toolbar.ToolbarMiddleware',
    'tart.core.main_app.middleware.ListSitesMiddleware'
)

DJANGO_CMS_APPS = (
    # These are all for django-cms
    'cms',  # django CMS itself
    'mptt',  # utilities for implementing a modified pre-order traversal tree
    'menus',  # helper for model independent hierarchical website navigation
    'sekizai',  # for javascript and css management
    # These are plugins for django-cms
    #'cms_themes',
    # 'filer',
    # 'cmsplugin_filer_file',
    # 'cmsplugin_filer_folder',
    # 'cmsplugin_filer_image',
    # 'cmsplugin_filer_teaser',
    # 'cmsplugin_filer_video',
    'cms.plugins.file',
    'cms.plugins.picture',
    'cms.plugins.teaser',
    'cms.plugins.video',
    'cms.plugins.flash',
    'cms.plugins.googlemap',
    'cms.plugins.link',
    #'cms.plugins.snippet',  # Potential security hazard, see Snippet in django-cms docs
    'cms.plugins.text',
    'cms.plugins.twitter',
    )

SHARED_APPS = (
    'tenant_schemas',  # mandatory
    'tart.core.accounts', # you must list the app where your tenant model resides in

    # everything below here is optional
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.admin',

    'tart.core.main_app', # Main application
    'tart.contrib.facebookext', # Facebook extensions
    'helpdesk', # helpdesk with email support
)

TENANT_APPS = (
    # The following Django contrib apps must be in TENANT_APPS
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.admin',

    'tart.contrib.facebookext', # Facebook extensions
    'helpdesk', # helpdesk with email support

) + DJANGO_CMS_APPS

INSTALLED_APPS = SHARED_APPS + TENANT_APPS


TENANT_MODEL = "tart.core.accounts.Client" # app.Model

SOUTH_DATABASE_ADAPTERS = {
    'default': 'south.db.postgresql_psycopg2',
}

This is the contents of TENANT_MODEL (tart.core.accounts.client):

from django.db import models
from django.contrib.auth.models import User
from django.contrib import admin
from django import forms

import datetime
import os

class Base(models.Model):
    created_at  = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now_add=True, auto_now=True)

    class Meta:
        abstract = True

from tenant_schemas.models import TenantMixin
from django.contrib.auth.models import User

class Client(TenantMixin, Base):
    name = models.CharField(max_length=100)
    paid_until =  models.DateField()
    PLANS = (
        ('Free', 'Personal Plan'),
        ('Pro', 'Professional Plan'),
        ('Heavy', 'Heavy User Plan'),
        ('WhtLbl', 'White Labelled'),
    )
    plan = models.CharField(max_length=6,
                                      choices=PLANS,
                                      default='Free')
    user = models.ForeignKey(User, blank=True, null=True)
    on_trial = models.BooleanField(default=True)
    # default true, schema will be automatically created and synced when it is saved
    auto_create_schema = True

This is the output of sync_schemas --shared:

python manage.py sync_schemas --shared
=== Running syncdb for schema public
Creating tables ...
Installing custom SQL ...
Installing indexes ...
Installed 80 object(s) from 1 fixture(s)

DatabaseError when saving new Client

Hi there. So far I'm not able to get this app working for me. I'm mostly following the documentation here with a few organizational tweaks of my own (nothing major).

I'm running into a DatabaseError when I try to save a new (non-public) Client record:

django.db.utils.DatabaseError
DatabaseError: relation "django_content_type" does not exist
LINE 1: ..."."app_label", "django_content_type"."model" FROM "django_co...

Seems as though the tenant schemas aren't being properly related back to the public schema for access to django_content_type records.

Here's the code for my implementation, and here's full detail on the error I'm receiving when I try to save a new Client.

BaseException

Hi ! again.

My config : ubuntu server 12.04LTS clean install , postgresql 9.2 , django 1.5.1

when I perform : python manage.py sync_schemas --shared , script runs ok but suddenly breaks , output:

/usr/local/lib/python2.7/dist-packages/tenant_schemas/postgresql_backend/base.py:59: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
raise utils.DatabaseError(e.message)

anybody knows why?

Unknown Command: sync_schemas

Running into a problem when attempting to initially sync schemas. Keep getting error: Unknown command: 'sync_schemas'.. I've attempted to install django-tenant-schemas a few times through pip to no avail.

I don't believe all the folders are being synced correctly using pip. In fact, I believe I'm missing all of them, along with the management folder that houses all the commands... That would be the problem. heh

Extra SET calls

First, thanks for this project. Although I am not using it since I don't use postgres, I found it helpful when researching multi-tenancy options.

I believe I have the answer to one mystery noted in the middleware. There, each request gratuitously issues connection.set_schema_to_public() first, soon followed by connection.set_tenant(..).

When using devserver, which is multithreaded by default (unless --nothreading is specified), I noticed a single thread only ever services a single connection object, ie, a new thread is spawned per request, and is discarded at the end of the request.

However, other servers such as gunicorn keep worker pools, meaning a long-lived thread will service distinct connection objects throughout its lifetime.

Since Django closes connections at the end of the request (look for signals.request_finished in django.db.__init__), it means the connection state is effectively reset after a request -- even though the threadlocal data persists. Thus the threadlocal data (applied search paths) is out-of-sync with the true connection state (default for new connection).

In summary, I believe you should attach some thread local state to the connection, not just to the thread. Since connections are threadlocal in django unless allow_thread_sharing is set, it'd probably be safe to just set/test an attribute the connection (ie cursor.connection.applied_tenant = tenant). (In fact, it shouldn't matter if the connection is shared or not, since the applied search path is connection state regardless).

This should also help you avoid spurious SET calls when the tenant has already been set on a given connection.

Hope this helps!

Question about schema table population

Hi,

I haven't installed the multi-tenant package yet. I think perhaps it doesn't meet my requirements. If I understand correctly, the TENANT_APPS' tables are created in each of the tenant schemas. So, if I want to run a query that spans all of the data in all the schemas I would have to do a union, correct?

I had an idea. One thing you can do in postgres is inherit a table. So, for example, I can create my table in the public schema, then, when I want that same table for a tenant I could:

  1. create the tenant schema.
  2. create table tenant.mytable () inherits (public.mytable);

When the tenant table is separate, data inserted into that table cannot be accessed by another tenant. However, all data entered in a tenant schemas can be selected in the central 'public.mytable' table. Another neat side effect is when south updates the public table the tenant tables automatically. I tried this a bit, I am going to do some more work with this. What do you think about this approach?

---greg

Cloning data from default schema to tenant schema

Hey,

Firstly I would like to say very good work, I spent so long trying to make multi-tenancy work in a number of different ways over the last year and this app was absolutely seamless, you are absolutely amazing!

Just a quick one from me, do you know how I might clone data from the default schema to the tenant schema? Is there a command to change which schema you are currently using? I'll explain the problem briefly, I have django.contrib.auth as both a tenant app and a shared app. I would like to copy over the user that created the Client (therefore the schema) to the tenant django.contrib.auth as a superuser.

Also, I would like to help out in any way that I can, I figure a good way to start would be to get the docs into sphinx format and write some more, if I do this and submit a pull request would you be willing to setup http://readthedocs.com and set it to pull from this repo? Would this be helpful?

All the best,

Chris

Small typo in migrate.py

The error message:

migrate has been disabled, use migrates_schemas instead...

Should read:

migrate has been disabled, use migrate_schemas instead...

latest master raises django.core.exceptions.ImproperlyConfigured on install

$ pip install -e '[email protected]:bcarneiro/django-tenant-schemas.git@master#egg=django_tenant_schemas-dev'

yields:

Obtaining django-tenant-schemas from git+git@github.com:bcarneiro/django-tenant-schemas.git@master#egg=django_tenant_schemas-dev
  Updating /home/steve/virtualenvs/testcase/src/django-tenant-schemas clone (to master)
  Running setup.py egg_info for package django-tenant-schemas
    Traceback (most recent call last):
      File "<string>", line 16, in <module>
      File "/home/steve/virtualenvs/testcase/src/django-tenant-schemas/setup.py", line 10, in <module>
        from tenant_schemas import __version__
      File "tenant_schemas/__init__.py", line 3, in <module>
        from django.db.models.signals import post_delete
      File "/home/steve/virtualenvs/testcase/local/lib/python2.7/site-packages/django/db/__init__.py", line 11, in <module>
        if settings.DATABASES and DEFAULT_DB_ALIAS not in settings.DATABASES:
      File "/home/steve/virtualenvs/testcase/local/lib/python2.7/site-packages/django/conf/__init__.py", line 53, in __getattr__
        self._setup(name)
      File "/home/steve/virtualenvs/testcase/local/lib/python2.7/site-packages/django/conf/__init__.py", line 46, in _setup
        % (desc, ENVIRONMENT_VARIABLE))
    django.core.exceptions.ImproperlyConfigured: Requested setting DATABASES, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):

  File "<string>", line 16, in <module>

  File "/home/steve/virtualenvs/testcase/src/django-tenant-schemas/setup.py", line 10, in <module>

    from tenant_schemas import __version__

  File "tenant_schemas/__init__.py", line 3, in <module>

    from django.db.models.signals import post_delete

  File "/home/steve/virtualenvs/testcase/local/lib/python2.7/site-packages/django/db/__init__.py", line 11, in <module>

    if settings.DATABASES and DEFAULT_DB_ALIAS not in settings.DATABASES:

  File "/home/steve/virtualenvs/testcase/local/lib/python2.7/site-packages/django/conf/__init__.py", line 53, in __getattr__

    self._setup(name)

  File "/home/steve/virtualenvs/testcase/local/lib/python2.7/site-packages/django/conf/__init__.py", line 46, in _setup

    % (desc, ENVIRONMENT_VARIABLE))

django.core.exceptions.ImproperlyConfigured: Requested setting DATABASES, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.

A little googling reveals how the folks over at django-tracking worked around this issue:
https://bitbucket.org/codekoala/django-tracking/commits/f365cd2d59ef

I tried that approach and still had some problems, but I did notice that commenting out everything in tenant_schemas/__init__.py except the version number allows the install to complete.

That said, I do not like the django-tracking approach... it doesn't seem very pythonic. It's just a workaround to let you do things in places where you're apparently not supposed to do them.

Anyone have any thoughts?

Guide to Apache setup for deployment

It'd be great if there were documented somewhere (just in here for a start) the best way to set up a server deployment of a project, particularly on Apache.

For example, I've set up 'A record' routing with my domain hosting provider to route all traffic with '*' wildcard prefix, and with no prefix, to my server.

1st question, how best to handle www.mydomain.com so that it hits the public schema and doesn't try for a tenant url of 'www'. Is that a rewrite rule?

And more importantly, how best to setup the Apache config? I think it's with Virtual Hosts, and based off Bernardo's comment in #9 (comment) this can be wildcarded too.

Apache admin isn't a strength of mine, so while I'm confident I could get it working, this will be something that most everyone will need to do with this framework and some solid advice would be appreciated.

Thanks!

Tenant-only app South migration gets faked on tenant creation

I create my Shared schema initially:

bin/django sync_schemas --shared
bin/django migrate_schemas
...
from management.models import Client
tenant = Client(domain_url='localhost',
  schema_name='public',
  name='Schemas Inc.',
  on_trial=False)
tenant.save()

Then create a tenant:

from management.models import Client
tenant = Client(domain_url='tenant1.localhost',
  schema_name='tenant1',
  name='The Tenant1 Inc.',
  on_trial=False)
tenant.save()

This causes the middleware to create the new schema. But for my tenant-only app (fids_admin), the tables don't get created. Reason being the South migrations are faked:

=== Running migrate for schema: tenant1
Running migrations for fids_admin:
 - Migrating forwards to 0002_auto__add_field_flight_recurrences.
 > fids_admin:0001_initial
   (faked)
 > fids_admin:0002_auto__add_field_flight_recurrences
   (faked)

To work around this I delete all rows from tenantschema.south_migrationhistory then run:

bin/django migrate_schemas

Here's my apps config in settings:

SHARED_APPS = (

    'tenant_schemas',  # mandatory

    # everything below here is optional
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.admin',
    'django.contrib.staticfiles',

    'south',

    'management',
)

TENANT_APPS = (
    # The following Django contrib apps must be in TENANT_APPS
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.staticfiles',

    'south',

    # your tenant-specific apps
    'fids_admin',
    'recurrence',
)

Why would the tenant creation fake the migration steps?

tenant-schemas v1.3.1
South v0.7.6

Thanks

Test Project and Management command

Hi, First of all I appreciate such a useful and great project. I will like to send you a link to a test project (minimal Blog) I've put that can be useful for people interested in starting using your code.

Second: Are you willing to include a management command to create superusers in the schemas ? I think that can be useful in settings such as the one of blogs that use admin to enter data. In affirmative case I can put some time to contribute it.

regards,

python manage.py migrate_schemas

Hi!

My settings :
SHARED_APPS=(
'tenant_schemas',
'South',
.
.
.

now I add new app

'Tariff',
)
TENANT_APPS=(
'Core',# Core has a class with a attribute with foreingkey with newapp
'South',
)

When I perform :
$ python manage.py sync_schemas

the tenant schema that has name demo
=== Running syncdb for schema demo
Syncing...
Creating tables ...
Creating table Core_metering_element_data
Creating table Core_metering_element_tariff
DatabaseError: relation "Tariff_company_tariff" does not exist

Anybody knows why I can update my schemas?

Still got no luck using this masterpiece

Dear Bernardo and All

I Read and do all as mentioned at https://github.com/bcarneiro/django-tenant-schemas/blob/master/README.markdown , but still got no luck

my pastebin for :

my project name is 'mts1'
Whenever I make request to http://bino.int:8000/login , or http://cl1.bino.int:8000/login ..
I got exception , traceback at --> http://pastebin.com/0T0uGvdc

additionaly, I also read about create superuser at #24 , and I still don't know where is the BaseTenantCommand.

Kindly please give your enlighten to fix the problem

Sincerely
-bino-

Not Issue only a question

Hi!

How I can access to schema to save in the auth_user of my app a new user, when I create a new tenant?

Thanks

Test "test_edit_tenant" fails with clean implementation

Running

python manage.py test tenant_schemas

returns the following results:

=====================================================================
ERROR: test_edit_tenant (tenant_schemas.tests.tenants.TenantTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/var/virtual/test/local/lib/python2.7/site-packages/tenant_schemas/tests
/tenants.py", line 53, in test_edit_tenant
    tenant.save()
  File "/var/virtual/test/local/lib/python2.7/site-packages/tenant_schemas/model
s.py", line 23, in save
    raise Exception("Can't update tenant outside the public schema. Current sche
ma is %s." % connection.get_schema())
Exception: Can't update tenant outside the public schema. Current schema is test
.

======================================================================
FAIL: test_switching_search_path (tenant_schemas.tests.tenants.TenantTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/var/virtual/test/local/lib/python2.7/site-packages/tenant_schemas/tests
/tenants.py", line 96, in test_switching_search_path
    self.assertEqual(DummyModel.objects.count(), dummies_tenant1_count)
AssertionError: 5 != 2

----------------------------------------------------------------------
Ran 6 tests in 1.038s

FAILED (failures=1, errors=1)
Destroying test database for alias 'default'...

The actual client/tenant application used was the example project that I grabbed from
caioariede@943becd

Using the above example project, my settings are

SHARED_APPS = (
    'tenant_schemas',
    'customers',
)

TENANT_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'example_project',
)

INSTALLED_APPS = SHARED_APPS + TENANT_APPS

TENANT_MODEL = "customers.Client" 

Additional details:
Postgres 9.1 on Ubuntu 12.04 LTS x86
Django==1.5.1
django-tenant-schemas==1.1.3
psycopg2==2.5

Expected results: all tests should pass

Integration with contrib.sites

The way I use this app is this:

class Team(Site, TenantMixin):
    site = models.OneToOneField(Site, parent_link=True)

The problem is, that the built in django Site model already has a domain field, so when I do this, I got domain_url and domain fields on the Team model, which is redundant and broke DRY principle.
By renaming domain_url field to domain, the integration with sites framework would became seamless.
I also propose changing schema_name change to schema as that is ugly, unnecesary and I hate typing that much :) Also I think schema describe more precisely the purpose of this field.

With all these changes and the new shared feature we could make a new v.2.0 release, indicating the API breaking changes.
What you think about this @bernardopires?

ImproperlyConfigured error after updating to 1.0

I had everything working this morning then I updated to the pypi 1.0 version and ran into an error with the database engine:

(tenants)mike@ellie ~/sites/tenants $ ./manage.py validate
ImproperlyConfigured: 'tenant_schemas.postgresql_backend' isn't an available database backend.
Try using 'django.db.backends.XXX', where XXX is one of:
    u'mysql', u'oracle', u'postgresql_psycopg2', u'sqlite3'
Error was: No module named postgresql_backend.base

Doesn't work on django 1.4

ImportError: No module named six.moves when trying to create tenants. We need to either find a solution or only support Django 1.5+

Unit test runner doesn't respect shared vs. tenant apps, doesn't create tenant.

When splitting apps up into "shared" and "tenant" apps, running unit tests doesn't function as expected. I found the following:

  • When halting the unit test and inspecting the test database, tables for all TENANT_APPS were installed in the public schema. They shouldn't have been if they were split up according to tenant and shared apps.
  • TenantClient post() and get() requests get a 404 with the error "No Client matches the given query."
    • When halting the unit tests midway and inspecting the test database with postgres, it's true that no Clients exist in the customer_client table on the public schema.
    • This is counter to what I expected to happen, as the tenant.save() method hooks to create the new schema and calls sync_schemas. I don't understand why the object would not seem to be saved to the DB.
    • But when inspecting the response from the TenantClient's request, self.tenant is available and has an id.
    • To remedy this I added in "settings.TENANT_APPS += ('apps.customer',)" to the beginning of the setUpClass() method of TenantTestCase. With the customer app in the TENANT_APPS the tenant gets created. But we don't want to have the customer app in our tenant databases for production, because when we want to look up a customer, we want to be looking at what's on the public schema.

This is all pretty confusing to me...any thoughts on what's going on would be greatly appreciated! Would be happy to write a patch if I knew what was happening.

Thanks.

Unable to install django-tenant-schemas 1.4.0

I am trying to install version 1.4.0 of django-tenant-schemas, but it fails. Here's pip output:

(venv)ottavio@magritte:~/Projects/Miei/webcapm$ pip install django-tenant-schemas
Downloading/unpacking django-tenant-schemas
  Running setup.py egg_info for package django-tenant-schemas

    error: package directory 'examples' does not exist
    Complete output from command python setup.py egg_info:
    running egg_info

writing requirements to pip-egg-info/django_tenant_schemas.egg-info/requires.txt

writing pip-egg-info/django_tenant_schemas.egg-info/PKG-INFO

writing top-level names to pip-egg-info/django_tenant_schemas.egg-info/top_level.txt

writing dependency_links to pip-egg-info/django_tenant_schemas.egg-info/dependency_links.txt

warning: manifest_maker: standard file '-c' not found



error: package directory 'examples' does not exist

----------------------------------------
Command python setup.py egg_info failed with error code 1 in /home/ottavio/Projects/Miei/webcapm/venv/build/django-tenant-schemas
Storing complete log in /home/ottavio/.pip/pip.log
(venv)ottavio@magritte:~/Projects/Miei/webcapm$

in pip.log I have this additional information

Command python setup.py egg_info failed with error code 1 in /home/ottavio/Projects/Miei/webcapm/venv/build/django-tenant-schemas

Exception information:
Traceback (most recent call last):
  File "/home/ottavio/Projects/Miei/webcapm/venv/local/lib/python2.7/site-packages/pip-1.1-py2.7.egg/pip/basecommand.py", line 104, in main
    status = self.run(options, args)
  File "/home/ottavio/Projects/Miei/webcapm/venv/local/lib/python2.7/site-packages/pip-1.1-py2.7.egg/pip/commands/install.py", line 245, in run
    requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle)
  File "/home/ottavio/Projects/Miei/webcapm/venv/local/lib/python2.7/site-packages/pip-1.1-py2.7.egg/pip/req.py", line 1009, in prepare_files
    req_to_install.run_egg_info()
  File "/home/ottavio/Projects/Miei/webcapm/venv/local/lib/python2.7/site-packages/pip-1.1-py2.7.egg/pip/req.py", line 225, in run_egg_info

Should LICENSE.txt file have the txt extension?

I get these warning from setup.py :

Running setup.py egg_info for package django-tenant-schemas

warning: no files found matching 'LICENSE'
warning: no files found matching 'version.py'
warning: no files found matching 'VERSION'

Doc updates - buildout etc

Hi Bernardo

Some comments to aid the install docs, no issue here yet:).

Running under buildout requires:

unzip = true

Otherwise the extra django commands cannot be found by the buildout-generated bin/django script.

A dependency that needs to be included is: psycopg2

The docs suggest that SHARED_APPS and TENANT_APPS are optional, but they seem to be required?

I'll keep my experience notes coming in here if this is the best place?

Thanks, and look forward to running it for the first time soon!
Brad

Error after create a tenant

The schema is created and synchronized correctly, but after that, the following error occurs:

'NoneType' object has no attribute 'schema_name'
Traceback (most recent call last):
File "/home/jjimenez/venv/my_project/local/lib/python2.7/site->packages/jsonview/decorators.py", line 42, in _wrapped
ret = f(request, _a, *_kw)
File "/home/jjimenez/Work/my_project/src/my_project/customers/views.py", line 67, in save_quick_customer
customer.save(admin_email=admin_email)
File "/home/jjimenez/Work/my_project/src/my_project/customers/models.py", line 62, in save
admin_user.save()
File "/usr/lib/python2.7/contextlib.py", line 24, in exit
self.gen.next()
File "/home/jjimenez/venv/my_project/local/lib/python2.7/site->packages/django_tenant_schemas-1.3.1_12_g6899a7d->py2.7.egg/tenant_schemas/utils.py", line 25, in tenant_context
connection.set_tenant(previous_tenant)
File "/home/jjimenez/venv/my_project/local/lib/python2.7/site->packages/django_tenant_schemas-1.3.1_12_g6899a7d->py2.7.egg/tenant_schemas/postgresql_backend/base.py", line 107, in set_tenant
self.set_settings_schema(tenant.schema_name)
AttributeError: 'NoneType' object has no attribute 'schema_name'

Utilize Sites framework

Hi,

Did you consider using the sites framework for the tenants?
If possible then I think it would create a "cleaner" solution and play nice with other packages that use that framework.

Regards,
-Stefan

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.