Giter Club home page Giter Club logo

django-tenants's Introduction

django-tenants

Build status https://readthedocs.org/projects/pip/badge/?version=latest https://img.shields.io/pypi/dm/django-tenants.svg?maxAge=180 https://codecov.io/gh/django-tenants/django-tenants/branch/master/graph/badge.svg?token=wCNgSgTTR8

This application enables django powered websites to have multiple tenants via PostgreSQL schemas. A vital feature for every Software-as-a-Service (SaaS) website.

Read the full documentation here: django-tenants.readthedocs.org

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 its 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 its 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 its 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-tenants.readthedocs.org.

Your DATABASE_ENGINE setting needs to be changed to

DATABASES = {
    'default': {
        'ENGINE': 'django_tenants.postgresql_backend',
        # ..
    }
}

Add the middleware django_tenants.middleware.main.TenantMainMiddleware to the top of MIDDLEWARE, so that each request can be set to use the correct schema.

MIDDLEWARE = (
    'django_tenants.middleware.main.TenantMainMiddleware',
    #...
)

Add django_tenants.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 = (
    'django_tenants.routers.TenantSyncRouter',
)

Add django_tenants to your INSTALLED_APPS.

Create your tenant model

from django.db import models
from django_tenants.models import TenantMixin, DomainMixin

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)

class Domain(DomainMixin):
    pass

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
TENANT_DOMAIN_MODEL = "customers.Domain" # app.Model

Now run migrate_schemas. This will 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 the schema.

from customers.models import Client, Domain

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

# Add one or more domains for the tenant
domain = Domain()
domain.domain = 'tenant.my-domain.com'
domain.tenant = tenant
domain.is_primary = True
domain.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-tenants.readthedocs.org.

Running the example project

django-tenants comes with an example project please see

examples.

Credits

I would like to thank two of the original authors of this project.

  1. Bernardo Pires under the name django-tenant-schemas.
  2. Vlada Macek under the name of django-schemata.

Requirements

  • Django 2 if you want to use Django 1.11 or lower please use version 1 of django-tenants
  • PostgreSQL

Testing

If you want to run tests, you can either run run_tests.sh (which requires access to a PostgreSQL instance, location of which you can customize using the DATABASE_HOST env variable) or use docker-compose like this:

## Start Docker service
# start docker   # with Upstart
# systemctl start docker  # with systemd

## Install docker-compose (you might want to do this in Python virtualenv)
# pip install docker-compose

## In main directory of this repo do:
docker-compose run --rm django-tenants-test  # runs django-tenants tests.
# dockerized PostgreSQL service is started implicitly

(note that upon first run the Dockerfile will be built).

Video Tutorial

An online video tutorial is available on youtube.

Donation

If this project helped you reduce development time, you can give me cake :)

https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif

django-tenants's People

Contributors

adnathanail avatar adrienrusso avatar alexandernst avatar atodorov avatar bartonip avatar browniebroke avatar cclauss avatar dependabot-preview[bot] avatar dependabot[bot] avatar domdinicola avatar hanckmann avatar hexponent avatar hho6643 avatar ivome avatar jcass77 avatar kosz85 avatar kozlek avatar lorinkoz avatar mateuspadua avatar mikicz avatar mmcclelland1002 avatar perrohunter avatar pyup-bot avatar riconnon avatar robalar avatar sguermond avatar suriya avatar thenewguy avatar thomasmatecki avatar tomturner 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

django-tenants's Issues

Adding and removing a model from a tenant_app breaks the migrate_schemas

Hello guys,

Just testing here:

  1. I had a simple model definition on a tenant schema. It ran just fine.
  2. I added a new model and ran migrate_schemas (without --shared);
  3. Removed that model;
  4. Ran migrate_schemas again;
  5. Break;
Traceback (most recent call last):
  File "./manage.py", line 11, in <module>
    execute_from_command_line(sys.argv)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 338, in execute_from_command_line
    utility.execute()
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 330, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/base.py", line 393, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/base.py", line 444, in execute
    output = self.handle(*args, **options)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django_tenants/management/commands/migrate_schemas.py", line 46, in handle
    self.run_migrations(self.schema_name, settings.SHARED_APPS, options)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django_tenants/management/commands/migrate_schemas.py", line 64, in run_migrations
    command.execute(*self.args, **options)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/base.py", line 444, in execute
    output = self.handle(*args, **options)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/commands/migrate.py", line 225, in handle
    emit_post_migrate_signal(created_models, self.verbosity, self.interactive, connection.alias)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/core/management/sql.py", line 280, in emit_post_migrate_signal
    using=db)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/dispatch/dispatcher.py", line 201, in send
    response = receiver(signal=self, sender=sender, **named)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/contrib/contenttypes/management.py", line 81, in update_contenttypes
    ct.delete()
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/models/base.py", line 871, in delete
    collector.collect([self])
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/models/deletion.py", line 228, in collect
    elif sub_objs:
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/models/query.py", line 170, in __nonzero__
    return type(self).__bool__(self)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/models/query.py", line 166, in __bool__
    self._fetch_all()
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/models/query.py", line 965, in _fetch_all
    self._result_cache = list(self.iterator())
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/models/query.py", line 238, in iterator
    results = compiler.execute_sql()
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 840, in execute_sql
    cursor.execute(sql, params)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 79, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/utils.py", line 97, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/george/projetos/.virtualenvs/greentape/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: relation "auth_permission" does not exist
