Files
octopus/myproject/shops/models.py
Andrey Smakotin 097d4ea304 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>
2025-10-27 19:13:10 +03:00

143 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
from phonenumber_field.modelfields import PhoneNumberField
class Shop(models.Model):
"""
Модель магазина/пункта самовывоза для цветочного магазина в Минске.
"""
name = models.CharField(
max_length=200,
verbose_name="Название магазина"
)
# Адрес магазина
street = models.CharField(
max_length=255,
verbose_name="Улица"
)
building_number = models.CharField(
max_length=20,
verbose_name="Номер здания"
)
district = models.CharField(
max_length=100,
blank=True,
null=True,
verbose_name="Район",
help_text="Район в Минске"
)
# Контактная информация
phone = PhoneNumberField(
verbose_name="Телефон",
help_text="Контактный телефон магазина"
)
email = models.EmailField(
blank=True,
null=True,
verbose_name="Email"
)
# Режим работы
opening_time = models.TimeField(
verbose_name="Время открытия",
help_text="Время начала работы магазина"
)
closing_time = models.TimeField(
verbose_name="Время закрытия",
help_text="Время окончания работы магазина"
)
working_days = models.CharField(
max_length=100,
default="Пн-Вс",
verbose_name="Рабочие дни",
help_text="Например: Пн-Пт, Пн-Вс, Пн-Сб"
)
# Статусы и настройки
is_active = models.BooleanField(
default=True,
verbose_name="Активен",
help_text="Работает ли магазин в данный момент"
)
is_pickup_point = models.BooleanField(
default=True,
verbose_name="Пункт самовывоза",
help_text="Доступен ли магазин для самовывоза заказов"
)
# Дополнительная информация
description = models.TextField(
blank=True,
null=True,
verbose_name="Описание",
help_text="Дополнительная информация о магазине"
)
delivery_instructions = models.TextField(
blank=True,
null=True,
verbose_name="Инструкции для клиентов",
help_text="Как найти магазин, где припарковаться и т.д."
)
# Координаты для карты (опционально)
latitude = models.DecimalField(
max_digits=9,
decimal_places=6,
null=True,
blank=True,
verbose_name="Широта",
help_text="Координаты для отображения на карте"
)
longitude = models.DecimalField(
max_digits=9,
decimal_places=6,
null=True,
blank=True,
verbose_name="Долгота",
help_text="Координаты для отображения на карте"
)
# Временные метки
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name="Дата создания"
)
updated_at = models.DateTimeField(
auto_now=True,
verbose_name="Дата обновления"
)
class Meta:
verbose_name = "Магазин"
verbose_name_plural = "Магазины"
indexes = [
models.Index(fields=['is_active']),
models.Index(fields=['is_pickup_point']),
models.Index(fields=['district']),
]
ordering = ['name']
def __str__(self):
return f"{self.name} ({self.full_address})"
@property
def full_address(self):
"""Полный адрес магазина"""
return f"{self.street}, {self.building_number}"
@property
def working_hours(self):
"""Форматированный режим работы"""
return f"{self.working_days}: {self.opening_time.strftime('%H:%M')} - {self.closing_time.strftime('%H:%M')}"