Commit 6337ba2c authored by guglielmo's avatar guglielmo

multitenancy implemented

parent 375d20f7
......@@ -13,4 +13,3 @@
docker-compose.yml
docker-compose.*.yml
Dockerfile
dump*
......@@ -82,8 +82,11 @@ DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL", default=ADMIN_EMAIL)
# DATABASES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {"default": env.db("DATABASE_URL")}
DATABASES = {"default": env.db("DATABASE_URL", engine='django_tenants.postgresql_backend')}
DATABASES["default"]["ATOMIC_REQUESTS"] = True
DATABASE_ROUTERS = (
'django_tenants.routers.TenantSyncRouter',
)
# MEDIA CONFIGURATION
# -----------------------------------------------------------------------------
......@@ -150,6 +153,7 @@ TEMPLATES = [
# -----------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#middleware-classes
MIDDLEWARE = [
'django_tenants.middleware.main.TenantMainMiddleware',
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
......@@ -166,8 +170,9 @@ ROOT_URLCONF = "config.urls"
# APPS CONFIGURATION
# -----------------------------------------------------------------------------
DJANGO_APPS = (
SHARED_APPS = (
# Default Django apps:
"django_tenants",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
......@@ -179,18 +184,30 @@ DJANGO_APPS = (
# 'django.contrib.admindocs',
# Django helper
"django_extensions",
# admin enhancers apps
'django_object_actions',
'django_admin_row_actions',
# Customers or tenants
"project.tenants",
)
# Apps specific for this project go here.
LOCAL_APPS = (
TENANT_APPS = (
# Default Django apps:
"django.contrib.contenttypes",
"django.contrib.auth",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
# Admin panel and documentation:
"django.contrib.admin",
# admin enhancers apps
"django_object_actions",
"django_admin_row_actions",
# The app
"project.webapp",
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + LOCAL_APPS
INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS]
# AUTHENTICATION CONFIGURATION
# -----------------------------------------------------------------------------
......@@ -206,11 +223,20 @@ LOGS_PATH = env("LOGS_PATH", default=os.path.normpath(str(RESOURCES_PATH) + "/lo
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"filters": {
"tenant_context": {
"()": "django_tenants.log.TenantContextFilter"
},
},
"formatters": {
"verbose": {
"format": "[%(asctime)s] %(levelname)s [%(pathname)s:%(funcName)s:%(lineno)s] %(message)s",
"datefmt": "%d/%b/%Y %H:%M:%S",
},
"tenant_context": {
"format": "[%(schema_name)s:%(domain_url)s] "
"%(levelname)-7s %(asctime)s %(message)s",
},
"simple": {"format": "[%(asctime)s] %(levelname)s %(message)s", "datefmt": "%d/%b/%Y %H:%M:%S"},
},
"handlers": {
......@@ -220,9 +246,14 @@ LOGGING = {
"filename": os.path.normpath(os.path.join(LOGS_PATH, "opsv.log")),
"maxBytes": 1024 * 1024 * 10, # 10 MB
"backupCount": 7,
"formatter": "verbose",
"filters": ["tenant_context", ]
},
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"stream": sys.stdout,
"filters": ["tenant_context", ]
},
"console": {"level": "DEBUG", "class": "logging.StreamHandler", "stream": sys.stdout, "formatter": "verbose"},
},
"loggers": {
"django": {
......@@ -262,6 +293,11 @@ WSGI_APPLICATION = "wsgi.application"
# See: https://docs.djangoproject.com/en/dev/releases/1.6/#new-test-runner
TEST_RUNNER = "django.test.runner.DiscoverRunner"
# TENANT CONFIGURATION
# -------------------
TENANT_MODEL = "tenants.Instance"
TENANT_DOMAIN_MODEL = "tenants.Domain"
# END TENANT CONFIGURATION
VERSION = __import__(PROJECT_PACKAGE).__version__
......
from django.contrib import admin
from django.contrib.sites.models import Site
from django_tenants.admin import TenantAdminMixin
from .models import Instance, Domain
class DomainInline(admin.TabularInline):
"""An inline for related domains."""
model = Domain
extra = 0
fields = ('domain', )
max_num = 3
@admin.register(Instance)
class InstanceAdmin(TenantAdminMixin, admin.ModelAdmin):
inlines = [DomainInline, ]
list_display = ('name', 'on_trial')
def has_module_permission(self, request):
"""
Return empty perms dict thus hiding the model from admin index.
"""
if 'localhost' not in list(request.tenant.domains.values_list('domain', flat=True)):
return {}
else:
return super().has_module_permission(request)
def has_add_permission(self, request):
"""
Return empty perms dict blocking users from instance application from adding instances
"""
if 'localhost' not in list(request.tenant.domains.values_list('domain', flat=True)):
return {}
else:
return super().has_add_permission(request)
def get_queryset(self, request):
if 'localhost' not in list(request.tenant.domains.values_list('domain', flat=True)):
return Instance.objects.none()
# return super().get_queryset(request).filter(id__in=[request.tenant.id])
else:
return super().get_queryset(request)
admin.site.unregister(Site)
from django.apps import AppConfig
class TenantsConfig(AppConfig):
name = 'project.tenants'
# Generated by Django 2.2.1 on 2019-05-31 08:59
from django.db import migrations, models
import django.db.models.deletion
import django_tenants.postgresql_backend.base
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Instance',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('schema_name', models.CharField(db_index=True, max_length=63, unique=True, validators=[django_tenants.postgresql_backend.base._check_schema_name])),
('name', models.CharField(max_length=100)),
('on_trial', models.BooleanField(default=True)),
('created_on', models.DateField(auto_now_add=True)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Domain',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('domain', models.CharField(db_index=True, max_length=253, unique=True)),
('is_primary', models.BooleanField(db_index=True, default=True)),
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='domains', to='tenants.Instance')),
],
options={
'abstract': False,
},
),
]
from django.db import models
from django_tenants.models import TenantMixin, DomainMixin
class Instance(TenantMixin):
name = models.CharField(max_length=100)
on_trial = models.BooleanField(default=True)
created_on = models.DateField(auto_now_add=True)
# default true, schema will be automatically created and synced when it is saved
auto_create_schema = True
def __str__(self):
return self.name
class Domain(DomainMixin):
def __str__(self):
return f"{self.domain}"
from django.apps import AppConfig
class WebappConfig(AppConfig):
name = 'project.webapp'
from unittest.mock import patch
from django.test import TestCase
from django_tenants.test.cases import TenantTestCase
from django_tenants.test.client import TenantClient
from django.utils import timezone
from project.webapp.models import Content, OrganisationType, APIException
from project.webapp.tests import html_content, parsed_content
class ContentTests(TestCase):
class ContentTests(TenantTestCase):
@classmethod
def setUpClass(cls):
......@@ -24,6 +26,9 @@ class ContentTests(TestCase):
create contents and read html and parsed content used in tests
:return:
"""
super().setUp()
self.client_stub = TenantClient(self.tenant)
self.org = OrganisationType.objects.create(
name='Test'
)
......
from unittest.mock import patch
from django.test import TestCase, Client
from django_tenants.test.cases import TenantTestCase
from django_tenants.test.client import TenantClient
from project.webapp.models import OrganisationType
from project.webapp.views import Content
from project.webapp.tests import parsed_content
class ViewTest(TestCase):
class ViewTest(TenantTestCase):
def setUp(self):
"""
......@@ -16,7 +17,8 @@ class ViewTest(TestCase):
test parsed from the (mocked) live site
:return:
"""
self.client_stub = Client()
super().setUp()
self.client_stub = TenantClient(self.tenant)
self.org = OrganisationType.objects.create(
name='Test'
......
# Ignore everything
*
# But not these files...
!.gitignore
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment