Backend Draft

This commit is contained in:
__init__
2026-02-23 20:31:53 +05:30
commit eec700af51
127 changed files with 2356 additions and 0 deletions

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

8
backend/tenants/admin.py Normal file
View File

@@ -0,0 +1,8 @@
from django.contrib import admin
from .models import Tenant
@admin.register(Tenant)
class TenantAdmin(admin.ModelAdmin):
list_display = ('name', 'subdomain', 'is_active', 'created_at')
search_fields = ('name', 'subdomain')
list_filter = ('is_active',)

6
backend/tenants/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class TenantsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "tenants"

View File

@@ -0,0 +1,294 @@
#!/usr/bin/env python
"""
Django Management Command: populate_test_data
Creates realistic test data for MTCBD.
Run with: python manage.py populate_test_data
"""
from django.core.management.base import BaseCommand
from faker import Faker
from django.utils import timezone
import random
import datetime
from tenants.models import Tenant
from accounts.models import User
from projects.models import Project, Task
from dashboard.models import Notification
from analytics.models import ActivityLog
fake = Faker()
class Command(BaseCommand):
help = 'Populate database with realistic test data (100+ users, projects, tasks, etc.)'
def add_arguments(self, parser):
parser.add_argument(
'--clear',
action='store_true',
help='Clear existing data before populating',
)
def handle(self, *args, **options):
clear = options['clear']
if clear and Tenant.objects.exists():
self.stdout.write(self.style.WARNING('Clearing existing data...'))
Task.objects.all().delete()
Project.objects.all().delete()
Notification.objects.all().delete()
ActivityLog.objects.all().delete()
User.objects.all().delete()
Tenant.objects.all().delete()
self.stdout.write(self.style.SUCCESS('✓ Data cleared'))
self.main()
self.stdout.write(self.style.SUCCESS('\n✅ Population complete!'))
def main(self):
self.stdout.write("MTCBD Database Population".center(60))
self.stdout.write("="*60)
# Configuration
NUM_TENANTS = 5
USERS_PER_TENANT = 20
PROJECTS_PER_TENANT = (3, 8)
TASKS_PER_PROJECT = (5, 15)
NOTIFICATIONS_PER_USER = (0, 5)
ACTIVITY_LOGS_PER_TENANT = 50
TENANT_DATA = [
{'name': 'Tech Academy', 'subdomain': 'techacademy'},
{'name': 'Medical College', 'subdomain': 'medcollege'},
{'name': 'Business School', 'subdomain': 'businessschool'},
{'name': 'Engineering Institute', 'subdomain': 'enginstitute'},
{'name': 'Arts University', 'subdomain': 'artsuniv'},
]
tenants = self.create_tenants(TENANT_DATA)
total_users = 0
total_projects = 0
total_tasks = 0
total_notifications = 0
total_logs = 0
for tenant in tenants:
users = self.create_users_for_tenant(tenant, USERS_PER_TENANT)
total_users += len(users)
projects = self.create_projects_for_tenant(tenant, users, PROJECTS_PER_TENANT)
total_projects += len(projects)
if projects:
tasks_created = self.create_tasks_for_projects(projects, users, TASKS_PER_PROJECT)
total_tasks += tasks_created
notifications = self.create_notifications(users, NOTIFICATIONS_PER_USER)
total_notifications += notifications
logs = self.create_activity_logs(tenant, users, ACTIVITY_LOGS_PER_TENANT)
total_logs += logs
self.print_summary(tenants, total_users, total_projects, total_tasks, total_notifications, total_logs)
def create_tenants(self, tenant_data):
self.stdout.write('\nCreating tenants...')
tenants = []
for data in tenant_data:
tenant, created = Tenant.objects.get_or_create(
subdomain=data['subdomain'],
defaults={'name': data['name'], 'is_active': True}
)
tenants.append(tenant)
status = "✓ Created" if created else "→ Exists"
self.stdout.write(f' {status}: {tenant.name} ({tenant.subdomain})')
return tenants
def create_users_for_tenant(self, tenant, count):
self.stdout.write(f'\n Creating users for {tenant.subdomain}...')
users = []
# One super_admin and one institution_admin
admin_roles = ['super_admin', 'institution_admin']
for role in admin_roles:
username = f"{role}_{tenant.subdomain}"
email = f"{role}@{tenant.subdomain}.com"
user, created = User.objects.get_or_create(
username=username,
defaults={
'email': email,
'first_name': fake.first_name(),
'last_name': fake.last_name(),
'role': role,
'tenant': tenant,
'is_staff': role in ['super_admin', 'institution_admin'],
'is_active': True,
}
)
if created:
user.set_password('password123')
user.save()
users.append(user)
self.stdout.write(f'{role}: {username}')
# Other users
remaining = count - 2
for i in range(remaining):
role = random.choice(['teacher', 'project_manager', 'student', 'student', 'student'])
username = f"{tenant.subdomain}_{i+1}"
email = f"user{i+1}@{tenant.subdomain}.com"
user, created = User.objects.get_or_create(
username=username,
defaults={
'email': email,
'first_name': fake.first_name(),
'last_name': fake.last_name(),
'role': role,
'tenant': tenant,
'is_active': True,
}
)
if created:
user.set_password('password123')
user.save()
users.append(user)
self.stdout.write(f' → Total users for {tenant.subdomain}: {len(users)}')
return users
def create_projects_for_tenant(self, tenant, users, range_tuple):
self.stdout.write(f'\n Creating projects for {tenant.subdomain}...')
projects = []
num_projects = random.randint(*range_tuple)
statuses = ['active', 'planned', 'completed', 'archived']
admin_users = [u for u in users if u.role in ['teacher', 'institution_admin', 'super_admin', 'project_manager']]
if not admin_users:
admin_users = users
for i in range(num_projects):
creator = random.choice(admin_users)
project = Project.objects.create(
tenant=tenant,
name=fake.catch_phrase().title(),
description=fake.text(max_nb_chars=200) if random.random() > 0.3 else '',
created_by=creator,
status=random.choice(statuses),
created_at=random_date(timezone.now() - datetime.timedelta(days=365), timezone.now()),
)
projects.append(project)
self.stdout.write(f' ✓ Project: {project.name}')
return projects
def create_tasks_for_projects(self, projects, users, range_tuple):
self.stdout.write(f'\n Creating tasks...')
total_tasks = 0
statuses = ['todo', 'in_progress', 'review', 'done']
priorities = ['low', 'medium', 'high', 'urgent']
for project in projects:
num_tasks = random.randint(*range_tuple)
project_users = [u for u in users if u.tenant == project.tenant]
for i in range(num_tasks):
assigned_to = random.choice(project_users) if random.random() > 0.3 else None
due_date = random_date(timezone.now() + datetime.timedelta(days=1), timezone.now() + datetime.timedelta(days=90)) if random.random() > 0.4 else None
task = Task.objects.create(
tenant=project.tenant,
project=project,
title=fake.sentence(nb_words=6).rstrip('.'),
description=fake.text(max_nb_chars=150) if random.random() > 0.5 else '',
assigned_to=assigned_to,
status=random.choice(statuses),
priority=random.choice(priorities),
due_date=due_date,
)
total_tasks += 1
self.stdout.write(f'{project.name}: {num_tasks} tasks')
self.stdout.write(f' ✓ Total tasks created: {total_tasks}')
return total_tasks
def create_notifications(self, users, range_tuple):
self.stdout.write(f'\n Creating notifications...')
total_notifications = 0
titles = [
'New task assigned', 'Project update', 'Deadline approaching',
'Comment on your task', 'Project completed', 'Meeting reminder',
]
for user in users:
num_notifications = random.randint(*range_tuple)
for i in range(num_notifications):
Notification.objects.create(
tenant=user.tenant,
user=user,
title=random.choice(titles),
message=fake.sentence(),
is_read=random.choice([True, False, False, False]),
link=fake.url() if random.random() > 0.7 else None,
created_at=random_date(timezone.now() - datetime.timedelta(days=30), timezone.now()),
)
total_notifications += 1
self.stdout.write(f' ✓ Total notifications: {total_notifications}')
return total_notifications
def create_activity_logs(self, tenant, users, count):
self.stdout.write(f'\n Creating activity logs for {tenant.subdomain}...')
actions = ['created', 'updated', 'deleted', 'logged_in', 'assigned', 'completed']
target_types = ['project', 'task', 'user', 'notification']
all_users = list(User.objects.all())
total_logs = 0
for i in range(count):
user = random.choice(all_users) if all_users else None
ActivityLog.objects.create(
tenant=tenant,
user=user,
action=random.choice(actions),
target_type=random.choice(target_types),
target_id=str(random.randint(1, 1000)),
metadata={
'ip_address': fake.ipv4(),
'user_agent': fake.user_agent(),
} if random.random() > 0.5 else {},
created_at=random_date(timezone.now() - datetime.timedelta(days=180), timezone.now()),
)
total_logs += 1
self.stdout.write(f' ✓ Activity logs: {total_logs}')
return total_logs
def print_summary(self, tenants, total_users, total_projects, total_tasks, total_notifications, total_logs):
self.stdout.write("\n" + "="*60)
self.stdout.write(" POPULATION COMPLETE".center(60))
self.stdout.write("="*60)
self.stdout.write(f' Tenants created: {len(tenants)}')
self.stdout.write(f' Total users: {total_users}')
self.stdout.write(f' Total projects: {total_projects}')
self.stdout.write(f' Total tasks: {total_tasks}')
self.stdout.write(f' Total notifications: {total_notifications}')
self.stdout.write(f' Total activity logs: {total_logs}')
self.stdout.write("="*60)
self.stdout.write('\nTest accounts per tenant:')
self.stdout.write(' Username: super_admin_<subdomain> / Password: password123')
self.stdout.write(' Username: institution_admin_<subdomain> / Password: password123')
self.stdout.write(' Other users: <subdomain>_1, <subdomain>_2, ...')
self.stdout.write('\nLogin example (use first tenant ID as X-Tenant-ID):')
self.stdout.write(' POST /api/auth/login/')
self.stdout.write(' Headers: X-Tenant-ID: 1')
self.stdout.write(' Body: {"username": "super_admin_techacademy", "password": "password123"}')
self.stdout.write("="*60)
# Also print tenant IDs for reference
self.stdout.write('\nTenant IDs:')
for t in tenants:
self.stdout.write(f' {t.id}: {t.name} ({t.subdomain})')
def random_date(start_date, end_date):
delta = end_date - start_date
random_days = random.randint(0, delta.days)
return start_date + datetime.timedelta(days=random_days)