LINE 1: ...ntent_type_id", "auth_permission"."codename" FROM "auth_perm...

Incorrect migration when limiting set search_path calls

As originally reported by @bjersey for Issue #253 in the django-tenant-schemas project:

When setting TENANT_LIMIT_SET_CALLS = True, we are observing interesting (and incorrect) behavior when migrating schemas. When this value is set, we're seeing various errors in migrate_schemas (both when creating new tenants AND just running migrate_schemas directly).
To reproduce one of the issues:

Create a tenant (all migrations should be faked and properly saved into this tenant's history properly at this point)
Create a new empty schema migration for a tenant app
Try to apply the migration
When trying to migrate the tenant app, you should see a South error about no migrations for a PUBLIC app.

Any help is appreciated. Thanks!

Then from @philfriesen:
@bernardopires Hi Bernardo. We've been using tenant schemas successfully here at The Advisory Board Company. Thanks! We created the original PR for TENANT_LIMIT_SET_CALLS to improve DB performance. We later discovered that this broke migrations as my teammate Brian documented above. We hacked a workaround, but we think we owe you a new PR. We're having some difficulty locating the area of code we need to change to fix the migration problem our original PR created. Can you point us in the right direction? Thanks! --Phil.

@bernardopires @bjersey: Here's some more info Brian ( @bjersey ) gave me:
Summary of the issue: running the migrate_schemas command (either directly or when a new tenant is created) results in errors or incorrect south_migrationhistory records
o The problem manifests itself in two ways:
 Creating a new tenant fails; upon creating the tenant the migrations are faked, but we see an error about the tenant not having migrations for a PUBLIC app
 Upon creating a new tenant, the public.south_migrationhistory table has history of migrations from tenant apps, and the tenant’s south_migrationhistory table doesn’t have all the records that it should
o The difference in observing the two different errors are whether or not the public.south_migrationhistory table is empty or not before creating the tenant; if it’s properly populated, we observe the first error, if it’s empty then we see the second
• We are able to avoid all of these issues if we run migrate_schemas (or create new tenant) with TENANT_LIMIT_SET_CALLS = False
• From our understanding, this setting just dictates whether or not the Postgres search path is set upon every database transaction

Accesing tenant schema_name on models layer.

Hi, first of all not sure if "Issues" section is the correct way to post this, since this is not an issue at all just asking an open question for opinions.

I am migrating a django project to django tenants, we have several years working as SaaS in a traditional way using basically one site per customer.

We put the equivalent to "tenant.schema_name" in settings.py since each customer works on a site with its own settings file so any time I needed to use it I'l just get it from django.conf.settings. How ever since django tenants has this on request it isn't available on models layer.

Our current working solution is to pass schema_name as a parameter every time a model function that needs this info is called, but I am wondering other options about this, for example storing schema_name in the models which need it.

How do you commonly deal with this?

Set a TIME_ZONE setting per tenant

Hi, is there a way to establish the TIME_ZONE setting for each tenant?

According to django documentation I could use USE_TZ=True and then change dynamically the setting, but I would have to worry about naive and aware dates when performing operations.

How do you (everybody here) manage this setting?

Thanks for the project maintenance and help.

Difference between shared and tenants apps in testing

Hello everyone,

I might have not see anything like this in the docs, but I checked and I'm not sure.

I have a custom method to create my tenants, which I'm trying to test it. There are things that I need to do to set the client/tenant and I rather not have this scattered all over.

So, I basically create a new client, calculate it's domain based off some settings, create the domain and then I modify the default Site record (SITE_ID = 1) so it's domain matches the domain we created.

This is the method:

    def criar(self, nome, cpf_cnpj, email, dominio=None):
        '''Função wrapper que contém a lógica da criação de um novo
        cliente.'''

        if not self.validador_documento.e_valido(cpf_cnpj):
            raise ValidationError(_(u'CPF ou CNPJ inválido: %(value)s'),
                                  params={'value': cpf_cnpj},
                                  code="cpf_cnpj_invalido")

        cliente = Cliente()
        cliente.nome = nome
        cliente.cpf_cnpj
        cliente.email = email
        cliente.save()

        dominio = Dominio()
        if dominio:
            dominio.domain = self._calcular_dominio_cliente(dominio)
        else:
            dominio.domain = self._calcular_dominio_cliente(cliente.nome)

        dominio.tenant = cliente
        dominio.is_primary = True
        dominio.save()

        site = Site.objects.get(pk=1)
        site.name = cliente.nome
        site.domain = dominio.dominio_completo()
        site.save()

        return cliente

When auto_create_schema = True, there is some problem when I hit the cliente.save() line (cliente = tenant). It seems that django-tenants does not create the new schema.

Inside the test, I've tried also, to set (just for the test) auto_create_schema = False. This gives me an error, saying that the Site table does not exists. Of course, django just copied the database definition so it can be run inside the test, and Site is just a TENANT_APP, not a shared one.

Any thoughts on how we can make this happen? I don't care to set auto_create_schema=False for the tests, I just want to make it work. Also I rather not have to maintain a separate test database.

Thanks

tenant_commands are not executing

runing something as simple as python manage.py tenant_command check is always returning error:

usage: manage.py tenant_command [-h] [--version] [-v {0,1,2,3}]
                                [--settings SETTINGS]
                                [--pythonpath PYTHONPATH] [--traceback]
                                [--no-color] [-s SCHEMA_NAME]
manage.py tenant_command: error: unrecognized arguments: check

Cannot create Tenants via Admin in 1.1.2

When adding a new Tenant via the default admin, the view processing is decorated with "transaction.atomic". If a postgres connection is closed while in a transaction, the server performs an automatic rollback. There is no exception thrown, nor error shown. The Tenant simply isn't created.

The connection is closed in migration_executors/base.py, lines 30-31. Commenting out these two lines seems to make adding a new Tenant work as expected.

I replicated this using the dts_test_project, adding this admin file:

#customers/admin.py:

from customers.models import Client
from django.contrib import admin

class ClientAdmin( admin.ModelAdmin ):
    pass

admin.site.register( Client, ClientAdmin )

Guidelines on using django-tenants in a secure manner

Are there any guidelines on using django-tenants in a secure manner?

I can think of at least one issue by which using django-tenants could lead to a security vulnerability. By having 'django.contrib.sessions' in SHARED_APPS, can't a user in one sub-domain/tenant impersonate a user in some other sub-domain/tenant?

While I am not sure if my guess is correct, we should have a section on security in the documentation and also have checks in the code that will catch insecurely configured projects.

Unintuitive output of migration management commands

Various management commands related to migrations, such as ./manage.py migrate --list or ./manage.py migrate_schemas list all migrations in all schemas. When I went under the hood, I came to understand that only migrations to models present in a tenant are applied (even through the django_migrations table in all schemas lists migrations from all models).

While listing or applying migrations, it would be nice if the output specifies which migrations will actually be applied. If that is something too hard, a message specifying that only a subset of the listed apps will be migrated would be very useful.

Any ideas on having tenant and shared authentication?

It's rather hard to formulate it in a title, so let me try to describe it instead:

I have a public app where tenants can sign up, which redirects to the private tenant app after registration. In this private tenant app they can create users for that tenant.

I'd like to provide a global support/user forum across all tenants. Do you guys have any idea on how to implement this? Architecture, risks of duplicate usernames, etc.

Thanks!

Apps do not migrate when a project uses a submodule for applications.

Had a small issue when trying to use the latest version of django tenants:

On my settings file:

SHARED_APPS = (
    'django_tenants',

    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.admin',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'apps.nvccommunities',
    'apps.nvcusers',
)

TENANT_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.admin',
    'django.contrib.sessions',
    'django.contrib.messages',

    # utils
    'rest_framework',

    'apps.nvcusers',
    'apps.nvcmembers',
    'apps.nvcadmin',
    'apps.nvcimages',
    'apps.nvcpages',
    'apps.nvcposts',
    'apps.nvccomments',
    'apps.nvcexperts',
    'apps.nvcsettings',
    'apps.nvccore',
    'apps.nvcfrontend',
    'apps.nvcdev',
    'apps.nvcdocs',
    'apps.nvctasks',
    'apps.nvcmoderation',
)

