From 849e5bd6c47a55148e6830a8744a3c46e14b6c3d Mon Sep 17 00:00:00 2001 From: behruz-dev Date: Wed, 19 Nov 2025 16:03:31 +0500 Subject: [PATCH] django-tenants added, and configurated customers app --- config/settings/base.py | 28 +++++++++---- core/apps/customers/__init__.py | 0 core/apps/customers/admin/__init__.py | 2 + core/apps/customers/admin/client.py | 11 +++++ core/apps/customers/admin/domain.py | 8 ++++ core/apps/customers/apps.py | 10 +++++ .../management/commands/create_client.py | 22 ++++++++++ .../apps/customers/migrations/0001_initial.py | 42 +++++++++++++++++++ ...lient_on_trial_remove_client_paid_until.py | 21 ++++++++++ core/apps/customers/migrations/__init__.py | 0 core/apps/customers/models/__init__.py | 2 + core/apps/customers/models/client.py | 10 +++++ core/apps/customers/models/domain.py | 7 ++++ core/apps/customers/urls.py | 6 +++ core/apps/shared/__init__.py | 0 core/apps/shared/apps.py | 6 +++ core/apps/shared/migrations/__init__.py | 0 core/apps/shared/models/__init__.py | 1 + core/apps/shared/models/base.py | 9 ++++ core/apps/shared/urls.py | 0 requirements.txt | 10 ++++- 21 files changed, 186 insertions(+), 9 deletions(-) create mode 100644 core/apps/customers/__init__.py create mode 100644 core/apps/customers/admin/__init__.py create mode 100644 core/apps/customers/admin/client.py create mode 100644 core/apps/customers/admin/domain.py create mode 100644 core/apps/customers/apps.py create mode 100644 core/apps/customers/management/commands/create_client.py create mode 100644 core/apps/customers/migrations/0001_initial.py create mode 100644 core/apps/customers/migrations/0002_remove_client_on_trial_remove_client_paid_until.py create mode 100644 core/apps/customers/migrations/__init__.py create mode 100644 core/apps/customers/models/__init__.py create mode 100644 core/apps/customers/models/client.py create mode 100644 core/apps/customers/models/domain.py create mode 100644 core/apps/customers/urls.py create mode 100644 core/apps/shared/__init__.py create mode 100644 core/apps/shared/apps.py create mode 100644 core/apps/shared/migrations/__init__.py create mode 100644 core/apps/shared/models/__init__.py create mode 100644 core/apps/shared/models/base.py create mode 100644 core/apps/shared/urls.py diff --git a/config/settings/base.py b/config/settings/base.py index 22e29a8..7ddd5b6 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -13,27 +13,32 @@ ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') # APPS +SHARED_APPS = [ + 'django_tenants', + 'core.apps.customers', + 'django.contrib.contenttypes', +] + DJANGO_APPS = [ 'django.contrib.admin', 'django.contrib.auth', - 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] -LOCAL_APPS = [ - +TENANT_APPS = [ + 'core.apps.shared', ] -PACKAGES = [ +PACKAGES = [] -] -INSTALLED_APPS = DJANGO_APPS + PACKAGES + LOCAL_APPS +INSTALLED_APPS = SHARED_APPS + DJANGO_APPS + PACKAGES + TENANT_APPS # Middlewares MIDDLEWARE = [ + 'django_tenants.middleware.main.TenantMainMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -65,7 +70,7 @@ WSGI_APPLICATION = 'config.wsgi.application' DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql', + 'ENGINE': 'django_tenants.postgresql_backend', 'NAME': env.str('POSTGRES_DB'), 'USER': env.str('POSTGRES_USER'), 'PASSWORD': env.str('POSTGRES_PASSWORD'), @@ -74,6 +79,10 @@ DATABASES = { } } +DATABASE_ROUTERS = ( + 'django_tenants.routers.TenantSyncRouter', +) + AUTH_PASSWORD_VALIDATORS = [ { @@ -103,3 +112,8 @@ MEDIA_ROOT = BASE_DIR / 'resources/media' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# Django tenants +TENANT_MODEL = "customers.Client" + +TENANT_DOMAIN_MODEL = "customers.Domain" diff --git a/core/apps/customers/__init__.py b/core/apps/customers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/customers/admin/__init__.py b/core/apps/customers/admin/__init__.py new file mode 100644 index 0000000..a6e8c1f --- /dev/null +++ b/core/apps/customers/admin/__init__.py @@ -0,0 +1,2 @@ +from .domain import * +from .client import * \ No newline at end of file diff --git a/core/apps/customers/admin/client.py b/core/apps/customers/admin/client.py new file mode 100644 index 0000000..95da56e --- /dev/null +++ b/core/apps/customers/admin/client.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from core.apps.customers.models import Client +from core.apps.customers.admin.domain import DomainInline + + +@admin.register(Client) +class ClientAdmin(admin.ModelAdmin): + list_display = ['id', 'name', 'schema_name'] + search_fields = ['name'] + inlines = [DomainInline] \ No newline at end of file diff --git a/core/apps/customers/admin/domain.py b/core/apps/customers/admin/domain.py new file mode 100644 index 0000000..48a3caf --- /dev/null +++ b/core/apps/customers/admin/domain.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from core.apps.customers.models import Domain + + +class DomainInline(admin.TabularInline): + model = Domain + extra = 0 \ No newline at end of file diff --git a/core/apps/customers/apps.py b/core/apps/customers/apps.py new file mode 100644 index 0000000..5850bf8 --- /dev/null +++ b/core/apps/customers/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + + +class CustomersConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core.apps.customers' + + def ready(self): + import core.apps.customers.admin + \ No newline at end of file diff --git a/core/apps/customers/management/commands/create_client.py b/core/apps/customers/management/commands/create_client.py new file mode 100644 index 0000000..74ae294 --- /dev/null +++ b/core/apps/customers/management/commands/create_client.py @@ -0,0 +1,22 @@ +from django.core.management import BaseCommand + +from core.apps.customers.models import Client, Domain + +class Command(BaseCommand): + def handle(self, *args, **options): + client_name = input('Mijoz nomini kiriting: ') + schema_name = input('Schema nomini kiriting: ').lower() + domain = input('Domain kiriting: ').lower() + + client, created = Client.objects.get_or_create( + name=client_name, + schema_name=schema_name, + ) + Domain.objects.get_or_create( + domain=domain, + tenant=client, + is_primary=True + ) + + self.stdout("Mijoz qo'shildi") + \ No newline at end of file diff --git a/core/apps/customers/migrations/0001_initial.py b/core/apps/customers/migrations/0001_initial.py new file mode 100644 index 0000000..d28f3f4 --- /dev/null +++ b/core/apps/customers/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 5.2 on 2025-11-19 10:53 + +import django.db.models.deletion +import django_tenants.postgresql_backend.base +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Client', + fields=[ + ('id', models.BigAutoField(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)), + ('paid_until', models.DateField()), + ('on_trial', models.BooleanField()), + ('created_at', models.DateField(auto_now_add=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Domain', + fields=[ + ('id', models.BigAutoField(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='customers.client')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/core/apps/customers/migrations/0002_remove_client_on_trial_remove_client_paid_until.py b/core/apps/customers/migrations/0002_remove_client_on_trial_remove_client_paid_until.py new file mode 100644 index 0000000..279dcce --- /dev/null +++ b/core/apps/customers/migrations/0002_remove_client_on_trial_remove_client_paid_until.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2 on 2025-11-19 10:57 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('customers', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='client', + name='on_trial', + ), + migrations.RemoveField( + model_name='client', + name='paid_until', + ), + ] diff --git a/core/apps/customers/migrations/__init__.py b/core/apps/customers/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/customers/models/__init__.py b/core/apps/customers/models/__init__.py new file mode 100644 index 0000000..e56fc89 --- /dev/null +++ b/core/apps/customers/models/__init__.py @@ -0,0 +1,2 @@ +from .client import * +from .domain import * \ No newline at end of file diff --git a/core/apps/customers/models/client.py b/core/apps/customers/models/client.py new file mode 100644 index 0000000..52971dc --- /dev/null +++ b/core/apps/customers/models/client.py @@ -0,0 +1,10 @@ +from django.db import models + +from django_tenants.models import TenantMixin + + +class Client(TenantMixin): + name = models.CharField(max_length=100) + created_at = models.DateField(auto_now_add=True) + + auto_create_schema = True \ No newline at end of file diff --git a/core/apps/customers/models/domain.py b/core/apps/customers/models/domain.py new file mode 100644 index 0000000..c8aeee2 --- /dev/null +++ b/core/apps/customers/models/domain.py @@ -0,0 +1,7 @@ +from django.db import models + +from django_tenants.models import DomainMixin + + +class Domain(DomainMixin): + pass \ No newline at end of file diff --git a/core/apps/customers/urls.py b/core/apps/customers/urls.py new file mode 100644 index 0000000..2787512 --- /dev/null +++ b/core/apps/customers/urls.py @@ -0,0 +1,6 @@ +from django.urls import path + + +urlpatterns = [ + +] \ No newline at end of file diff --git a/core/apps/shared/__init__.py b/core/apps/shared/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/shared/apps.py b/core/apps/shared/apps.py new file mode 100644 index 0000000..ca9e94d --- /dev/null +++ b/core/apps/shared/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SharedConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core.apps.shared' diff --git a/core/apps/shared/migrations/__init__.py b/core/apps/shared/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/shared/models/__init__.py b/core/apps/shared/models/__init__.py new file mode 100644 index 0000000..773cfc4 --- /dev/null +++ b/core/apps/shared/models/__init__.py @@ -0,0 +1 @@ +from .base import * \ No newline at end of file diff --git a/core/apps/shared/models/base.py b/core/apps/shared/models/base.py new file mode 100644 index 0000000..342a4e2 --- /dev/null +++ b/core/apps/shared/models/base.py @@ -0,0 +1,9 @@ +from django.db import models + + +class BaseModel(models.Model): + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True \ No newline at end of file diff --git a/core/apps/shared/urls.py b/core/apps/shared/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt index 1f92fac..a5ad1f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,11 @@ +asgiref==3.10.0 +click==8.3.1 Django==5.2 +django-environ==0.12.0 +django-tenants==3.9.0 gunicorn==23.0.0 +h11==0.16.0 +packaging==25.0 +psycopg2-binary==2.9.11 +sqlparse==0.5.3 uvicorn==0.38.0 -django-environ -psycopg2-binary \ No newline at end of file