diff --git a/chat/admin.py b/chat/admin.py index 2c467d4..a94de30 100644 --- a/chat/admin.py +++ b/chat/admin.py @@ -8,8 +8,8 @@ from .models import Channel, Message @admin.register(Channel) class ChannelAdmin(admin.ModelAdmin): - list_display = ('name', 'read_access', 'write_access', 'tournament', 'pool', 'team', 'private',) - list_filter = ('read_access', 'write_access', 'tournament', 'private',) + list_display = ('name', 'category', 'read_access', 'write_access', 'tournament', 'private',) + list_filter = ('category', 'read_access', 'write_access', 'tournament', 'private',) search_fields = ('name', 'tournament__name', 'team__name', 'team__trigram',) autocomplete_fields = ('tournament', 'pool', 'team', 'invited', ) diff --git a/chat/consumers.py b/chat/consumers.py index 1f7441c..47b66ce 100644 --- a/chat/consumers.py +++ b/chat/consumers.py @@ -3,7 +3,6 @@ from channels.generic.websocket import AsyncJsonWebsocketConsumer from django.contrib.auth.models import User -from participation.models import Team, Pool, Tournament from registration.models import Registration from .models import Channel, Message @@ -78,6 +77,7 @@ class ChatConsumer(AsyncJsonWebsocketConsumer): { 'id': channel.id, 'name': channel.name, + 'category': channel.category, 'read_access': True, 'write_access': await write_channels.acontains(channel), } diff --git a/chat/management/__init__.py b/chat/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chat/management/commands/__init__.py b/chat/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chat/management/commands/create_chat_channels.py b/chat/management/commands/create_chat_channels.py new file mode 100644 index 0000000..0baf61c --- /dev/null +++ b/chat/management/commands/create_chat_channels.py @@ -0,0 +1,144 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.core.management import BaseCommand +from django.utils.translation import activate + +from participation.models import Team, Tournament +from tfjm.permissions import PermissionType + +from ...models import Channel + + +class Command(BaseCommand): + def handle(self, *args, **kwargs): + activate('fr') + + Channel.objects.update_or_create( + name="Annonces", + defaults=dict( + category=Channel.ChannelCategory.GENERAL, + read_access=PermissionType.AUTHENTICATED, + write_access=PermissionType.ADMIN, + ), + ) + + Channel.objects.update_or_create( + name="Aide jurys et orgas", + defaults=dict( + category=Channel.ChannelCategory.GENERAL, + read_access=PermissionType.VOLUNTEER, + write_access=PermissionType.VOLUNTEER, + ), + ) + + Channel.objects.update_or_create( + name="Général", + defaults=dict( + category=Channel.ChannelCategory.GENERAL, + read_access=PermissionType.AUTHENTICATED, + write_access=PermissionType.AUTHENTICATED, + ), + ) + + Channel.objects.update_or_create( + name="Je cherche une équipe", + defaults=dict( + category=Channel.ChannelCategory.GENERAL, + read_access=PermissionType.AUTHENTICATED, + write_access=PermissionType.AUTHENTICATED, + ), + ) + + Channel.objects.update_or_create( + name="Détente", + defaults=dict( + category=Channel.ChannelCategory.GENERAL, + read_access=PermissionType.AUTHENTICATED, + write_access=PermissionType.AUTHENTICATED, + ), + ) + + for tournament in Tournament.objects.all(): + Channel.objects.update_or_create( + name=f"{tournament.name} - Annonces", + defaults=dict( + category=Channel.ChannelCategory.TOURNAMENT, + read_access=PermissionType.TOURNAMENT_MEMBER, + write_access=PermissionType.TOURNAMENT_ORGANIZER, + tournament=tournament, + ), + ) + + Channel.objects.update_or_create( + name=f"{tournament.name} - Général", + defaults=dict( + category=Channel.ChannelCategory.TOURNAMENT, + read_access=PermissionType.TOURNAMENT_MEMBER, + write_access=PermissionType.TOURNAMENT_MEMBER, + tournament=tournament, + ), + ) + + Channel.objects.update_or_create( + name=f"{tournament.name} - Détente", + defaults=dict( + category=Channel.ChannelCategory.TOURNAMENT, + read_access=PermissionType.TOURNAMENT_MEMBER, + write_access=PermissionType.TOURNAMENT_MEMBER, + tournament=tournament, + ), + ) + + Channel.objects.update_or_create( + name=f"{tournament.name} - Juré⋅es", + defaults=dict( + category=Channel.ChannelCategory.TOURNAMENT, + read_access=PermissionType.JURY_MEMBER, + write_access=PermissionType.JURY_MEMBER, + tournament=tournament, + ), + ) + + if tournament.remote: + Channel.objects.update_or_create( + name=f"{tournament.name} - Président⋅es de jury", + defaults=dict( + category=Channel.ChannelCategory.TOURNAMENT, + read_access=PermissionType.TOURNAMENT_JURY_PRESIDENT, + write_access=PermissionType.TOURNAMENT_JURY_PRESIDENT, + tournament=tournament, + ), + ) + + for pool in tournament.pools.all(): + Channel.objects.update_or_create( + name=f"{tournament.name} - Poule {pool.short_name}", + defaults=dict( + category=Channel.ChannelCategory.TOURNAMENT, + read_access=PermissionType.POOL_MEMBER, + write_access=PermissionType.POOL_MEMBER, + pool=pool, + ), + ) + + Channel.objects.update_or_create( + name=f"{tournament.name} - Poule {pool.short_name} - Jury", + defaults=dict( + category=Channel.ChannelCategory.TOURNAMENT, + read_access=PermissionType.JURY_MEMBER, + write_access=PermissionType.JURY_MEMBER, + pool=pool, + ), + ) + + for team in Team.objects.filter(participation__valid=True).all(): + Channel.objects.update_or_create( + name=f"Équipe {team.trigram}", + defaults=dict( + category=Channel.ChannelCategory.TEAM, + read_access=PermissionType.TEAM_MEMBER, + write_access=PermissionType.TEAM_MEMBER, + team=team, + ), + ) diff --git a/chat/migrations/0002_alter_channel_options_channel_category.py b/chat/migrations/0002_alter_channel_options_channel_category.py new file mode 100644 index 0000000..82898dd --- /dev/null +++ b/chat/migrations/0002_alter_channel_options_channel_category.py @@ -0,0 +1,36 @@ +# Generated by Django 5.0.3 on 2024-04-28 11:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("chat", "0001_initial"), + ] + + operations = [ + migrations.AlterModelOptions( + name="channel", + options={ + "ordering": ("category", "name"), + "verbose_name": "channel", + "verbose_name_plural": "channels", + }, + ), + migrations.AddField( + model_name="channel", + name="category", + field=models.CharField( + choices=[ + ("general", "General channels"), + ("tournament", "Tournament channels"), + ("team", "Team channels"), + ("private", "Private channels"), + ], + default="general", + max_length=255, + verbose_name="category", + ), + ), + ] diff --git a/chat/models.py b/chat/models.py index 913d8b8..9b91ec0 100644 --- a/chat/models.py +++ b/chat/models.py @@ -13,11 +13,24 @@ from tfjm.permissions import PermissionType class Channel(models.Model): + class ChannelCategory(models.TextChoices): + GENERAL = 'general', _("General channels") + TOURNAMENT = 'tournament', _("Tournament channels") + TEAM = 'team', _("Team channels") + PRIVATE = 'private', _("Private channels") + name = models.CharField( max_length=255, verbose_name=_("name"), ) + category = models.CharField( + max_length=255, + verbose_name=_("category"), + choices=ChannelCategory, + default=ChannelCategory.GENERAL, + ) + read_access = models.CharField( max_length=16, verbose_name=_("read permission"), @@ -90,7 +103,7 @@ class Channel(models.Model): return Channel.objects.filter(**{permission_type: PermissionType.ANONYMOUS}) qs |= Channel.objects.filter(**{permission_type: PermissionType.AUTHENTICATED}) - registration = await Registration.objects.aget(user_id=user.id) + registration = await Registration.objects.prefetch_related('user').aget(user_id=user.id) if registration.is_admin: return Channel.objects.all() @@ -147,7 +160,7 @@ class Channel(models.Model): class Meta: verbose_name = _("channel") verbose_name_plural = _("channels") - ordering = ('name',) + ordering = ('category', 'name',) class Message(models.Model): diff --git a/chat/static/chat.js b/chat/static/chat.js index bc74972..7a0ee09 100644 --- a/chat/static/chat.js +++ b/chat/static/chat.js @@ -6,6 +6,7 @@ const MAX_MESSAGES = 50 +const channel_categories = ['general', 'tournament', 'team', 'private'] let channels = {} let messages = {} let selected_channel_id = null @@ -62,20 +63,27 @@ function sendMessage() { function setChannels(new_channels) { channels = {} - let navTab = document.getElementById('nav-channels-tab') - navTab.innerHTML = '' + let categoryLists = {} + for (let category of channel_categories) { + categoryLists[category] = document.getElementById(`nav-${category}-channels-tab`) + categoryLists[category].innerHTML = '' + categoryLists[category].parentElement.classList.add('d-none') + } for (let channel of new_channels) { channels[channel['id']] = channel if (!messages[channel['id']]) messages[channel['id']] = new Map() + let categoryList = categoryLists[channel['category']] + categoryList.parentElement.classList.remove('d-none') + let navItem = document.createElement('li') navItem.classList.add('list-group-item') navItem.id = `tab-channel-${channel['id']}` navItem.setAttribute('data-bs-dismiss', 'offcanvas') navItem.onclick = () => selectChannel(channel['id']) - navTab.appendChild(navItem) + categoryList.appendChild(navItem) let channelButton = document.createElement('button') channelButton.classList.add('nav-link') diff --git a/chat/templates/chat/content.html b/chat/templates/chat/content.html index e712e14..b58e35a 100644 --- a/chat/templates/chat/content.html +++ b/chat/templates/chat/content.html @@ -3,13 +3,30 @@ -
+
-