INSTALLED_APPS = tuple(set(SHARED_APPS + TENANT_APPS))

pip install django-tenants==1.1.2
python manage.py test -v3 --noinput

...
  File "/xyz/local/lib/python2.7/site-packages/django/db/utils.py", line 97, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/xyz/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: relation "nvcusers_user" does not exist

On django_tenants/routers.py when checking if the app is installed, it is not covering the case of submodules. It works fine on version 1.0.2.

Eg: looks for 'nvcusers' when 'apps.nvcusers' exists on settings.TENANT_APPS.

Thanks,
Salvador

How to test this on localhost?

Hi Tom,

I'm so glad that you continue the project and I got a very simple project up and running in Django 1.8.

BUT:
How do I test multiple tenants with djangos runserver? How to I set up the domains?
Or shall I install a local development server other then runserver? What do you recon?

Kind regards
Mikey

Is possible to have a super_tenant?

Hi, my name is Milton Lenis from Colombia, i'm member of CEDESOFT Team and we are working so hard with this tool.

We need to have a kind of "super_tenant" that can run queries (with django models) on all the other tenants, like a big reporter. We are thinking right now on strategies to do it, but i wonder if there is a solution directly with django-tenants.

Thank you for your support, cheers from Colombia.

tenant -> shared app

I have an app that is listed in shared and tenants in the settings.py but it isn't showing up as shared when I add an object in one tenant (I don't see it in the other tenant). It could be that I added the model to SHARED_APPS too late, but even when I do a makemigrations it doesn't pick up a change to that app (ie that it should be shared).

