feat: implement password setup link for tenant registration
When admin approves tenant registration: - Owner account created in tenant schema (in addition to admin@localhost) - Owner assigned 'owner' role with full permissions - Password setup email sent with secure 7-day token link - Owner sets password via link and auto-logs into their shop Key changes: - Added password_setup_token fields to TenantRegistration model - Created tenants/services.py with formatted email service - Modified _approve_registration to create owner account - Added password_setup_confirm view with token validation - Created password setup template and URL route - Added admin action to resend password setup emails Security: - Token expires after 7 days - Password not transmitted in email (secure setup link) - Owner account inactive until password set - Admin@localhost preserved for system administrator access 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -137,7 +137,7 @@ class TenantRegistrationAdmin(admin.ModelAdmin):
|
||||
}),
|
||||
)
|
||||
|
||||
actions = ['approve_registrations', 'reject_registrations']
|
||||
actions = ['approve_registrations', 'reject_registrations', 'resend_password_setup_email']
|
||||
|
||||
def actions_column(self, obj):
|
||||
"""Кнопки действий для каждой заявки"""
|
||||
@@ -187,6 +187,23 @@ class TenantRegistrationAdmin(admin.ModelAdmin):
|
||||
|
||||
reject_registrations.short_description = "✗ Отклонить выбранные заявки"
|
||||
|
||||
def resend_password_setup_email(self, request, queryset):
|
||||
"""Повторно отправить письмо с ссылкой для установки пароля"""
|
||||
from tenants.services import send_password_setup_email
|
||||
|
||||
sent_count = 0
|
||||
for registration in queryset.filter(status=TenantRegistration.STATUS_APPROVED):
|
||||
try:
|
||||
send_password_setup_email(registration)
|
||||
sent_count += 1
|
||||
except Exception as e:
|
||||
messages.error(request, f"Не удалось отправить письмо для {registration.shop_name}: {e}")
|
||||
|
||||
if sent_count > 0:
|
||||
messages.success(request, f"Письма повторно отправлены: {sent_count}")
|
||||
|
||||
resend_password_setup_email.short_description = "📧 Отправить письмо повторно"
|
||||
|
||||
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||
"""
|
||||
Обработка действий активации/отклонения через GET параметры
|
||||
@@ -285,6 +302,32 @@ class TenantRegistrationAdmin(admin.ModelAdmin):
|
||||
else:
|
||||
logger.warning(f"Пользователь с email {settings.TENANT_ADMIN_EMAIL} уже существует в тенанте")
|
||||
|
||||
# Создаем аккаунт владельца тенанта
|
||||
logger.info(f"Создание аккаунта владельца для тенанта: {client.id}")
|
||||
if not User.objects.filter(email=registration.owner_email).exists():
|
||||
owner = User.objects.create_user(
|
||||
email=registration.owner_email,
|
||||
name=registration.owner_name,
|
||||
password=None # Пароль будет установлен через ссылку
|
||||
)
|
||||
# Помечаем email как подтвержденный, так как владелец регистрировался с ним
|
||||
owner.is_email_confirmed = True
|
||||
owner.email_confirmed_at = timezone.now()
|
||||
owner.is_active = False # Неактивен до установки пароля
|
||||
owner.save()
|
||||
logger.info(f"Аккаунт владельца создан: {owner.id} ({owner.email})")
|
||||
|
||||
# Назначаем роль owner владельцу
|
||||
try:
|
||||
from user_roles.models import Role
|
||||
RoleService.assign_role_to_user(owner, Role.OWNER, created_by=None)
|
||||
logger.info(f"Роль owner назначена владельцу {owner.email}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при назначении роли owner: {e}", exc_info=True)
|
||||
# Не прерываем процесс
|
||||
else:
|
||||
logger.warning(f"Пользователь с email {registration.owner_email} уже существует в тенанте")
|
||||
|
||||
# Создаем системного клиента для анонимных продаж
|
||||
logger.info(f"Создание системного клиента для тенанта: {client.id}")
|
||||
from customers.models import Customer
|
||||
@@ -337,6 +380,17 @@ class TenantRegistrationAdmin(admin.ModelAdmin):
|
||||
# Возвращаемся в public схему
|
||||
connection.set_schema_to_public()
|
||||
|
||||
# Отправляем письмо владельцу с ссылкой для установки пароля
|
||||
logger.info(f"Отправка письма владельцу с ссылкой установки пароля: {registration.owner_email}")
|
||||
try:
|
||||
from tenants.services import send_password_setup_email
|
||||
send_password_setup_email(registration)
|
||||
logger.info(f"Письмо успешно отправлено владельцу {registration.owner_email}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке письма владельцу: {e}", exc_info=True)
|
||||
# Не прерываем процесс одобрения, если письмо не отправилось
|
||||
# Админ может повторно отправить письмо через действие в админке
|
||||
|
||||
# Обновляем статус заявки
|
||||
registration.status = TenantRegistration.STATUS_APPROVED
|
||||
registration.processed_at = timezone.now()
|
||||
|
||||
Reference in New Issue
Block a user