View File

@@ -0,0 +1,15 @@
from django.db import models
from django.core.exceptions import FieldError
class TenantScopedQuerySet(models.QuerySet):
def tenant(self, tenant=None):
if tenant:
return self.filter(tenant=tenant)
return self
class TenantScopedManager(models.Manager):
def get_queryset(self):
return TenantScopedQuerySet(self.model, using=self._db)
def tenant(self, tenant=None):
return self.get_queryset().tenant(tenant)

View File

@@ -0,0 +1,39 @@
from django.http import JsonResponse
from tenants.models import Tenant
class TenantMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.exempt_paths = [
'/admin/',
'/api/schema/',
'/api/docs/',
'/api/auth/login/',
'/api/auth/register/',
'/api/auth/token/refresh/',
'/api/auth/profile/',
]
def __call__(self, request):
if any(request.path.startswith(path) for path in self.exempt_paths):
request.tenant = None
return self.get_response(request)
# 1. Check Header
tenant_id = request.headers.get('X-Tenant-ID')
if not tenant_id:
# 2. Check Subdomain (Optional, skipping for now, can implement later)
# host = request.get_host().split(':')[0]
# subdomain = host.split('.')[0]
pass
if tenant_id:
try:
request.tenant = Tenant.objects.get(id=tenant_id, is_active=True)
except Tenant.DoesNotExist:
return JsonResponse({"detail": "Invalid or inactive tenant ID supplied."}, status=403)
else:
# Normally we might enforce tenant_id, but we'll let permission classes handle it.
request.tenant = None
return self.get_response(request)

View File

@@ -0,0 +1,34 @@
# Generated by Django 6.0.2 on 2026-02-20 18:33
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Tenant",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
(
"subdomain",
models.CharField(db_index=True, max_length=100, unique=True),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("is_active", models.BooleanField(default=True)),
],
),
]

View File

10
backend/tenants/models.py Normal file
View File

@@ -0,0 +1,10 @@
from django.db import models
class Tenant(models.Model):
name = models.CharField(max_length=255)
subdomain = models.CharField(max_length=100, unique=True, db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name

View File

@@ -0,0 +1,8 @@
from rest_framework import serializers
from .models import Tenant
class TenantSerializer(serializers.ModelSerializer):
class Meta:
model = Tenant
fields = ['id', 'name', 'subdomain', 'created_at', 'is_active']
read_only_fields = ['id', 'created_at']

3
backend/tenants/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
backend/tenants/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.