{% trans "Chat channels" %}

+

{% trans "Chat channels" %}

- +
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 187ca3f..dd23b19 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: TFJM\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-04-28 11:56+0200\n" +"POT-Creation-Date: 2024-04-28 13:08+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Emmy D'Anello \n" "Language-Team: LANGUAGE \n" @@ -21,20 +21,40 @@ msgstr "" msgid "API" msgstr "API" -#: chat/models.py:18 participation/models.py:35 participation/models.py:263 +#: chat/models.py:17 +msgid "General channels" +msgstr "Canaux généraux" + +#: chat/models.py:18 +msgid "Tournament channels" +msgstr "Canaux de tournois" + +#: chat/models.py:19 +msgid "Team channels" +msgstr "Canaux d'équipes" + +#: chat/models.py:20 +msgid "Private channels" +msgstr "Messages privés" + +#: chat/models.py:24 participation/models.py:35 participation/models.py:263 #: participation/tables.py:18 participation/tables.py:34 msgid "name" msgstr "nom" -#: chat/models.py:23 +#: chat/models.py:29 +msgid "category" +msgstr "catégorie" + +#: chat/models.py:36 msgid "read permission" msgstr "permission de lecture" -#: chat/models.py:29 +#: chat/models.py:42 msgid "write permission" msgstr "permission d'écriture" -#: chat/models.py:39 draw/admin.py:53 draw/admin.py:71 draw/admin.py:88 +#: chat/models.py:52 draw/admin.py:53 draw/admin.py:71 draw/admin.py:88 #: draw/models.py:26 participation/admin.py:79 participation/admin.py:140 #: participation/admin.py:171 participation/models.py:693 #: participation/models.py:717 participation/models.py:935 @@ -43,7 +63,7 @@ msgstr "permission d'écriture" msgid "tournament" msgstr "tournoi" -#: chat/models.py:41 +#: chat/models.py:54 msgid "" "For a permission that concerns a tournament, indicates what is the concerned " "tournament." @@ -51,21 +71,21 @@ msgstr "" "Pour une permission qui concerne un tournoi, indique quel est le tournoi " "concerné." -#: chat/models.py:50 draw/models.py:429 draw/models.py:456 +#: chat/models.py:63 draw/models.py:429 draw/models.py:456 #: participation/admin.py:136 participation/admin.py:155 #: participation/models.py:1434 participation/models.py:1443 #: participation/tables.py:84 msgid "pool" msgstr "poule" -#: chat/models.py:52 +#: chat/models.py:65 msgid "" "For a permission that concerns a pool, indicates what is the concerned pool." msgstr "" "Pour une permission qui concerne une poule, indique quelle est la poule " "concernée." -#: chat/models.py:61 draw/templates/draw/tournament_content.html:277 +#: chat/models.py:74 draw/templates/draw/tournament_content.html:277 #: participation/admin.py:167 participation/models.py:252 #: participation/models.py:708 #: participation/templates/participation/tournament_harmonize.html:15 @@ -75,18 +95,18 @@ msgstr "" msgid "team" msgstr "équipe" -#: chat/models.py:63 +#: chat/models.py:76 msgid "" "For a permission that concerns a team, indicates what is the concerned team." msgstr "" "Pour une permission qui concerne une équipe, indique quelle est l'équipe " "concernée." -#: chat/models.py:67 +#: chat/models.py:80 msgid "private" msgstr "privé" -#: chat/models.py:69 +#: chat/models.py:82 msgid "" "If checked, only users who have been explicitly added to the channel will be " "able to access it." @@ -94,11 +114,11 @@ msgstr "" "Si sélectionné, seul⋅es les utilisateur⋅rices qui ont été explicitement " "ajouté⋅es au canal pourront y accéder." -#: chat/models.py:74 +#: chat/models.py:87 msgid "invited users" msgstr "Utilisateur⋅rices invité" -#: chat/models.py:77 +#: chat/models.py:90 msgid "" "Extra users who have been invited to the channel, in addition to the " "permitted group of the channel." @@ -106,40 +126,40 @@ msgstr "" "Utilisateur⋅rices supplémentaires qui ont été invité⋅es au canal, en plus du " "groupe autorisé du canal." -#: chat/models.py:82 +#: chat/models.py:95 #, python-brace-format msgid "Channel {name}" msgstr "Canal {name}" -#: chat/models.py:148 chat/models.py:157 +#: chat/models.py:161 chat/models.py:170 msgid "channel" msgstr "canal" -#: chat/models.py:149 +#: chat/models.py:162 msgid "channels" msgstr "canaux" -#: chat/models.py:163 +#: chat/models.py:176 msgid "author" msgstr "auteur⋅rice" -#: chat/models.py:170 +#: chat/models.py:183 msgid "created at" msgstr "créé le" -#: chat/models.py:175 +#: chat/models.py:188 msgid "updated at" msgstr "modifié le" -#: chat/models.py:180 +#: chat/models.py:193 msgid "content" msgstr "contenu" -#: chat/models.py:243 +#: chat/models.py:256 msgid "message" msgstr "message" -#: chat/models.py:244 +#: chat/models.py:257 msgid "messages" msgstr "messages" @@ -171,7 +191,7 @@ msgstr "Déconnexion" msgid "Install app on home screen" msgstr "Installer l'application sur l'écran d'accueil" -#: chat/templates/chat/content.html:54 +#: chat/templates/chat/content.html:55 msgid "Fetch previous messages…" msgstr "Récupérer les messages précédents…"