Улучшена структура списков чатов: круглые аватары, поддержка изображений, названия отделов, badge 'Основной'

This commit is contained in:
admin i 2025-11-30 11:29:54 +03:00
parent 83704ebce3
commit 81d9178ec9
4 changed files with 110 additions and 15 deletions

View File

@ -0,0 +1,41 @@
"""
Management command для обновления названий основных чатов отделов
"""
from django.core.management.base import BaseCommand
from communications.models import Chat
class Command(BaseCommand):
help = 'Обновляет названия основных чатов отделов'
def handle(self, *args, **options):
# Находим все основные чаты отделов без названия
chats = Chat.objects.filter(
type='department',
is_main=True
).select_related('department')
updated = 0
for chat in chats:
if not chat.name or chat.name == '':
old_name = chat.name or '(пусто)'
chat.name = f"Основной чат {chat.department.name}"
chat.save()
self.stdout.write(
self.style.SUCCESS(
f'✓ Обновлен чат ID {chat.id}: '
f'"{old_name}" → "{chat.name}"'
)
)
updated += 1
if updated == 0:
self.stdout.write(
self.style.WARNING('Нет чатов для обновления')
)
else:
self.stdout.write(
self.style.SUCCESS(
f'\n✓ Обновлено названий: {updated}'
)
)

View File

@ -16,7 +16,12 @@ def create_main_department_chat(sender, instance, created, **kwargs):
if not Chat.objects.filter(
type="department", department=instance, is_main=True
).exists():
Chat.objects.create(type="department", department=instance, is_main=True)
Chat.objects.create(
type="department",
department=instance,
is_main=True,
name=f"Основной чат {instance.name}"
)
@receiver(pre_save, sender='communications.Chat')

View File

@ -86,7 +86,7 @@
.card-icon img {
width: 44px;
height: 44px;
border-radius: 8px; /* Овальная форма вместо круглой */
border-radius: 50%; /* Круглая форма */
object-fit: cover;
border: 2px solid var(--bs-border-color);
}

View File

