From 58efe33070adea010497c7b7716d249de5d31b30 Mon Sep 17 00:00:00 2001 From: lokses Date: Wed, 16 Sep 2026 20:32:34 +0300 Subject: [PATCH] =?UTF-8?q?mattermost:=20=D0=B0=D0=B2=D1=82=D0=BE=D0=B7?= =?UTF-8?q?=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=20=D0=B8=D0=B7=20Mattermost=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Если у бота задан только access-token (без id/username/имени), при сохранении в админке дёргаем GET users/me этим токеном и заполняем user_id, username, display_name автоматически. - MattermostService.identity_for_token(token) — кто владелец токена; - MattermostBotAdmin.save_model подставляет пустые поля, сообщает результат. Co-Authored-By: Claude Opus 4.8 --- apps/mattermost_connector/admin.py | 31 ++++++++++++++++++- .../mattermost_service.py | 27 ++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/apps/mattermost_connector/admin.py b/apps/mattermost_connector/admin.py index be2d19d..40879fa 100755 --- a/apps/mattermost_connector/admin.py +++ b/apps/mattermost_connector/admin.py @@ -1,5 +1,5 @@ from django import forms -from django.contrib import admin +from django.contrib import admin, messages from unfold.admin import ModelAdmin, TabularInline from unfold.decorators import display from django.utils.html import format_html @@ -78,6 +78,35 @@ class MattermostBotAdmin(ModelAdmin): def caps_display(self, obj): return ', '.join(obj.caps()) or '—' + def save_model(self, request, obj, form, change): + # Задан только токен — подтянуть id/username/имя из Mattermost (GET users/me). + if obj.token and (not obj.user_id or not obj.username or not obj.display_name): + try: + from apps.mattermost_connector.mattermost_service import MattermostService + info = MattermostService.identity_for_token(obj.token) + if info and info.get('user_id'): + obj.user_id = obj.user_id or info['user_id'] + obj.username = obj.username or info['username'] + obj.display_name = obj.display_name or info['display_name'] + self.message_user( + request, + f'Данные бота получены из Mattermost: @{obj.username} (id={obj.user_id})', + level=messages.SUCCESS, + ) + elif info is None: + self.message_user( + request, + 'Коннектор Mattermost не настроен — автозаполнение бота пропущено.', + level=messages.WARNING, + ) + except Exception as e: + self.message_user( + request, + f'Не удалось получить данные бота из Mattermost (проверьте токен): {e}', + level=messages.WARNING, + ) + super().save_model(request, obj, form, change) + @admin.register(MattermostTeam) class MattermostTeamAdmin(ModelAdmin): diff --git a/apps/mattermost_connector/mattermost_service.py b/apps/mattermost_connector/mattermost_service.py index 20eef46..2aba159 100755 --- a/apps/mattermost_connector/mattermost_service.py +++ b/apps/mattermost_connector/mattermost_service.py @@ -85,6 +85,33 @@ class MattermostService: """Получение пользователя по username""" return self._call(f'GET users/username/{username}', self.driver.users.get_user_by_username, username) + @staticmethod + def identity_for_token(token: str) -> Dict | None: + """Кто владелец токена (GET users/me): id/username/display_name. + + Используется, чтобы автозаполнить карточку бота, когда задан только + access-token. Возвращает None, если коннектор не настроен или токен пуст. + """ + from core.models import ConnectorConfig + cfg = ConnectorConfig.objects.filter(connector_type='mattermost', is_enabled=True).first() + if not cfg or not cfg.host or not token: + return None + scheme = 'https' if cfg.use_ssl else 'http' + port = cfg.port or (443 if cfg.use_ssl else 80) + driver = TypedDriver({'url': cfg.host, 'port': port, 'scheme': scheme, 'timeout': 30}) + driver.client.token = token + me = driver.users.get_user('me') + display = ( + me.get('nickname') + or f"{me.get('first_name', '')} {me.get('last_name', '')}".strip() + or me.get('username', '') + ) + return { + 'user_id': me.get('id', ''), + 'username': me.get('username', ''), + 'display_name': display, + } + def update_user(self, user_id: str, user_data: Dict) -> Dict: """Обновление данных пользователя через PATCH""" return self._call(f'PATCH users/{user_id}', self.driver.users.patch_user, user_id, log_data=user_data, **user_data)