From d9bb0a0860798b4e8d6e722b63c20c94777964c2 Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Tue, 23 Apr 2024 00:22:18 +0200 Subject: [PATCH] Prepare models for new chat feature Signed-off-by: Emmy D'Anello --- chat/__init__.py | 2 + chat/admin.py | 22 ++ chat/apps.py | 9 + chat/migrations/0001_initial.py | 198 ++++++++++++++++ chat/migrations/__init__.py | 2 + chat/models.py | 116 ++++++++++ chat/tests.py | 2 + chat/urls.py | 2 + chat/views.py | 2 + .../migrations/0003_alter_teamdraw_options.py | 28 +++ locale/fr/LC_MESSAGES/django.po | 212 ++++++++++++++---- tfjm/permissions.py | 19 ++ tfjm/settings.py | 1 + tfjm/urls.py | 1 + 14 files changed, 571 insertions(+), 45 deletions(-) create mode 100644 chat/__init__.py create mode 100644 chat/admin.py create mode 100644 chat/apps.py create mode 100644 chat/migrations/0001_initial.py create mode 100644 chat/migrations/__init__.py create mode 100644 chat/models.py create mode 100644 chat/tests.py create mode 100644 chat/urls.py create mode 100644 chat/views.py create mode 100644 draw/migrations/0003_alter_teamdraw_options.py create mode 100644 tfjm/permissions.py diff --git a/chat/__init__.py b/chat/__init__.py new file mode 100644 index 0000000..80ea069 --- /dev/null +++ b/chat/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/chat/admin.py b/chat/admin.py new file mode 100644 index 0000000..2c467d4 --- /dev/null +++ b/chat/admin.py @@ -0,0 +1,22 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.contrib import admin + +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',) + search_fields = ('name', 'tournament__name', 'team__name', 'team__trigram',) + autocomplete_fields = ('tournament', 'pool', 'team', 'invited', ) + + +@admin.register(Message) +class MessageAdmin(admin.ModelAdmin): + list_display = ('channel', 'author', 'created_at', 'updated_at', 'content',) + list_filter = ('channel', 'created_at', 'updated_at',) + search_fields = ('author__username', 'author__first_name', 'author__last_name', 'content',) + autocomplete_fields = ('channel', 'author',) diff --git a/chat/apps.py b/chat/apps.py new file mode 100644 index 0000000..a17b8f5 --- /dev/null +++ b/chat/apps.py @@ -0,0 +1,9 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.apps import AppConfig + + +class ChatConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "chat" diff --git a/chat/migrations/0001_initial.py b/chat/migrations/0001_initial.py new file mode 100644 index 0000000..75f8f15 --- /dev/null +++ b/chat/migrations/0001_initial.py @@ -0,0 +1,198 @@ +# Generated by Django 5.0.3 on 2024-04-27 06:48 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("participation", "0013_alter_pool_options_pool_room"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Channel", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="name")), + ( + "read_access", + models.PositiveSmallIntegerField( + choices=[ + ("anonymous", "Everyone, including anonymous users"), + ("authenticated", "Authenticated users"), + ("volunteer", "All volunteers"), + ("tournament", "All members of a given tournament"), + ("organizer", "Tournament organizers only"), + ( + "jury_president", + "Tournament organizers and jury presidents of the tournament", + ), + ("jury", "Jury members of the pool"), + ("pool", "Jury members and participants of the pool"), + ( + "team", + "Members of the team and organizers of concerned tournaments", + ), + ( + "private", + "Private, reserved to explicit authorized users", + ), + ("admin", "Admin users"), + ], + verbose_name="read permission", + ), + ), + ( + "write_access", + models.PositiveSmallIntegerField( + choices=[ + ("anonymous", "Everyone, including anonymous users"), + ("authenticated", "Authenticated users"), + ("volunteer", "All volunteers"), + ("tournament", "All members of a given tournament"), + ("organizer", "Tournament organizers only"), + ( + "jury_president", + "Tournament organizers and jury presidents of the tournament", + ), + ("jury", "Jury members of the pool"), + ("pool", "Jury members and participants of the pool"), + ( + "team", + "Members of the team and organizers of concerned tournaments", + ), + ( + "private", + "Private, reserved to explicit authorized users", + ), + ("admin", "Admin users"), + ], + verbose_name="write permission", + ), + ), + ( + "private", + models.BooleanField( + default=False, + help_text="If checked, only users who have been explicitly added to the channel will be able to access it.", + verbose_name="private", + ), + ), + ( + "invited", + models.ManyToManyField( + blank=True, + help_text="Extra users who have been invited to the channel, in addition to the permitted group of the channel.", + related_name="+", + to=settings.AUTH_USER_MODEL, + verbose_name="invited users", + ), + ), + ( + "pool", + models.ForeignKey( + blank=True, + default=None, + help_text="For a permission that concerns a pool, indicates what is the concerned pool.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="chat_channels", + to="participation.pool", + verbose_name="pool", + ), + ), + ( + "team", + models.ForeignKey( + blank=True, + default=None, + help_text="For a permission that concerns a team, indicates what is the concerned team.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="chat_channels", + to="participation.team", + verbose_name="team", + ), + ), + ( + "tournament", + models.ForeignKey( + blank=True, + default=None, + help_text="For a permission that concerns a tournament, indicates what is the concerned tournament.", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="chat_channels", + to="participation.tournament", + verbose_name="tournament", + ), + ), + ], + options={ + "verbose_name": "channel", + "verbose_name_plural": "channels", + "ordering": ("name",), + }, + ), + migrations.CreateModel( + name="Message", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="updated at"), + ), + ("content", models.TextField(verbose_name="content")), + ( + "author", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="chat_messages", + to=settings.AUTH_USER_MODEL, + verbose_name="author", + ), + ), + ( + "channel", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="messages", + to="chat.channel", + verbose_name="channel", + ), + ), + ], + options={ + "verbose_name": "message", + "verbose_name_plural": "messages", + "ordering": ("created_at",), + }, + ), + ] diff --git a/chat/migrations/__init__.py b/chat/migrations/__init__.py new file mode 100644 index 0000000..80ea069 --- /dev/null +++ b/chat/migrations/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/chat/models.py b/chat/models.py new file mode 100644 index 0000000..b5a35e7 --- /dev/null +++ b/chat/models.py @@ -0,0 +1,116 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.db import models +from django.utils.text import format_lazy +from django.utils.translation import gettext_lazy as _ +from tfjm.permissions import PermissionType + + +class Channel(models.Model): + name = models.CharField( + max_length=255, + verbose_name=_("name"), + ) + + read_access = models.PositiveSmallIntegerField( + verbose_name=_("read permission"), + choices=PermissionType, + ) + + write_access = models.PositiveSmallIntegerField( + verbose_name=_("write permission"), + choices=PermissionType, + ) + + tournament = models.ForeignKey( + 'participation.Tournament', + on_delete=models.CASCADE, + blank=True, + null=True, + default=None, + verbose_name=_("tournament"), + related_name='chat_channels', + help_text=_("For a permission that concerns a tournament, indicates what is the concerned tournament."), + ) + + pool = models.ForeignKey( + 'participation.Pool', + on_delete=models.CASCADE, + blank=True, + null=True, + default=None, + verbose_name=_("pool"), + related_name='chat_channels', + help_text=_("For a permission that concerns a pool, indicates what is the concerned pool."), + ) + + team = models.ForeignKey( + 'participation.Team', + on_delete=models.CASCADE, + blank=True, + null=True, + default=None, + verbose_name=_("team"), + related_name='chat_channels', + help_text=_("For a permission that concerns a team, indicates what is the concerned team."), + ) + + private = models.BooleanField( + verbose_name=_("private"), + default=False, + help_text=_("If checked, only users who have been explicitly added to the channel will be able to access it."), + ) + + invited = models.ManyToManyField( + 'auth.User', + verbose_name=_("invited users"), + related_name='+', + blank=True, + help_text=_("Extra users who have been invited to the channel, " + "in addition to the permitted group of the channel."), + ) + + def __str__(self): + return format_lazy(_("Channel {name}"), name=self.name) + + class Meta: + verbose_name = _("channel") + verbose_name_plural = _("channels") + ordering = ('name',) + + +class Message(models.Model): + channel = models.ForeignKey( + Channel, + on_delete=models.CASCADE, + verbose_name=_("channel"), + related_name='messages', + ) + + author = models.ForeignKey( + 'auth.User', + verbose_name=_("author"), + on_delete=models.SET_NULL, + null=True, + related_name='chat_messages', + ) + + created_at = models.DateTimeField( + verbose_name=_("created at"), + auto_now_add=True, + ) + + updated_at = models.DateTimeField( + verbose_name=_("updated at"), + auto_now=True, + ) + + content = models.TextField( + verbose_name=_("content"), + ) + + class Meta: + verbose_name = _("message") + verbose_name_plural = _("messages") + ordering = ('created_at',) diff --git a/chat/tests.py b/chat/tests.py new file mode 100644 index 0000000..80ea069 --- /dev/null +++ b/chat/tests.py @@ -0,0 +1,2 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/chat/urls.py b/chat/urls.py new file mode 100644 index 0000000..80ea069 --- /dev/null +++ b/chat/urls.py @@ -0,0 +1,2 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/chat/views.py b/chat/views.py new file mode 100644 index 0000000..80ea069 --- /dev/null +++ b/chat/views.py @@ -0,0 +1,2 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/draw/migrations/0003_alter_teamdraw_options.py b/draw/migrations/0003_alter_teamdraw_options.py new file mode 100644 index 0000000..8725ba9 --- /dev/null +++ b/draw/migrations/0003_alter_teamdraw_options.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.3 on 2024-04-22 22:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("draw", "0002_alter_teamdraw_purposed"), + ] + + operations = [ + migrations.AlterModelOptions( + name="teamdraw", + options={ + "ordering": ( + "round__draw__tournament__name", + "round__number", + "pool__letter", + "passage_index", + "choice_dice", + "passage_dice", + ), + "verbose_name": "team draw", + "verbose_name_plural": "team draws", + }, + ), + ] diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 5043b78..13cd29e 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-22 23:36+0200\n" +"POT-Creation-Date: 2024-04-27 08:46+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Emmy D'Anello \n" "Language-Team: LANGUAGE \n" @@ -21,14 +21,21 @@ msgstr "" msgid "API" msgstr "API" -#: draw/admin.py:39 draw/admin.py:57 draw/admin.py:75 -#: participation/admin.py:109 participation/models.py:253 -#: participation/tables.py:88 -msgid "teams" -msgstr "équipes" +#: chat/models.py:13 participation/models.py:35 participation/models.py:263 +#: participation/tables.py:18 participation/tables.py:34 +msgid "name" +msgstr "nom" -#: draw/admin.py:53 draw/admin.py:71 draw/admin.py:88 draw/models.py:26 -#: participation/admin.py:79 participation/admin.py:140 +#: chat/models.py:17 +msgid "read permission" +msgstr "permission de lecture" + +#: chat/models.py:22 +msgid "write permission" +msgstr "permission d'écriture" + +#: chat/models.py:32 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 #: registration/models.py:756 @@ -36,6 +43,112 @@ msgstr "équipes" msgid "tournament" msgstr "tournoi" +#: chat/models.py:34 +msgid "" +"For a permission that concerns a tournament, indicates what is the concerned " +"tournament." +msgstr "" +"Pour une permission qui concerne un tournoi, indique quel est le tournoi " +"concerné." + +#: chat/models.py:43 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:45 +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:54 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 +#: registration/models.py:157 registration/models.py:747 +#: registration/tables.py:39 +#: registration/templates/registration/payment_form.html:52 +msgid "team" +msgstr "équipe" + +#: chat/models.py:56 +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:60 +msgid "private" +msgstr "privé" + +#: chat/models.py:62 +msgid "" +"If checked, only users who have been explicitly added to the channel will be " +"able to access it." +msgstr "" +"Si sélectionné, seul⋅es les utilisateur⋅rices qui ont été explicitement " +"ajouté⋅es au canal pourront y accéder." + +#: chat/models.py:67 +msgid "invited users" +msgstr "Utilisateur⋅rices invité" + +#: chat/models.py:70 +msgid "" +"Extra users who have been invited to the channel, in addition to the " +"permitted group of the channel." +msgstr "" +"Utilisateur⋅rices supplémentaires qui ont été invité⋅es au canal, en plus du " +"groupe autorisé du canal." + +#: chat/models.py:75 +#, python-brace-format +msgid "Channel {name}" +msgstr "Canal {name}" + +#: chat/models.py:78 chat/models.py:87 +msgid "channel" +msgstr "canal" + +#: chat/models.py:79 +msgid "channels" +msgstr "canaux" + +#: chat/models.py:93 +msgid "author" +msgstr "auteur⋅rice" + +#: chat/models.py:100 +msgid "created at" +msgstr "créé le" + +#: chat/models.py:105 +msgid "updated at" +msgstr "modifié le" + +#: chat/models.py:110 +msgid "content" +msgstr "contenu" + +#: chat/models.py:114 +msgid "message" +msgstr "message" + +#: chat/models.py:115 +msgid "messages" +msgstr "messages" + +#: draw/admin.py:39 draw/admin.py:57 draw/admin.py:75 +#: participation/admin.py:109 participation/models.py:253 +#: participation/tables.py:88 +msgid "teams" +msgstr "équipes" + #: draw/admin.py:92 draw/models.py:234 draw/models.py:448 #: participation/models.py:939 msgid "round" @@ -213,12 +326,6 @@ msgstr "L'instance complète de la poule." msgid "Pool {letter}{number}" msgstr "Poule {letter}{number}" -#: 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" - #: draw/models.py:430 participation/models.py:1435 msgid "pools" msgstr "poules" @@ -352,15 +459,6 @@ msgstr "Tirer un problème pour" msgid "Pb." msgstr "Pb." -#: 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 -#: registration/models.py:157 registration/models.py:747 -#: registration/tables.py:39 -#: registration/templates/registration/payment_form.html:52 -msgid "team" -msgstr "équipe" - #: draw/templates/draw/tournament_content.html:287 #: draw/templates/draw/tournament_content.html:288 #: draw/templates/draw/tournament_content.html:289 @@ -589,11 +687,6 @@ msgstr "Ce⋅tte défenseur⋅se ne travaille pas sur ce problème." msgid "The PDF file must not have more than 2 pages." msgstr "Le fichier PDF ne doit pas avoir plus de 2 pages." -#: participation/models.py:35 participation/models.py:263 -#: participation/tables.py:18 participation/tables.py:34 -msgid "name" -msgstr "nom" - #: participation/models.py:41 participation/tables.py:39 msgid "trigram" msgstr "trigramme" @@ -1219,16 +1312,6 @@ msgstr "Pas d'équipe définie" msgid "Update" msgstr "Modifier" -#: participation/templates/participation/chat.html:7 -msgid "" -"The chat feature is now out of usage. If you feel that having a chat feature " -"between participants is important, for example to build a team, please " -"contact us." -msgstr "" -"La fonctionnalité de chat est désormais hors-service. Si vous pensez " -"qu'avoir un chat entre les participant⋅es est important, par exemple pour " -"former une équipe, merci de nous contacter." - #: participation/templates/participation/create_team.html:11 #: participation/templates/participation/tournament_form.html:14 #: tfjm/templates/base.html:80 @@ -3484,11 +3567,55 @@ msgstr "Autorisation parentale de {student}.{ext}" msgid "Payment receipt of {registrations}.{ext}" msgstr "Justificatif de paiement de {registrations}.{ext}" -#: tfjm/settings.py:167 +#: tfjm/permissions.py:9 +msgid "Everyone, including anonymous users" +msgstr "Tout le monde, incluant les utilisateur⋅rices anonymes" + +#: tfjm/permissions.py:10 +msgid "Authenticated users" +msgstr "Utilisateur⋅rices connecté⋅es" + +#: tfjm/permissions.py:11 +msgid "All volunteers" +msgstr "Toustes les bénévoles" + +#: tfjm/permissions.py:12 +msgid "All members of a given tournament" +msgstr "Toustes les membres d'un tournoi donné" + +#: tfjm/permissions.py:13 +msgid "Tournament organizers only" +msgstr "Organisateur⋅rices du tournoi seulement" + +#: tfjm/permissions.py:14 +msgid "Tournament organizers and jury presidents of the tournament" +msgstr "Organisateur⋅rices du tournoi et président⋅es de jury du tournoi" + +#: tfjm/permissions.py:15 +msgid "Jury members of the pool" +msgstr "Membres du jury de la poule" + +#: tfjm/permissions.py:16 +msgid "Jury members and participants of the pool" +msgstr "Membre du jury et participant⋅es de la poule" + +#: tfjm/permissions.py:17 +msgid "Members of the team and organizers of concerned tournaments" +msgstr "Membres de l'équipe et organisateur⋅rices des tournois concernés" + +#: tfjm/permissions.py:18 +msgid "Private, reserved to explicit authorized users" +msgstr "Privé, réservé aux utilisateur⋅rices explicitement autorisé⋅es" + +#: tfjm/permissions.py:19 +msgid "Admin users" +msgstr "Administrateur⋅rices" + +#: tfjm/settings.py:168 msgid "English" msgstr "Anglais" -#: tfjm/settings.py:168 +#: tfjm/settings.py:169 msgid "French" msgstr "Français" @@ -3645,8 +3772,3 @@ msgstr "Aucun résultat." #: tfjm/templates/sidebar.html:10 tfjm/templates/sidebar.html:21 msgid "Informations" msgstr "Informations" - -#~ msgid "Can't determine the pool size. Are you sure your file is correct?" -#~ msgstr "" -#~ "Impossible de déterminer la taille de la poule. Êtes-vous sûr⋅e que le " -#~ "fichier est correct ?" diff --git a/tfjm/permissions.py b/tfjm/permissions.py new file mode 100644 index 0000000..f29d5c6 --- /dev/null +++ b/tfjm/permissions.py @@ -0,0 +1,19 @@ +# Copyright (C) 2024 by Animath +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class PermissionType(models.TextChoices): + ANONYMOUS = 'anonymous', _("Everyone, including anonymous users") + AUTHENTICATED = 'authenticated', _("Authenticated users") + VOLUNTEER = 'volunteer', _("All volunteers") + TOURNAMENT_MEMBER = 'tournament', _("All members of a given tournament") + TOURNAMENT_ORGANIZER = 'organizer', _("Tournament organizers only") + TOURNAMENT_JURY_PRESIDENT = 'jury_president', _("Tournament organizers and jury presidents of the tournament") + JURY_MEMBER = 'jury', _("Jury members of the pool") + POOL_MEMBER = 'pool', _("Jury members and participants of the pool") + TEAM_MEMBER = 'team', _("Members of the team and organizers of concerned tournaments") + PRIVATE = 'private', _("Private, reserved to explicit authorized users") + ADMIN = 'admin', _("Admin users") diff --git a/tfjm/settings.py b/tfjm/settings.py index c1b98bb..eb1c53f 100644 --- a/tfjm/settings.py +++ b/tfjm/settings.py @@ -68,6 +68,7 @@ INSTALLED_APPS = [ 'rest_framework.authtoken', 'api', + 'chat', 'draw', 'registration', 'participation', diff --git a/tfjm/urls.py b/tfjm/urls.py index 1185dbc..a397f07 100644 --- a/tfjm/urls.py +++ b/tfjm/urls.py @@ -37,6 +37,7 @@ urlpatterns = [ path('search/', AdminSearchView.as_view(), name="haystack_search"), path('api/', include('api.urls')), + # path('chat/', include('chat.urls')), path('draw/', include('draw.urls')), path('participation/', include('participation.urls')), path('registration/', include('registration.urls')),