Files
ACORE/apps/mattermost_connector/services/sync_service.py
T
lokses 5008ba737f AD integration, Mattermost client rewrite, Equipment brand to Directory
- ad_connector: full Django→AD provisioning via ldap3 — LDAP client,
  sync service (create/update/rename CN/enable-disable), Celery tasks,
  signals (auto-create with panel password, attribute updates, password
  sync, block/unblock), sync_users_to_ad management command
- mattermost_connector: rewrite service on mattermostautodriver/httpx,
  fix bare except in username generation (catch ResourceNotFound),
  format_html args instead of interpolation
- it_manager: Equipment.brand CharField → FK to core.Directory
  (equipment_brand type) with data migration
- add missing employees is_staff migration, add requirements.txt

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:01:44 +03:00

471 lines
22 KiB
Python
Executable File
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 typing import Optional
from django.utils import timezone
from django.contrib.auth import get_user_model
from apps.mattermost_connector.models import MattermostUser
Employee = get_user_model()
from apps.mattermost_connector.mattermost_service import MattermostService
from mattermostautodriver.exceptions import ResourceNotFound
import logging
logger = logging.getLogger(__name__)
def generate_username(employee: Employee) -> str:
"""Генерация username для Mattermost"""
if employee.username:
return employee.username
# Если нет username, генерируем из email
base = employee.email.split('@')[0].lower()
# Убираем недопустимые символы
base = ''.join(c if c.isalnum() or c in '-_.' else '_' for c in base)
return base
def sync_employee(employee: Employee, password: Optional[str] = None) -> bool:
"""
Синхронизация сотрудника с Mattermost
Args:
employee: Объект сотрудника
password: Пароль пользователя (если известен, используется при создании)
"""
service = MattermostService()
try:
mattermost_user = None
mm_data = None
# 1. Проверяем, есть ли уже mattermost_user_id у сотрудника
if employee.mattermost_user_id:
try:
mm_data = service.get_user(employee.mattermost_user_id)
mattermost_user = MattermostUser.objects.filter(
mattermost_id=employee.mattermost_user_id
).first()
logger.info(f"Найден существующий пользователь Mattermost по ID: {employee.mattermost_user_id}")
except Exception as e:
logger.warning(f"Пользователь {employee.mattermost_user_id} не найден в Mattermost: {e}")
Employee.objects.filter(pk=employee.pk).update(mattermost_user_id=None)
employee.mattermost_user_id = None
# 2. Если ID нет, пытаемся найти по email
if not employee.mattermost_user_id:
try:
mm_data = service.get_user_by_email(employee.email)
logger.info(f"Найден существующий пользователь Mattermost по email: {employee.email}")
# Обновляем Employee через QuerySet (без вызова signals)
Employee.objects.filter(pk=employee.pk).update(
mattermost_user_id=mm_data['id'],
mattermost_username=mm_data['username']
)
employee.mattermost_user_id = mm_data['id']
employee.mattermost_username = mm_data['username']
# Создаем или обновляем MattermostUser
mattermost_user, created = MattermostUser.objects.get_or_create(
mattermost_id=mm_data['id'],
defaults={
'django_user': employee,
'username': mm_data['username'],
'email': mm_data['email'],
'is_active': not mm_data.get('delete_at', 0)
}
)
if not created and mattermost_user.django_user != employee:
mattermost_user.django_user = employee
mattermost_user.save()
except Exception as e:
logger.info(f"Пользователь с email {employee.email} не найден в Mattermost, будет создан новый")
mm_data = None
# 3. Если пользователь не найден - создаем нового
if not mm_data:
username = generate_username(employee)
# Проверяем уникальность username
counter = 1
original_username = username
while True:
try:
service.get_user_by_username(username)
username = f"{original_username}{counter}"
counter += 1
except ResourceNotFound:
break
# Используем переданный пароль или генерируем новый
user_password = password if password else generate_random_password()
user_data = {
'email': employee.email,
'username': username,
'password': user_password,
'first_name': employee.first_name or '',
'last_name': employee.last_name or '',
'nickname': employee.get_full_name() or '',
'position': employee.post.name if employee.post else '',
'auth_service': '', # Обязательное поле для пользователей без SSO
'auth_data': '' # Обязательное поле для пользователей без SSO
}
logger.info(f"Создание нового пользователя Mattermost: {username} (пароль {'передан из Django' if password else 'сгенерирован'})")
mm_data = service.create_user(user_data)
# Устанавливаем timezone отдельно через preferences
try:
timezone_data = {
'useAutomaticTimezone': 'false',
'manualTimezone': 'Europe/Kiev',
'automaticTimezone': ''
}
service.update_user_preferences(mm_data['id'], timezone_data)
logger.info(f"Установлен часовой пояс для {username}")
except Exception as e:
logger.warning(f"Не удалось установить часовой пояс для {username}: {e}")
# Создаем запись в MattermostUser
mattermost_user = MattermostUser.objects.create(
django_user=employee,
mattermost_id=mm_data['id'],
username=mm_data['username'],
email=mm_data['email'],
is_active=True
)
# Обновляем Employee через QuerySet
Employee.objects.filter(pk=employee.pk).update(
mattermost_user_id=mm_data['id'],
mattermost_username=mm_data['username'],
mattermost_sync_status='synced',
mattermost_last_sync=timezone.now()
)
employee.mattermost_user_id = mm_data['id']
employee.mattermost_username = mm_data['username']
employee.mattermost_sync_status = 'synced'
employee.mattermost_last_sync = timezone.now()
logger.info(f"Создан пользователь Mattermost для {employee.email}")
# Синхронизируем фото при создании
if employee.photo:
try:
service.set_profile_image(mm_data['id'], employee.photo.path)
logger.info(f"Фото профиля синхронизировано для {employee.email}")
except Exception as e:
logger.warning(f"Не удалось синхронизировать фото для {employee.email}: {e}")
else:
# 4. Обновляем существующего пользователя
update_data = {
'email': employee.email,
'first_name': employee.first_name,
'last_name': employee.last_name,
'nickname': employee.get_full_name(),
'position': employee.post.name if employee.post else '',
}
logger.info(f"Обновление пользователя Mattermost: {employee.mattermost_user_id}")
service.update_user(employee.mattermost_user_id, update_data)
# Синхронизируем фото при обновлении
if employee.photo:
try:
service.set_profile_image(employee.mattermost_user_id, employee.photo.path)
logger.info(f"Фото профиля обновлено для {employee.email}")
except Exception as e:
logger.warning(f"Не удалось обновить фото для {employee.email}: {e}")
# Обновляем MattermostUser
if mattermost_user:
mattermost_user.email = employee.email
mattermost_user.updated_at = timezone.now()
mattermost_user.last_sync = timezone.now()
mattermost_user.save()
# Обновляем Employee через QuerySet
Employee.objects.filter(pk=employee.pk).update(
mattermost_sync_status='synced',
mattermost_last_sync=timezone.now()
)
employee.mattermost_sync_status = 'synced'
employee.mattermost_last_sync = timezone.now()
logger.info(f"Обновлен пользователь Mattermost для {employee.email}")
# 5. Добавляем пользователя в команды согласно настройкам
add_user_to_teams(employee)
# 6. Добавляем пользователя в каналы согласно настройкам
add_user_to_channels(employee)
# 7. Если сотрудник привязан к аптеке, добавляем в канал аптеки
if employee.user_pharmacy:
from apps.mattermost_connector.services.pharmacy_channel_service import add_employee_to_pharmacy_channel
add_employee_to_pharmacy_channel(employee)
return True
except Exception as e:
logger.error(f"Ошибка синхронизации {employee.email}: {e}")
Employee.objects.filter(pk=employee.pk).update(mattermost_sync_status='error')
employee.mattermost_sync_status = 'error'
return False
def deactivate_mattermost_user(employee: Employee) -> bool:
"""Деактивация пользователя в Mattermost"""
if not employee.mattermost_user_id:
return False
service = MattermostService()
try:
service.deactivate_user(employee.mattermost_user_id)
# Обновляем MattermostUser
mattermost_user = MattermostUser.objects.filter(
mattermost_id=employee.mattermost_user_id
).first()
if mattermost_user:
mattermost_user.is_active = False
mattermost_user.save()
logger.info(f"Деактивирован пользователь Mattermost для {employee.email}")
return True
except Exception as e:
logger.error(f"Ошибка деактивации {employee.email}: {e}")
return False
def sync_password(employee: Employee, new_password: str) -> bool:
"""
Синхронизация пароля с Mattermost
Args:
employee: Объект сотрудника
new_password: Новый пароль в plain-text
"""
if not employee.mattermost_user_id:
logger.warning(f"Не могу синхронизировать пароль для {employee.email}: нет mattermost_user_id")
return False
service = MattermostService()
try:
logger.info(f"Синхронизация пароля для {employee.email} в Mattermost")
service.reset_password(employee.mattermost_user_id, new_password)
# Обновляем время последней синхронизации
Employee.objects.filter(pk=employee.pk).update(
mattermost_last_sync=timezone.now()
)
employee.mattermost_last_sync = timezone.now()
logger.info(f"Пароль успешно синхронизирован для {employee.email}")
return True
except Exception as e:
logger.error(f"Ошибка синхронизации пароля для {employee.email}: {e}")
return False
def activate_mattermost_user(employee: Employee) -> bool:
"""Активация пользователя в Mattermost"""
if not employee.mattermost_user_id:
return False
service = MattermostService()
try:
service.activate_user(employee.mattermost_user_id)
# Обновляем MattermostUser
mattermost_user = MattermostUser.objects.filter(
mattermost_id=employee.mattermost_user_id
).first()
if mattermost_user:
mattermost_user.is_active = True
mattermost_user.save()
logger.info(f"Активирован пользователь Mattermost для {employee.email}")
return True
except Exception as e:
logger.error(f"Ошибка активации {employee.email}: {e}")
return False
def generate_random_password(length: int = 16) -> str:
"""Генерация случайного пароля"""
import secrets
import string
alphabet = string.ascii_letters + string.digits + string.punctuation
password = ''.join(secrets.choice(alphabet) for _ in range(length))
return password
def add_user_to_teams(employee: Employee) -> bool:
"""
Добавление пользователя в команды Mattermost согласно настройкам синхронизации
Args:
employee: Объект сотрудника
Returns:
bool: True если успешно добавлен хотя бы в одну команду
"""
from apps.mattermost_connector.models import TeamSyncSettings, MattermostTeamMembership, MattermostUser
if not employee.mattermost_user_id:
logger.warning(f"Пользователь {employee.email} не имеет mattermost_user_id")
return False
service = MattermostService()
added_to_teams = 0
try:
# Получаем MattermostUser
mattermost_user = MattermostUser.objects.filter(mattermost_id=employee.mattermost_user_id).first()
if not mattermost_user:
logger.warning(f"MattermostUser не найден для {employee.email}")
return False
# Получаем все активные настройки синхронизации, отсортированные по приоритету
team_settings = TeamSyncSettings.objects.filter(is_active=True).select_related('team').prefetch_related('departments')
for setting in team_settings:
# Проверяем, нужно ли добавлять пользователя в эту команду
should_add = False
if setting.auto_add_users:
if setting.filter_by_department:
# Проверяем, что пользователь из нужного отдела
if employee.department and setting.departments.filter(id=employee.department.id).exists():
should_add = True
else:
# Добавляем всех пользователей
should_add = True
if should_add:
# Проверяем, не добавлен ли уже пользователь в эту команду
membership_exists = MattermostTeamMembership.objects.filter(
user=mattermost_user,
team=setting.team
).exists()
if not membership_exists:
try:
# Добавляем пользователя в команду
logger.info(f"Добавление {employee.email} в команду {setting.team.display_name}")
service.add_user_to_team(setting.team.team_id, employee.mattermost_user_id)
# Создаем запись о членстве
MattermostTeamMembership.objects.create(
user=mattermost_user,
team=setting.team
)
added_to_teams += 1
logger.info(f"Пользователь {employee.email} добавлен в команду {setting.team.display_name}")
except Exception as e:
logger.error(f"Ошибка добавления {employee.email} в команду {setting.team.display_name}: {e}")
else:
logger.debug(f"Пользователь {employee.email} уже в команде {setting.team.display_name}")
if added_to_teams > 0:
logger.info(f"Пользователь {employee.email} добавлен в {added_to_teams} команд(у/ы)")
return True
else:
logger.info(f"Пользователь {employee.email} уже добавлен во все необходимые команды")
return True
except Exception as e:
logger.error(f"Ошибка добавления пользователя {employee.email} в команды: {e}")
return False
def add_user_to_channels(employee: Employee) -> bool:
"""
Добавление пользователя в каналы Mattermost согласно настройкам синхронизации
Args:
employee: Объект сотрудника
Returns:
bool: True если успешно добавлен хотя бы в один канал
"""
from apps.mattermost_connector.models import ChannelSyncSettings, MattermostChannelMembership, MattermostUser
if not employee.mattermost_user_id:
logger.warning(f"Пользователь {employee.email} не имеет mattermost_user_id")
return False
service = MattermostService()
added_to_channels = 0
try:
# Получаем MattermostUser
mattermost_user = MattermostUser.objects.filter(mattermost_id=employee.mattermost_user_id).first()
if not mattermost_user:
logger.warning(f"MattermostUser не найден для {employee.email}")
return False
# Получаем все активные настройки синхронизации каналов, отсортированные по приоритету
channel_settings = ChannelSyncSettings.objects.filter(is_active=True).select_related('channel', 'channel__team').prefetch_related('departments')
for setting in channel_settings:
# Проверяем, нужно ли добавлять пользователя в этот канал
should_add = False
if setting.auto_add_users:
if setting.filter_by_department:
# Проверяем, что пользователь из нужного отдела
if employee.department and setting.departments.filter(id=employee.department.id).exists():
should_add = True
else:
# Добавляем всех пользователей
should_add = True
if should_add:
# Проверяем, не добавлен ли уже пользователь в этот канал
membership_exists = MattermostChannelMembership.objects.filter(
user=mattermost_user,
channel=setting.channel
).exists()
if not membership_exists:
try:
# Добавляем пользователя в канал
logger.info(f"Добавление {employee.email} в канал {setting.channel.display_name}")
service.add_user_to_channel(setting.channel.channel_id, employee.mattermost_user_id)
# Создаем запись о членстве
MattermostChannelMembership.objects.create(
user=mattermost_user,
channel=setting.channel
)
added_to_channels += 1
logger.info(f"Пользователь {employee.email} добавлен в канал {setting.channel.display_name}")
except Exception as e:
logger.error(f"Ошибка добавления {employee.email} в канал {setting.channel.display_name}: {e}")
else:
logger.debug(f"Пользователь {employee.email} уже в канале {setting.channel.display_name}")
if added_to_channels > 0:
logger.info(f"Пользователь {employee.email} добавлен в {added_to_channels} канал(ов)")
return True
else:
logger.info(f"Пользователь {employee.email} уже добавлен во все необходимые каналы")
return True
except Exception as e:
logger.error(f"Ошибка добавления пользователя {employee.email} в каналы: {e}")
return False