Реализована полноценная система мультитенантности на базе 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>
134 lines
5.2 KiB
HTML
134 lines
5.2 KiB
HTML
{% 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 %}
|