So... is there a way to make this model actually shared given it is in the SHARED_APPS list but doesn't appear to be shared?

Or, is there something else I can check to see where the problem might lie (probably with me).

Thanks!

axes_reset management command not called on all tenants

Hi guys,

I just set up a first test environment.

In my application I use a module called django-axes for security. It logs wrong login entries and bans IPs.
Anyway:
in my settings.py I got

SHARED_APPS = (
    'django_tenants', # ...
    'axes',  # enable security for my public tenant
)

TENANT_APPS = (
    'axes',  # enable security for my private tenants
)

after a

python manage.py makemigrations; python manage.py migrate_schemas

each tenant (1public plus 2 private) has its own wrong-login-attempts tables, and the banning itself works fine.

BUT:
when I then run the associated manamgenet command

python manage.py axes_reset

ONLY the public app table is resetted.

I also tried a

python manage.py tenant_command axes_reset

which just gives me the following error:

usage: manage.py tenant_command [-h] [--version] [-v {0,1,2,3}]
                                [--settings SETTINGS]
                                [--pythonpath PYTHONPATH] [--traceback]
                                [--no-color] [-s SCHEMA_NAME]
manage.py tenant_command: error: unrecognized arguments: axes_reset

Any ideas whats wrong? How do I run this command on ALL tenants?

ContentType question

This might be related to a question just recently asked but I wanted to give specific context to my question.

I have content types in TENANT_APPS in my settings.py file:

TENANT_APPS = (
    'django.contrib.contenttypes',

I have approximately 20 tenants and am trying to set a content_type variable in a Tag to a specific content_type. There were issues, so I boiled it down to:

from customer.models import Customer
from django.db import connection
from django.db.models import get_model
from django.contrib.contenttypes.models import ContentType
tenants = Customer.objects.exclude(schema_name="public")

for tenant in tenants:
    if not 'public' in tenant.schema_name:
        print('---------------------------------------------')
        connection.set_tenant(tenant)

        # Add the call tags
        app, model = 'call.Call'.split('.')
        class_object = get_model(app, model)
        print('class object {}'.format(class_object))

        ct = ContentType.objects.get_for_model(class_object)
        print('CT {} and CT.id {}'.format(ct, ct.id))

        cts = ContentType.objects.filter(model__icontains='Call')
        print('CTS: {}'.format([(x.id, x) for x in cts if x.name=='Call']))

When I run the above code I get the output below. What I am confused by is why the CT.id is always "24" from the get_for_model does not match the id when I do the ContentType.objects.filter call. The last one is what I would expect is correct.

class object <class 'call.models.Call'>
CT Call and CT.id 24
CTS: [(90, <ContentType: Call>)]
---------------------------------------------
class object <class 'call.models.Call'>
CT Call and CT.id 24
CTS: [(55, <ContentType: Call>)]
---------------------------------------------
class object <class 'call.models.Call'>
CT Call and CT.id 24
CTS: [(26, <ContentType: Call>)]
---------------------------------------------
class object <class 'call.models.Call'>
CT Call and CT.id 24
CTS: [(17, <ContentType: Call>)]
---------------------------------------------
class object <class 'call.models.Call'>
CT Call and CT.id 24
CTS: [(60, <ContentType: Call>)]
---------------------------------------------
class object <class 'call.models.Call'>
CT Call and CT.id 24
CTS: [(1, <ContentType: Call>)]
---------------------------------------------
class object <class 'call.models.Call'>
CT Call and CT.id 24
CTS: [(88, <ContentType: Call>)]


I am using django 1.8, Python 3.4 and django-tenants 0.9.1 (yes, slightly older).

New repo?

Hi Tom,
I see you set up a new repo for this again. Weren't you happy with us hosting it?
It's all fine with me, but we really want to contribute to this as well.
Should we fire pull request against this or did you plan to use the django-multi-tenancy repo later?
Cheers,
Loek

Transaction error when creating new tenant while celery backgroud tasks are being executed for other tenants

Hi, I'm getting this when creating a new tenant if some celery tasks are being executed:
InternalError: current transaction is aborted, commands ignored until end of transaction block

I already have the latest version (master), which already have this PR merged:
CGenie@e5b8408

I tought this would solve the problem but apparently not. My system is very heavy and I have tenants that have background celery tasks being executed all the time, one task per tenant (each tenant has its own task)... this tasks connect to a webservice to download and save objects on the tenant schema (dozens of objects per second per tenant). One of these objects are very nested, composed by over 20 classes and, because of that, I save it in a transaction.atomic() block to ensure everything is created properly.

Now, this is IMPORTANT: If I modify my celery task and remove the transaction.atomic() when saving this object, my task still works (subject to integrity errors, of course) and I CAN CREATE THE TENANT without the error, as expected... so, apparently, there is some conflict when creating a tenant while atomically saving objects in other teants...

See also the sentry captured event that describes it in more detail:
error

Explicit API to set and query the tenant

At present, it looks like the tenant can be set in three ways:

  1. ./manage.py tenant_command which sets the tenant in django_tenants.management.commands.tenant_command.Command.handle
  2. The user invoking the django_tenants.utils.tenant_context context manager
  3. Most importantly, for each request by django_tenants.middleware.TenantMiddleware.

The method to know the current tenant seems to be accessing connection.tenant in all three situations or request.tenant in the third case.

It would be good to explicitly document and provide an API to query the current tenant. connection.tenant does not seem to be a good method because tenants can be used for more than just deciding the database schema path. The use of request.urlconf based on the requested tenant is already an example. Other users might have their own uses for tenant specific logic.

My suggestion is to create a class that provides an authoritative API to set and get the tenant. connection.tenant and request.tenant are simply convenient ways to get the current tenant.

Running tests

I've installed this app in a django 1.8 project, after I configure the project, i try run tests with python manage.py test and got this error:

django.db.utils.ProgrammingError: relation "auth_user" does not exist

How can I run django tests?

Types of tenants proposal

Hi guys.
In a SaaS architecture can be desirable to have a kind of tenant's types or clasificación.

Inconsistency with multiple domain_urls breaks whole page

We have the problem, that the domain_urls values are not consistent.
The CharField of domain_urls has unique=true, but that only works for single values.

For example I can create multiple tenants with the same domain, which is pretty bad and breaks the whole page:

tenant = Client(domain_urls=['tenant.my-domain.com', 'other.my-domain.com'],
                schema_name='tenant1',
                name='My First Tenant',
                paid_until='2014-12-05',
                on_trial=True)
tenant.save()

# Same domains reverse order does not throw exceptions
tenant = Client(domain_urls=['other.my-domain.com', 'tenant.my-domain.com'],
                schema_name='tenant1',
                name='My First Tenant',
                paid_until='2014-12-05',
                on_trial=True)
tenant.save()

I would vote to remove the option of multiple domain_urls and make it a single value again. That way we can properly build distinct reverse URLs that include the domain name, for example for confirmation emails etc. We can also properly create an index on the domain (which is lacking right now). If someone needs support for multiple domains, the middleware could be easily extended or it could be handled with a redirect to avoid duplicate content.

Any opinions?

BOUNTY: foreign key constraint violation with custom Auth User Model

I have a custom User model through django-authtools that looks like this:

shared_usermodel/model.py

import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from authtools.models import AbstractEmailUser

from django.conf import settings

class User(AbstractEmailUser):
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, blank=True)
    first_name = models.CharField('first name', max_length=255, blank=True)
    last_name = models.CharField('first name', max_length=255, blank=True)

    def __str__(self):
        return "%s" % (self.email)

    def __repr__(self):
        return self.__str__()

    def __unicode__(self):
        return u'%s' % (self.email)

