feat: Добавить систему мультитенантности с регистрацией магазинов
Реализована полноценная система мультитенантности на базе django-tenants. Каждый магазин получает изолированную схему БД и поддомен. Основные компоненты: Django-tenants интеграция: - Модели Client (тенант) и Domain в приложении tenants/ - Разделение на SHARED_APPS и TENANT_APPS - Public schema для общей админки - Tenant schemas для изолированных данных магазинов Система регистрации магазинов: - Публичная форма регистрации на /register/ - Модель TenantRegistration для заявок со статусами (pending/approved/rejected) - Валидация schema_name (латиница, 3-63 символа, уникальность) - Проверка на зарезервированные имена (admin, api, www и т.д.) - Админ-панель для модерации заявок с кнопками активации/отклонения Система подписок: - Модель Subscription с планами (триал 90 дней, месяц, квартал, год) - Автоматическое создание триальной подписки при активации - Методы is_expired() и days_left() для проверки статуса - Цветовая индикация в админке (зеленый/оранжевый/красный) Приложения: - tenants/ - управление тенантами, регистрация, подписки - shops/ - точки магазинов/самовывоза (tenant app) - Обновлены миграции для всех приложений Утилиты: - switch_to_tenant.py - переключение между схемами тенантов - Обновлены image_processor и image_service Конфигурация: - urls_public.py - роуты для public schema (админка + регистрация) - urls.py - роуты для tenant schemas (магазины) - requirements.txt - добавлены django-tenants, django-environ, phonenumber-field Документация: - DJANGO_TENANTS_SETUP.md - настройка мультитенантности - TENANT_REGISTRATION_GUIDE.md - руководство по регистрации - QUICK_START.md - быстрый старт - START_HERE.md - общая документация Использование: 1. Пользователь: http://localhost:8000/register/ → заполняет форму 2. Админ: http://localhost:8000/admin/ → активирует заявку 3. Результат: http://{schema_name}.localhost:8000/ - готовый магазин 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
70
myproject/tenants/templates/tenants/base.html
Normal file
70
myproject/tenants/templates/tenants/base.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Регистрация магазина{% endblock %} - Inventory System</title>
|
||||
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.card {
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
}
|
||||
.card-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 15px 15px 0 0 !important;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.form-control:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||
}
|
||||
.alert {
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8 col-lg-6">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap 5 JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
133
myproject/tenants/templates/tenants/registration_form.html
Normal file
133
myproject/tenants/templates/tenants/registration_form.html
Normal file
@@ -0,0 +1,133 @@
|
||||
{% extends "tenants/base.html" %}
|
||||
|
||||
{% block title %}Регистрация нового магазина{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h3 class="mb-0">Регистрация нового магазина</h3>
|
||||
<p class="mb-0 mt-2">Заполните форму для создания вашего интернет-магазина</p>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Название магазина -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.shop_name.id_for_label }}" class="form-label">
|
||||
{{ form.shop_name.label }}
|
||||
<span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.shop_name }}
|
||||
{% if form.shop_name.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.shop_name.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.shop_name.help_text %}
|
||||
<div class="form-text">{{ form.shop_name.help_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Поддомен -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.schema_name.id_for_label }}" class="form-label">
|
||||
{{ form.schema_name.label }}
|
||||
<span class="text-danger">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
{{ form.schema_name }}
|
||||
<span class="input-group-text">.inventory.by</span>
|
||||
</div>
|
||||
{% if form.schema_name.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.schema_name.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.schema_name.help_text %}
|
||||
<div class="form-text">{{ form.schema_name.help_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Имя владельца -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.owner_name.id_for_label }}" class="form-label">
|
||||
{{ form.owner_name.label }}
|
||||
<span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.owner_name }}
|
||||
{% if form.owner_name.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.owner_name.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.owner_email.id_for_label }}" class="form-label">
|
||||
{{ form.owner_email.label }}
|
||||
<span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.owner_email }}
|
||||
{% if form.owner_email.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.owner_email.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.owner_email.help_text %}
|
||||
<div class="form-text">{{ form.owner_email.help_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Телефон -->
|
||||
<div class="mb-4">
|
||||
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
||||
{{ form.phone.label }}
|
||||
<span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.phone }}
|
||||
{% if form.phone.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.phone.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Кнопка отправки -->
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
Отправить заявку
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<small class="text-muted">
|
||||
После отправки заявки ваш магазин будет проверен администратором.<br>
|
||||
Уведомление придет на указанный email в течение 24 часов.
|
||||
</small>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<a href="/admin/" class="text-white text-decoration-none">
|
||||
<small>Войти в панель администратора</small>
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Автоматическое преобразование поддомена в lowercase
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const schemaNameInput = document.getElementById('{{ form.schema_name.id_for_label }}');
|
||||
if (schemaNameInput) {
|
||||
schemaNameInput.addEventListener('input', function() {
|
||||
this.value = this.value.toLowerCase();
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,50 @@
|
||||
{% extends "tenants/base.html" %}
|
||||
|
||||
{% block title %}Заявка отправлена{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h3 class="mb-0">Спасибо за регистрацию!</h3>
|
||||
</div>
|
||||
<div class="card-body p-5 text-center">
|
||||
<div class="mb-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" fill="currentColor" class="bi bi-check-circle text-success" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
||||
<path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h4 class="mb-3">Ваша заявка успешно отправлена!</h4>
|
||||
|
||||
<p class="lead mb-4">
|
||||
Мы получили вашу заявку на создание магазина.<br>
|
||||
Наш администратор проверит данные и активирует ваш магазин.
|
||||
</p>
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
<h5 class="alert-heading">Что дальше?</h5>
|
||||
<hr>
|
||||
<ul class="list-unstyled text-start mb-0">
|
||||
<li class="mb-2">✓ В течение 24 часов администратор проверит вашу заявку</li>
|
||||
<li class="mb-2">✓ После активации вы получите письмо на указанный email</li>
|
||||
<li class="mb-2">✓ В письме будет ссылка на ваш магазин и инструкции</li>
|
||||
<li>✓ Вам будет предоставлен триальный период на 90 дней</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<a href="{% url 'tenants:register' %}" class="btn btn-outline-primary">
|
||||
Подать еще одну заявку
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<small class="text-muted">
|
||||
Если у вас возникли вопросы, свяжитесь с нами:<br>
|
||||
<a href="mailto:support@inventory.by">support@inventory.by</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user