@ -101,6 +101,11 @@
<div class="d-flex align-items-center gap-2 flex-wrap">
<a href="{% url 'communications:chat_detail' chat.pk %}" class="card-title text-decoration-none stretched-link">
{{ chat.name|default:"Глобальный чат" }}
{% if chat.is_main %}
<span class="badge bg-primary-subtle text-primary-emphasis border border-primary-subtle ms-1">
<i class="bi-star-fill"></i> Основной
</span>
{% endif %}
</a>
<span class="badge bg-light text-dark border">
<i class="bi-chat-left-text"></i> {{ chat.messages.count }}
@ -129,6 +134,11 @@
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="#" data-action="pin"><i class="bi-pin me-2"></i><span data-pin-text>Закрепить</span></a></li>
<li><a class="dropdown-item" href="#" data-action="notifications"><i class="bi-bell me-2"></i>Уведомления</a></li>
{% if not chat.is_main %}
<li><a class="dropdown-item" href="#" data-action="edit"><i class="bi-pencil me-2"></i>Редактировать</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" data-action="delete"><i class="bi-trash me-2"></i>Удалить</a></li>
{% endif %}
</ul>
</div>
@ -157,12 +167,26 @@
data-chat-id="{{ chat.pk }}"
data-type="department"
data-last-ts="{% if last %}{{ last.created_at|date:'U' }}000{% else %}{{ chat.created_at|date:'U' }}000{% endif %}"
data-haystack="{{ chat.department.name }}">
<div class="card-icon" style="width:44px;height:44px;"><i class="bi-diagram-3"></i></div>
data-haystack="{{ chat.name|default:'Основной чат' }} {{ chat.department.name }}">
{# Аватар чата отдела или иконка #}
<div class="card-icon" style="width:44px;height:44px;">
{% if chat.avatar %}
<img src="{{ chat.avatar.url }}" alt="{{ chat.name|default:'Основной чат' }}" class="chat-avatar" loading="lazy">
{% else %}
<i class="bi-diagram-3"></i>
{% endif %}
</div>
<div class="flex-grow-1 position-relative">
<div class="d-flex align-items-center gap-2 flex-wrap">
<a href="{% url 'communications:chat_detail' chat.pk %}" class="card-title text-decoration-none stretched-link">
Чат отдела: {{ chat.department.name }}
{{ chat.name|default:"Основной чат" }}
{% if chat.is_main %}
<span class="badge bg-primary-subtle text-primary-emphasis border border-primary-subtle ms-1">
<i class="bi-star-fill"></i> Основной
</span>
{% endif %}
</a>
<span class="badge bg-light text-dark border">
<i class="bi-chat-left-text"></i> {{ chat.messages.count }}
@ -171,14 +195,18 @@
data-unread>+<span data-unread-count>{{ chat.unread_count|default:0 }}</span></span>
</div>
<div class="card-subtitle mt-1">
{# Название отдела таким же шрифтом как последнее сообщение #}
<span class="text-secondary">
<i class="bi-diagram-3"></i> {{ chat.department.name }}
</span>
{% if last %}
<span class="text-secondary">
<span data-last-time>{{ last.created_at|date:"d.m.Y H:i" }}</span> ·
· <span data-last-time>{{ last.created_at|date:"d.m.Y H:i" }}</span> ·
<strong data-last-author>{{ last.author.get_full_name|default:last.author.username }}</strong>:
<span data-last-preview>{{ last.content|default:"(вложение)"|truncatechars:120 }}</span>
<span data-last-preview>{{ last.content|default:"(вложение)"|truncatechars:100 }}</span>
</span>
{% else %}
<span class="text-secondary">Сообщений пока нет.</span>
<span class="text-secondary"> · Сообщений пока нет</span>
{% endif %}
</div>
</div>
@ -191,6 +219,11 @@
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="#" data-action="pin"><i class="bi-pin me-2"></i><span data-pin-text>Закрепить</span></a></li>
<li><a class="dropdown-item" href="#" data-action="notifications"><i class="bi-bell me-2"></i>Уведомления</a></li>
{% if not chat.is_main %}
<li><a class="dropdown-item" href="#" data-action="edit"><i class="bi-pencil me-2"></i>Редактировать</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" data-action="delete"><i class="bi-trash me-2"></i>Удалить</a></li>
{% endif %}
</ul>
</div>
@ -220,12 +253,22 @@
data-type="private"
data-last-ts="{% if last %}{{ last.created_at|date:'U' }}000{% else %}{{ chat.created_at|date:'U' }}000{% endif %}"
data-haystack="{% for u in chat.participants.all %}{% if u != user %} {{ u.get_full_name|default:u.username }}{% endif %}{% endfor %}">
{# Аватар собеседника #}
<div class="card-icon" style="width:44px;height:44px;">
{% with p0=chat.participants.all.0 p1=chat.participants.all.1 %}
{% if p0 and p0 != user and p0.avatar %}
<img src="{{ p0.avatar.url }}" alt="{{ p0.get_full_name|default:p0.username }}" loading="lazy">
{% elif p1 and p1 != user and p1.avatar %}
<img src="{{ p1.avatar.url }}" alt="{{ p1.get_full_name|default:p1.username }}" loading="lazy">
{% if p0 and p0 != user %}
{% if p0.avatar %}
<img src="{{ p0.avatar.url }}" alt="{{ p0.get_full_name|default:p0.username }}" class="chat-avatar" loading="lazy">
{% else %}
<i class="bi-person"></i>
{% endif %}
{% elif p1 and p1 != user %}
{% if p1.avatar %}
<img src="{{ p1.avatar.url }}" alt="{{ p1.get_full_name|default:p1.username }}" class="chat-avatar" loading="lazy">
{% else %}
<i class="bi-person"></i>
{% endif %}
{% else %}
<i class="bi-person"></i>
{% endif %}
@ -317,13 +360,19 @@
data-unread>+<span data-unread-count>{{ chat.unread_count|default:0 }}</span></span>
</div>
<div class="card-subtitle mt-1">
{# Описание группы если есть #}
{% if chat.description %}
<span class="text-secondary">
<i class="bi-info-circle"></i> {{ chat.description|truncatechars:80 }}
</span>
{% endif %}
{% if last %}
<span class="text-secondary">
<span data-last-time>{{ last.created_at|date:"d.m.Y H:i" }}</span> ·
{% if chat.description %}<br>{% endif %}<span data-last-time>{{ last.created_at|date:"d.m.Y H:i" }}</span> ·
<strong data-last-author>{{ last.author.get_full_name|default:last.author.username }}</strong>:
<span data-last-preview>{{ last.content|default:"(вложение)"|truncatechars:120 }}</span>
<span data-last-preview>{{ last.content|default:"(вложение)"|truncatechars:100 }}</span>
</span>
{% else %}
{% elif not chat.description %}
<span class="text-secondary">Сообщений пока нет.</span>
{% endif %}
</div>