and a Model in one of my webapps:

from django.db import models
from django.conf import settings

class Leave(models.Model):
    title = models.CharField(max_length=100)
    leave_owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True)

    def __str__(self):
        return "%s" % (self.title)

    def __repr__(self):
        return self.__str__()

    def __unicode__(self):
        return u'%s' % (self.title)

When I try to set a leave_owner, I get:

insert or update on table "webapp_core_leave" violates foreign key constraint "web_leave_owner_id_2ffe88b0e46ab4a6_fk_shared_usermodel_user_id"
DETAIL:  Key (leave_owner_id)=(2) is not present in table "shared_usermodel_user".

Any idea why this happens? I didn't used to have this issue with django-tenant-schemas.

Thanks!

Ps: my settings.py:

SHARED_APPS = (
    # 3rd Party
    'django_tenants',  # mandatory

    # Core
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',

    # CUSTOM USER MODEL
    'authtools',
    'shared_usermodel',

    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    'django.contrib.admindocs',
)

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

    # CUSTOM USER MODEL
    'authtools',
    'shared_usermodel',

    'webapp_core', # this holds the Leave Model
)

AUTH_USER_MODEL = "shared_usermodel.User" 
INSTALLED_APPS = list(set(TENANT_APPS + SHARED_APPS))

relation "customers_client" does not exist

Im getting this error whenever I try to save a tenant.
I'm using Django 1.9 with python 3

