mattermost: журнал синхронизации (как у AD/Carbonio/Nextcloud)

- модель MattermostSyncLog (employee/action/status/message) + read-only админка;
- запись при добавлении в команды/каналы (успех и ошибка) в sync_service;
- пункт «Журнал Mattermost» в меню рядом с прочими журналами.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 16:40:08 +03:00
parent dd1a31b4dd
commit c54a3b316a
5 changed files with 111 additions and 3 deletions
+5
View File
@@ -598,6 +598,11 @@ UNFOLD = {
"icon": "cloud",
"link": reverse_lazy("admin:nextcloud_connector_nextcloudsynclog_changelist"),
},
{
"title": "Журнал Mattermost",
"icon": "forum",
"link": reverse_lazy("admin:mattermost_connector_mattermostsynclog_changelist"),
},
],
},
],
+21
View File
@@ -13,9 +13,30 @@ from .models import (
TeamSyncSettings,
ChannelSyncSettings,
MattermostBot,
MattermostSyncLog,
)
@admin.register(MattermostSyncLog)
class MattermostSyncLogAdmin(ModelAdmin):
"""Журнал синхронизации Mattermost — только просмотр (по образцу AD)."""
list_display = ('employee', 'action', 'status', 'created_at')
list_filter = ('action', 'status')
search_fields = ('employee__username', 'employee__last_name', 'employee__first_name', 'message')
date_hierarchy = 'created_at'
actions = None
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
# Разрешаем CASCADE при удалении сотрудника; UI журнала без delete_selected.
return request.user.has_perm('employees.delete_employee')
class TeamSyncSettingsForm(forms.ModelForm):
location_types = forms.MultipleChoiceField(
choices=LOCATION_TYPE_CHOICES, required=False,
@@ -0,0 +1,35 @@
# Generated by Django 6.0.3 on 2026-09-16 13:39
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mattermost_connector', '0004_channelsyncsettings_filter_by_location_type_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='MattermostSyncLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('action', models.CharField(choices=[('create', 'Создание пользователя'), ('add_team', 'Добавление в команду'), ('add_channel', 'Добавление в канал'), ('update', 'Обновление'), ('deactivate', 'Отключение')], max_length=20, verbose_name='Действие')),
('status', models.CharField(choices=[('success', 'Успех'), ('error', 'Ошибка')], max_length=10, verbose_name='Статус')),
('message', models.TextField(blank=True, verbose_name='Сообщение')),
('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mattermost_sync_logs', to=settings.AUTH_USER_MODEL, verbose_name='Сотрудник')),
],
options={
'verbose_name': 'Запись синхронизации Mattermost',
'verbose_name_plural': 'Журнал синхронизации Mattermost',
'ordering': ['-created_at'],
},
),
]
+32 -1
View File
@@ -4,6 +4,7 @@ from django.db import models
from django.contrib.auth import get_user_model
from django.utils import timezone
from core.models import BaseModel
from core.models.fields import EncryptedCharField
# Типы локаций сотрудника (Employee.LOCATION_TYPES) — для фильтра синхронизации.
@@ -305,4 +306,34 @@ class ChannelSyncSettings(models.Model):
def __str__(self):
status = "✓" if self.is_active else "✗"
return f"{status} {self.channel.display_name} ({self.channel.team.display_name})"
return f"{status} {self.channel.display_name} ({self.channel.team.display_name})"
class MattermostSyncLog(BaseModel):
"""Журнал синхронизации сотрудников с Mattermost (по образцу AD/Carbonio/Nextcloud)."""
employee = models.ForeignKey(
'employees.Employee', on_delete=models.CASCADE,
related_name='mattermost_sync_logs', verbose_name='Сотрудник',
)
action = models.CharField(
'Действие', max_length=20,
choices=[
('create', 'Создание пользователя'),
('add_team', 'Добавление в команду'),
('add_channel', 'Добавление в канал'),
('update', 'Обновление'),
('deactivate', 'Отключение'),
],
)
status = models.CharField(
'Статус', max_length=10,
choices=[('success', 'Успех'), ('error', 'Ошибка')],
)
message = models.TextField('Сообщение', blank=True)
class Meta:
verbose_name = 'Запись синхронизации Mattermost'
verbose_name_plural = 'Журнал синхронизации Mattermost'
ordering = ['-created_at']
def __str__(self):
return f'{self.employee} — {self.get_action_display()} — {self.get_status_display()}'
@@ -328,7 +328,7 @@ def add_user_to_teams(employee: Employee) -> bool:
Returns:
bool: True если успешно добавлен хотя бы в одну команду
"""
from apps.mattermost_connector.models import TeamSyncSettings, MattermostTeamMembership, MattermostUser
from apps.mattermost_connector.models import TeamSyncSettings, MattermostTeamMembership, MattermostUser, MattermostSyncLog
if not employee.mattermost_user_id:
logger.warning(f"Пользователь {employee.email} не имеет mattermost_user_id")
@@ -386,9 +386,17 @@ def add_user_to_teams(employee: Employee) -> bool:
added_to_teams += 1
logger.info(f"Пользователь {employee.email} добавлен в команду {setting.team.display_name}")
MattermostSyncLog.objects.create(
employee=employee, action='add_team', status='success',
message=f'Команда: {setting.team.display_name}',
)
except Exception as e:
logger.error(f"Ошибка добавления {employee.email} в команду {setting.team.display_name}: {e}")
MattermostSyncLog.objects.create(
employee=employee, action='add_team', status='error',
message=f'{setting.team.display_name}: {e}',
)
else:
logger.debug(f"Пользователь {employee.email} уже в команде {setting.team.display_name}")
@@ -414,7 +422,7 @@ def add_user_to_channels(employee: Employee) -> bool:
Returns:
bool: True если успешно добавлен хотя бы в один канал
"""
from apps.mattermost_connector.models import ChannelSyncSettings, MattermostChannelMembership, MattermostUser
from apps.mattermost_connector.models import ChannelSyncSettings, MattermostChannelMembership, MattermostUser, MattermostSyncLog
if not employee.mattermost_user_id:
logger.warning(f"Пользователь {employee.email} не имеет mattermost_user_id")
@@ -471,9 +479,17 @@ def add_user_to_channels(employee: Employee) -> bool:
added_to_channels += 1
logger.info(f"Пользователь {employee.email} добавлен в канал {setting.channel.display_name}")
MattermostSyncLog.objects.create(
employee=employee, action='add_channel', status='success',
message=f'Канал: {setting.channel.display_name}',
)
except Exception as e:
logger.error(f"Ошибка добавления {employee.email} в канал {setting.channel.display_name}: {e}")
MattermostSyncLog.objects.create(
employee=employee, action='add_channel', status='error',
message=f'{setting.channel.display_name}: {e}',
)
else:
logger.debug(f"Пользователь {employee.email} уже в канале {setting.channel.display_name}")