django.db.utils.ProgrammingError: relation "customers_client" does not exist
LINE 1: INSERT INTO "customers_client" ("schema_name", "name", "desc...
I have try the tutorial and it seems to work, but i cannot make it work outside of it.

Feature-Request: Change tenant-domain from subdomain to url-parameter?

Hi guys,

I am just thinking: One of the big benefits of this app is the added security layer via database-schemas.
So users are probably quite security concerned and will also invest in an ssl-certificate.

Which is quite expensive for a startup... I think unlimited ssl-subdomains-certificates are around 500$ per year?

Which brings me to the point:
How easy do you think would it be to implement a switch "DOMAINTYPE = SUBDOMAIN/URL-PARAMETER"?

So that instead of

tenant.domain.com 

we would see

www.domain.com/tenant/*?

Add all Database Introspection

At the moment there is only one routine that get is in django-tenants introspection however in postgresql version there are lots i.e.

get_indexes
get_table_description

(#257)

Travis Integration

Tests are nice, automatic tests are better, and integrated with github are great :)

create_superuser not working correctly

Hi guys,

it seems like create_tenant_superuser does NOT create a user in the schema, but in the public table.

When I enter the following command for the first time a user is created - globally as it seems:

./manage.py create_tenant_superuser --username='bobby' --schema=tenant1

when I then do another one for my tenant2

./manage.py create_tenant_superuser --username='bobby' --schema=tenant2

it gives me a

django.db.utils.IntegrityError: duplicate key value violates unique constraint "auth_user_username_key"
DETAIL:  Key (username)=(bobby already exists.

Even worse:
I can now log into tenant2 as bobby, althogh it has NOT been created.

Strange stuff!

Can you reproduce this?

Accumulative QuerySet

Hello!

I am using the component to build a multi-tenant application.

I am trying to collect all tenants information making an accumulative queryset using the method:

for t in tenants:
connection.set_tenant(entidad)
ContentType.objects.clear_cache()
query = query | Class.objects.filter(some attr)

When loop change and tenant set to other one the queryset will execute in the current tenant , It is because of lazy django querysets however I thinks that you can have the same problem and maybe could have the solution.

I need the query sets because the filter method and other powerful functionalities of it

Selenium Tests? StaticLiveServerTestCase?

Hi guys,

I just tried to get selenium running against django-tenants and guess what? It does not work.

The problem seems to be that (when inheriting from StaticLiveServerTestCase) it will run a testserver on port:8001 and I just get a page-not-available when calling the tenants test-url.

Does anyone have a running selenium-test? If yes: how did you do it? :-)

Problem with Tenant specific user groups

Hi Everyone,

I'm having a difficult time trying to get django-tenants to play nicely in my app and I'm wondering if someone can point me in the right direction.

What I am trying to do:

  • Use django-tenants to provide schema separation for tenants
  • Use django-userena and django-guardian for user accounts and permissions respectively
  • Have users be held within the tenant schema and not in the public schema (i.e. each tenant has their own user pool)

What I have tried:

I have tried putting django.contrib.auth, userena and guardian in the SHARED_APPS setting. This works but results in a single user model that spans all tenants - not what I want (although enough to let me get on with testing other functionality).

Next I tried moving everything user related, including django.contrib.auth, into the TENANT_APPS setting. When I do this I cannot get my initial migrate_schema to run cleanly. It consistently fails with an error about a broken relation to auth_user.

django.db.utils.ProgrammingError: relation "auth_user" does not exist LINE 1: ...user"."is_active", "auth_user"."date_joined" FROM "auth_user...

Which makes sense because in this configuration auth_user will only exist in the tenant schema and I haven't created those yet.

So...based on some digging through previous issues here I duplicated django.contrib.auth in both SHARED_APPS and TENANT_APPS. This allows the initial migrate_schema command to run cleanly and allows me to create tenants. However it creates another problem - I now have multiple copies of the auth_ tables. My specific problem is with auth_group. With only the basic public and a single tenant created I have:

public.auth_group tenant1.auth_group

Which is as it should be. My issue is that I have some code that sets up the initial state of the application database which includes creating some groups. This is a command line tool that I run with the following command:

python application/manage.py tenant_command setup_my_app --schema=tenant1 --settings=config.settings.local

This code uses the Model.objects.get_or_create method to create the groups and I am getting a really weird problem. The creation of the object seems to work as expected and creates a row in the tenant1.auth_group table. However a subsequent use of that group seems to query the public.auth_group table where the row doesn't exist and the code blows up at that point!

Finally I'd like to say thank you very much to everyone who has put time and effort into this project - it has been really helpful for me and I hope one day I'll be able to do something as useful!

How to correctly have contrib.auth in both shared and tenant schemas?

Or: how to disable public <=> tenant relationships in apps common to both

I'm seeking a bit of advice:

I'm trying to use auth in both my public site and tentant sites. Specifically, the User model. My public site is pretty simple, containing only default admin pages for editing Users and Tenants. But django raises an exception when I try to delete any user on my public site. This exception being django is trying to look for a relation that should only exist in tenant apps.

The underlying issue is that django is relating a public User with tenant models. The default behaviour for relationships is to cascade on delete. Which causes a lookup for that tenant model, which doesn't exist in the public schema. Which goes kaboom. I know I can completely disable the reverse relation, but this is not desirable as I still want the reverse to work within tenant apps.

The options I've considered:
A. I'm doing something stupid, or missing something, and I'm too nearsighted to see what
B. using a custom public schema auth/user model that won't interact with tenant relationships
C. going through all of my apps and changing on_delete = CASCADE to on_delete = DO_NOTHING.
D. splitting the public website into its own project
E. spend a lot of time digging through the core code to see if I can selectively disable relationships on the fly
F. add any app containing a User foreign key in TENANT_APPS into SHARED_APPS

For now, I'm operating under the assumption that option B is the least crappy to implement. Options C-F all have some aspect to them that I don't like.

Any further input would be greatly appreciated.

Thanks.


To replicate this, I used the tenant_tutorial as a basis, with just the only data being a public schema and a single tenant/customer.

add the following to tenant_only/models.py:

from django.conf import settings

class TableThree( models.Model ):
     name = models.TextField()
     worker = models.ForeignKey( settings.AUTH_USER_MODEL )

open a shell to the public schema and create, then delete, a user:

> python3 manage.py tenant_command shell --schema=public
>>> from django.contrib.auth.models import User
>>> u = User( username = 'test1' )
>>> u.save()
>>> u.delete()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/django/db/models/base.py", line 862, in delete
    return collector.delete()
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/django/db/models/deletion.py", line 292, in delete
    count = qs._raw_delete(using=self.using)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/django/db/models/query.py", line 614, in _raw_delete
    return sql.DeleteQuery(self.model).delete_qs(self, using)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/django/db/models/sql/subqueries.py", line 81, in delete_qs
    cursor = self.get_compiler(using).execute_sql(CURSOR)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/django/db/models/sql/compiler.py", line 848, in execute_sql
    cursor.execute(sql, params)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/django/db/backends/utils.py", line 79, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/django/db/utils.py", line 95, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/django/utils/six.py", line 685, in reraise
    raise value.with_traceback(tb)
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
django.db.utils.ProgrammingError: relation "tenant_only_tablethree" does not exist
LINE 1: DELETE FROM "tenant_only_tablethree" WHERE "tenant_only_tabl...
                    ^

>>> for att in dir( u ):
...   if '_set' in att:
...     print( att )
... 
__setattr__
__setstate__
_set_pk_val
tablethree_set                # <<< this should not be here
>>> 

python 3.4.2
django 1.9.1 ( I also tested this in 1.8.9 with the same results )
django_tenants 1.1.3

Can't automatically create a user within a schema?

Hey Tom,
I've been busy trying to upgrade my app to Django 1.8 and django-tenants, but I've run into a snag.
My site has frontpage a form that:

  1. Creates a Tenant
  2. Logs into the Tenant by changing connection.set_tenant
  3. Creates an superuser + Profile (works)
  4. Creates a user + Profile based on form data (doesn't work)

It complains that it can't create a userprofile as it can't find the user in the database, which is untrue, because when I query the database before creating the profile, the user is present. Full error:

insert or update on table "webapp_core_userprofile" violates foreign key constraint "webapp_core_userprofil_user_id_4658f19604daf576_fk_auth_user_id"
DETAIL:  Key (user_id)=(2) is not present in table "auth_user".

My code:

    domain_urls = str(domain_urls[0]) #convert array field to string

    # Log into client schema
    newTenant = Customer.objects.get(domain_urls__contains=[domain_urls])
    connection.set_tenant(newTenant)
    ContentType.objects.clear_cache()

    # Create superuser
    from django.contrib.auth.models import User
    admin = User.objects.create_superuser(settings.SYSADMIN['username'], settings.SYSADMIN['email'], settings.SYSADMIN['password'])
    admin.first_name = settings.SYSADMIN['first_name']
    admin.last_name = settings.SYSADMIN['last_name']
    admin.save()
    # Add superuser to UserProfile
    from webapp_core.models import UserProfile
    adminprofile = UserProfile(user=admin)
    adminprofile.fresh_account=False
    adminprofile.save()

    # Create user
    user = User.objects.create_user(username=email, email=email, password=password)
    user.is_staff=True
    user.save()
    # Create userprofile
    myuser = User.objects.get(id=user.id)
    userprofile = UserProfile(user=myuser)
    userprofile.primary_owner=True
    userprofile.fresh_account=True
    userprofile.needs_password=False
    userprofile.save()

It's weird because it does work when creating a superuser but doesn't when I create a normal user. Any idea? Your help would be greatly appreciated :)

Thanks,
Dennis

Order of SHARED_APPS + TENANT_APPS causes import errors

In the documentation we have the following line in the installation guide.

# settings.py
INSTALLED_APPS = list(set(SHARED_APPS + TENANT_APPS))

The problem with this is that it changes the order of the apps. I ran into import errors because some models were imported before the app was loaded:

model doesn't declare an explicit app_label and either isn't in an application in INSTALLED_APPS or else was imported before its application was loaded. This will no longer be supported in Django 1.9.

To fix that I change it to the following:

# Add shared apps to installed apps
INSTALLED_APPS = list(SHARED_APPS)
for app in TENANT_APPS:
    if app not in INSTALLED_APPS:
        INSTALLED_APPS.append(app)

That works for me, but maybe other's need the TENANT_APPS first... Opinions?

assertRedirects does not take into account tenant host

It seems to be an issue when using assertRedirects

Response redirected to 'http://tenant.test.com/admin/images/', expected 'http://testserver/admin/images/'

from django.core.urlresolvers import reverse
from django.contrib.auth import get_user_model

from django_tenants.test.cases import TenantTestCase
from django_tenants.test.client import TenantClient, TenantRequestFactory

from nvcimages.models import get_image_model

from .utils import get_test_image_file


class BaseSetup(TenantTestCase):

    def setUp(self):
        self.client = TenantClient(self.tenant)

    def login(self):
        # create a user
        user = get_user_model().objects.create_superuser(
            email='[email protected]',
            password='password'
        )
        # login
        self.client.login(email='[email protected]', password='password')
        return user

class TestImageDeleteView(BaseSetup):

    def setUp(self):
        super(TestImageDeleteView, self).setUp()
        self.image = get_image_model()(
            title="test.png",
            file=get_test_image_file(),
        )
        self.image.save()
        self.login()

    def get(self, pk):
        return self.client.get(reverse('nvcimages_image_delete', args=[pk]))

    def post(self, pk):
        return self.client.post(reverse('nvcimages_image_delete', args=[pk]))

    def test_simple(self):
        response = self.get(self.image.pk)
        self.assertTemplateUsed(response, 'nvcimages/images/confirm_delete.html')

    def test_delete(self):
        response = self.post(self.image.pk)
        self.assertEqual(Image.objects.all().count(), 0)
        self.assertRedirects(response, reverse('nvcimages_index'))
$ python manage.py test apps.nvcimages.tests.test_cms_views.TestImageDeleteView  --failfast
Creating test database for alias 'default'...
=== Running migrate for schema public
=== Running migrate for schema public
=== Running migrate for schema test
F
======================================================================
FAIL: test_delete (apps.nvcimages.tests.test_cms_views.TestImageDeleteView)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/zzzz/backend/apps/nvcimages/tests/test_cms_views.py", line 174, in test_delete
    self.assert_redirects(response, reverse('nvcimages_index'))
  File "/zzzz/backend/apps/nvcimages/tests/test_cms_views.py", line 48, in assert_redirects
    super(BaseSetup, self).assertRedirects(*args, **kwargs)
  File "/zzzz/.virtualenvs/zzz/local/lib/python2.7/site-packages/django/test/testcases.py", line 306, in assertRedirects
    (url, expected_url))
AssertionError: Response redirected to 'http://tenant.test.com/admin/images/', expected 'http://testserver/admin/images/'

----------------------------------------------------------------------
Ran 1 test in 8.657s

FAILED (failures=1)

response.url is http://tenant.test.com/admin/images/ and
expected_url is /admin/images/

For now I need to specify the host on kwargs to "solve" the issue

    def assertRedirects(self, *args, **kwargs):
        kwargs['host'] = self.tenant.get_primary_domain().domain
        super(BaseSetup, self).assertRedirects(*args, **kwargs)

Any thoughts ?

Should django.contrib.contenttypes be in TENANT_APPS?

This question is regarding the django.contrib.contenttypes app. The documentation specifies that this app should be present in TENANT_APPS https://django-tenants.readthedocs.org/en/latest/install.html#configure-tenant-and-shared-applications Could someone please explain why this is the case?

I think that having django.contrib.contenttypes only in SHARED_APPS is sufficient. The ContentType model and its corresponding django_content_type will reside in the public schema and will contain definitions for all models (shared and tenant-specific). There is no harm in the table referring to tenant-specific models. You only need to ensure that when you lookup objects from a tenant-specific model, you have set the tenant.

If we do so, we don't have to clear the ContentType cache in TenantMiddleware.process_request. https://github.com/tomturner/django-tenants/blob/master/django_tenants/middleware.py#L31

Field defines a relation with model which is either not installed, or is abstract.

I am just starting with Django-tenants and setup a simple app but ran into an error.

Traceback
=========

$ python manage.py migrate_schemas --shared --settings=config.settings.dev
SystemCheckError: System check identified some issues:

ERRORS:
org.Domain.tenant: (fields.E300) Field defines a relation with model 'apps.org.Org', which is either not installed, or is abstract.

settings.py
=========

SHARED_APPS = (
    'django_tenants',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',
    'tinymce',
    'django_extensions',
    'sorl.thumbnail',
    'newsletter',
    # Custom apps
    'apps.org'
)

TENANT_APPS = (
    'django.contrib.contenttypes',
)

INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS]

TENANT_MODEL = "apps.org.Org"

TENANT_DOMAIN_MODEL = "apps.org.Domain"

models.py
========

class Org(TenantMixin, TimeStampedModel):
    """ Org class """

    name = models.CharField(max_length=100)
    url = models.URLField(null=True)

    # default true, schema will be automatically created and synced when it is saved
    auto_create_schema = True


class Domain(DomainMixin):
    """ Domain class """
    pass

Can someone please tell me what I am doing wrong.

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.