mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-11-04 09:42:10 +01:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			docs
			...
			1abe463575
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					
						
						
							
						
						1abe463575
	
				 | 
					
					
						||
| 
						 | 
					
						
						
							
						
						5b0081a531
	
				 | 
					
					
						||
| 
						 | 
					
						
						
							
						
						06c82a239d
	
				 | 
					
					
						||
| 
						 | 
					
						
						
							
						
						f8725cf8a9
	
				 | 
					
					
						
							
								
								
									
										2
									
								
								chat/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								chat/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					# Copyright (C) 2024 by Animath
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
							
								
								
									
										22
									
								
								chat/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								chat/admin.py
									
									
									
									
									
										Normal file
									
								
							@@ -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',)
 | 
				
			||||||
							
								
								
									
										9
									
								
								chat/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								chat/apps.py
									
									
									
									
									
										Normal file
									
								
							@@ -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"
 | 
				
			||||||
							
								
								
									
										75
									
								
								chat/consumers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								chat/consumers.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					# Copyright (C) 2024 by Animath
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from channels.generic.websocket import AsyncJsonWebsocketConsumer
 | 
				
			||||||
 | 
					from django.contrib.auth.models import User
 | 
				
			||||||
 | 
					from registration.models import Registration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .models import Channel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChatConsumer(AsyncJsonWebsocketConsumer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This consumer manages the websocket of the chat interface.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    async def connect(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        This function is called when a new websocket is trying to connect to the server.
 | 
				
			||||||
 | 
					        We accept only if this is a user of a team of the associated tournament, or a volunteer
 | 
				
			||||||
 | 
					        of the tournament.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if '_fake_user_id' in self.scope['session']:
 | 
				
			||||||
 | 
					            self.scope['user'] = await User.objects.aget(pk=self.scope['session']['_fake_user_id'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Fetch the registration of the current user
 | 
				
			||||||
 | 
					        user = self.scope['user']
 | 
				
			||||||
 | 
					        if user.is_anonymous:
 | 
				
			||||||
 | 
					            # User is not authenticated
 | 
				
			||||||
 | 
					            await self.close()
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        reg = await Registration.objects.aget(user_id=user.id)
 | 
				
			||||||
 | 
					        self.registration = reg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Accept the connection
 | 
				
			||||||
 | 
					        await self.accept()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def disconnect(self, close_code) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Called when the websocket got disconnected, for any reason.
 | 
				
			||||||
 | 
					        :param close_code: The error code.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if self.scope['user'].is_anonymous:
 | 
				
			||||||
 | 
					            # User is not authenticated
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def receive_json(self, content, **kwargs):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Called when the client sends us some data, parsed as JSON.
 | 
				
			||||||
 | 
					        :param content: The sent data, decoded from JSON text. Must content a `type` field.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        match content['type']:
 | 
				
			||||||
 | 
					            case 'fetch_channels':
 | 
				
			||||||
 | 
					                await self.fetch_channels()
 | 
				
			||||||
 | 
					            case unknown:
 | 
				
			||||||
 | 
					                print("Unknown message type:", unknown)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def fetch_channels(self) -> None:
 | 
				
			||||||
 | 
					        user = self.scope['user']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        read_channels = await Channel.get_accessible_channels(user, 'read')
 | 
				
			||||||
 | 
					        write_channels = await Channel.get_accessible_channels(user, 'write')
 | 
				
			||||||
 | 
					        print([channel async for channel in write_channels.all()])
 | 
				
			||||||
 | 
					        message = {
 | 
				
			||||||
 | 
					            'type': 'fetch_channels',
 | 
				
			||||||
 | 
					            'channels': [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    'id': channel.id,
 | 
				
			||||||
 | 
					                    'name': channel.name,
 | 
				
			||||||
 | 
					                    'read_access': True,
 | 
				
			||||||
 | 
					                    'write_access': await write_channels.acontains(channel),
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                async for channel in read_channels.all()
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await self.send_json(message)
 | 
				
			||||||
							
								
								
									
										200
									
								
								chat/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								chat/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,200 @@
 | 
				
			|||||||
 | 
					# Generated by Django 5.0.3 on 2024-04-27 07:00
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.CharField(
 | 
				
			||||||
 | 
					                        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"),
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                        max_length=16,
 | 
				
			||||||
 | 
					                        verbose_name="read permission",
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                (
 | 
				
			||||||
 | 
					                    "write_access",
 | 
				
			||||||
 | 
					                    models.CharField(
 | 
				
			||||||
 | 
					                        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"),
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                        max_length=16,
 | 
				
			||||||
 | 
					                        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",),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										2
									
								
								chat/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								chat/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					# Copyright (C) 2024 by Animath
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
							
								
								
									
										188
									
								
								chat/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								chat/models.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
				
			|||||||
 | 
					# Copyright (C) 2024 by Animath
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.contrib.auth.models import User
 | 
				
			||||||
 | 
					from django.db import models
 | 
				
			||||||
 | 
					from django.db.models import Q, QuerySet
 | 
				
			||||||
 | 
					from django.utils.text import format_lazy
 | 
				
			||||||
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
 | 
					from participation.models import Tournament
 | 
				
			||||||
 | 
					from registration.models import ParticipantRegistration, Registration, VolunteerRegistration
 | 
				
			||||||
 | 
					from tfjm.permissions import PermissionType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Channel(models.Model):
 | 
				
			||||||
 | 
					    name = models.CharField(
 | 
				
			||||||
 | 
					        max_length=255,
 | 
				
			||||||
 | 
					        verbose_name=_("name"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    read_access = models.CharField(
 | 
				
			||||||
 | 
					        max_length=16,
 | 
				
			||||||
 | 
					        verbose_name=_("read permission"),
 | 
				
			||||||
 | 
					        choices=PermissionType,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    write_access = models.CharField(
 | 
				
			||||||
 | 
					        max_length=16,
 | 
				
			||||||
 | 
					        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 str(format_lazy(_("Channel {name}"), name=self.name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    async def get_accessible_channels(user: User, permission_type: str = 'read') -> QuerySet["Channel"]:
 | 
				
			||||||
 | 
					        permission_type = 'write_access' if 'write' in permission_type.lower() else 'read_access'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        qs = Channel.objects.none()
 | 
				
			||||||
 | 
					        if user.is_anonymous:
 | 
				
			||||||
 | 
					            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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if registration.is_admin:
 | 
				
			||||||
 | 
					            return Channel.objects.all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if registration.is_volunteer:
 | 
				
			||||||
 | 
					            registration = await VolunteerRegistration.objects \
 | 
				
			||||||
 | 
					                .prefetch_related('jury_in__tournament', 'organized_tournaments').aget(user_id=user.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            qs |= Channel.objects.filter(**{permission_type: PermissionType.VOLUNTEER})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            qs |= Channel.objects.filter(Q(tournament__in=registration.interesting_tournaments),
 | 
				
			||||||
 | 
					                                         **{permission_type: PermissionType.TOURNAMENT_MEMBER})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            qs |= Channel.objects.filter(Q(tournament__in=registration.organized_tournaments.all()),
 | 
				
			||||||
 | 
					                                         **{permission_type: PermissionType.TOURNAMENT_ORGANIZER})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            qs |= Channel.objects.filter(Q(tournament__pools__in=registration.pools_presided.all())
 | 
				
			||||||
 | 
					                                         | Q(tournament__in=registration.organized_tournaments.all()),
 | 
				
			||||||
 | 
					                                         **{permission_type: PermissionType.TOURNAMENT_JURY_PRESIDENT})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            qs |= Channel.objects.filter(Q(pool__in=registration.jury_in.all())
 | 
				
			||||||
 | 
					                                         | Q(pool__tournament__in=registration.organized_tournaments.all())
 | 
				
			||||||
 | 
					                                         | Q(pool__tournament__pools__in=registration.pools_presided.all()),
 | 
				
			||||||
 | 
					                                         **{permission_type: PermissionType.JURY_MEMBER})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            qs |= Channel.objects.filter(Q(pool__in=registration.jury_in.all())
 | 
				
			||||||
 | 
					                                         | Q(pool__tournament__in=registration.organized_tournaments.all())
 | 
				
			||||||
 | 
					                                         | Q(pool__tournament__pools__in=registration.pools_presided.all()),
 | 
				
			||||||
 | 
					                                         **{permission_type: PermissionType.POOL_MEMBER})
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            registration = await ParticipantRegistration.objects \
 | 
				
			||||||
 | 
					                .prefetch_related('team__participation__pools', 'team__participation__tournament').aget(user_id=user.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            team = registration.team
 | 
				
			||||||
 | 
					            tournaments = []
 | 
				
			||||||
 | 
					            if team.participation.valid:
 | 
				
			||||||
 | 
					                tournaments.append(team.participation.tournament)
 | 
				
			||||||
 | 
					            if team.participation.final:
 | 
				
			||||||
 | 
					                tournaments.append(await Tournament.objects.aget(final=True))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            qs |= Channel.objects.filter(Q(tournament__in=tournaments),
 | 
				
			||||||
 | 
					                                         **{permission_type: PermissionType.TOURNAMENT_MEMBER})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            qs |= Channel.objects.filter(Q(pool__in=team.participation.pools.all()),
 | 
				
			||||||
 | 
					                                         **{permission_type: PermissionType.POOL_MEMBER})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            qs |= Channel.objects.filter(Q(team=team),
 | 
				
			||||||
 | 
					                                         **{permission_type: PermissionType.TEAM_MEMBER})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        qs |= Channel.objects.filter(invited=user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print(user)
 | 
				
			||||||
 | 
					        print(qs.query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return qs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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',)
 | 
				
			||||||
							
								
								
									
										59
									
								
								chat/static/chat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								chat/static/chat.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					(async () => {
 | 
				
			||||||
 | 
					    // check notification permission
 | 
				
			||||||
 | 
					    // This is useful to alert people that they should do something
 | 
				
			||||||
 | 
					    await Notification.requestPermission()
 | 
				
			||||||
 | 
					})()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Display a new notification with the given title and the given body.
 | 
				
			||||||
 | 
					 * @param title The title of the notification
 | 
				
			||||||
 | 
					 * @param body The body of the notification
 | 
				
			||||||
 | 
					 * @param timeout The time (in milliseconds) after that the notification automatically closes. 0 to make indefinite. Default to 5000 ms.
 | 
				
			||||||
 | 
					 * @return Notification
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function showNotification(title, body, timeout = 5000) {
 | 
				
			||||||
 | 
					    let notif = new Notification(title, {'body': body, 'icon': "/static/tfjm.svg"})
 | 
				
			||||||
 | 
					    if (timeout)
 | 
				
			||||||
 | 
					        setTimeout(() => notif.close(), timeout)
 | 
				
			||||||
 | 
					    return notif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', () => {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Process the received data from the server.
 | 
				
			||||||
 | 
					     * @param data The received message
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    function processMessage(data) {
 | 
				
			||||||
 | 
					        // TODO Implement chat protocol
 | 
				
			||||||
 | 
					        console.log(data)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function setupSocket(nextDelay = 1000) {
 | 
				
			||||||
 | 
					        // Open a global websocket
 | 
				
			||||||
 | 
					        socket = new WebSocket(
 | 
				
			||||||
 | 
					            (document.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/ws/chat/'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Listen on websockets and process messages from the server
 | 
				
			||||||
 | 
					        socket.addEventListener('message', e => {
 | 
				
			||||||
 | 
					            // Parse received data as JSON
 | 
				
			||||||
 | 
					            const data = JSON.parse(e.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            processMessage(data)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Manage errors
 | 
				
			||||||
 | 
					        socket.addEventListener('close', e => {
 | 
				
			||||||
 | 
					            console.error('Chat socket closed unexpectedly, restarting…')
 | 
				
			||||||
 | 
					            setTimeout(() => setupSocket(2 * nextDelay), nextDelay)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        socket.addEventListener('open', e => {
 | 
				
			||||||
 | 
					            socket.send(JSON.stringify({
 | 
				
			||||||
 | 
					                'type': 'fetch_channels',
 | 
				
			||||||
 | 
					            }))
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setupSocket()
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										12
									
								
								chat/templates/chat/chat.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								chat/templates/chat/chat.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% load static %}
 | 
				
			||||||
 | 
					{% load i18n %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block extrajavascript %}
 | 
				
			||||||
 | 
					    {# This script contains all data for the chat management #}
 | 
				
			||||||
 | 
					    <script src="{% static 'chat.js' %}"></script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
							
								
								
									
										2
									
								
								chat/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								chat/tests.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					# Copyright (C) 2024 by Animath
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
							
								
								
									
										13
									
								
								chat/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								chat/urls.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					# Copyright (C) 2024 by Animath
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.urls import path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .views import ChatView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app_name = 'chat'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					urlpatterns = [
 | 
				
			||||||
 | 
					    path('', ChatView.as_view(), name='chat'),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										13
									
								
								chat/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								chat/views.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					# Copyright (C) 2024 by Animath
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.contrib.auth.mixins import LoginRequiredMixin
 | 
				
			||||||
 | 
					from django.views.generic import TemplateView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChatView(LoginRequiredMixin, TemplateView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This view is the main interface of the chat system, which is working
 | 
				
			||||||
 | 
					    with Javascript and websockets.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    template_name = "chat/chat.html"
 | 
				
			||||||
							
								
								
									
										28
									
								
								draw/migrations/0003_alter_teamdraw_options.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								draw/migrations/0003_alter_teamdraw_options.py
									
									
									
									
									
										Normal file
									
								
							@@ -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",
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -1,10 +0,0 @@
 | 
				
			|||||||
# Copyright (C) 2023 by Animath
 | 
					 | 
				
			||||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.urls import path
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from . import consumers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
websocket_urlpatterns = [
 | 
					 | 
				
			||||||
    path("ws/draw/", consumers.DrawConsumer.as_asgi()),
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
@@ -14,8 +14,8 @@ from django.contrib.sites.models import Site
 | 
				
			|||||||
from django.test import TestCase
 | 
					from django.test import TestCase
 | 
				
			||||||
from django.urls import reverse
 | 
					from django.urls import reverse
 | 
				
			||||||
from participation.models import Team, Tournament
 | 
					from participation.models import Team, Tournament
 | 
				
			||||||
 | 
					from tfjm import routing as websocket_routing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import routing
 | 
					 | 
				
			||||||
from .models import Draw, Pool, Round, TeamDraw
 | 
					from .models import Draw, Pool, Round, TeamDraw
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -55,7 +55,7 @@ class TestDraw(TestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # Connect to Websocket
 | 
					        # Connect to Websocket
 | 
				
			||||||
        headers = [(b'cookie', self.async_client.cookies.output(header='', sep='; ').encode())]
 | 
					        headers = [(b'cookie', self.async_client.cookies.output(header='', sep='; ').encode())]
 | 
				
			||||||
        communicator = WebsocketCommunicator(AuthMiddlewareStack(URLRouter(routing.websocket_urlpatterns)),
 | 
					        communicator = WebsocketCommunicator(AuthMiddlewareStack(URLRouter(websocket_routing.websocket_urlpatterns)),
 | 
				
			||||||
                                             "/ws/draw/", headers)
 | 
					                                             "/ws/draw/", headers)
 | 
				
			||||||
        connected, subprotocol = await communicator.connect()
 | 
					        connected, subprotocol = await communicator.connect()
 | 
				
			||||||
        self.assertTrue(connected)
 | 
					        self.assertTrue(connected)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ msgid ""
 | 
				
			|||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Project-Id-Version: TFJM\n"
 | 
					"Project-Id-Version: TFJM\n"
 | 
				
			||||||
"Report-Msgid-Bugs-To: \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"
 | 
					"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
				
			||||||
"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
 | 
					"Last-Translator: Emmy D'Anello <emmy.danello@animath.fr>\n"
 | 
				
			||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
					"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
				
			||||||
@@ -21,14 +21,21 @@ msgstr ""
 | 
				
			|||||||
msgid "API"
 | 
					msgid "API"
 | 
				
			||||||
msgstr "API"
 | 
					msgstr "API"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: draw/admin.py:39 draw/admin.py:57 draw/admin.py:75
 | 
					#: chat/models.py:13 participation/models.py:35 participation/models.py:263
 | 
				
			||||||
#: participation/admin.py:109 participation/models.py:253
 | 
					#: participation/tables.py:18 participation/tables.py:34
 | 
				
			||||||
#: participation/tables.py:88
 | 
					msgid "name"
 | 
				
			||||||
msgid "teams"
 | 
					msgstr "nom"
 | 
				
			||||||
msgstr "équipes"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: draw/admin.py:53 draw/admin.py:71 draw/admin.py:88 draw/models.py:26
 | 
					#: chat/models.py:17
 | 
				
			||||||
#: participation/admin.py:79 participation/admin.py:140
 | 
					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/admin.py:171 participation/models.py:693
 | 
				
			||||||
#: participation/models.py:717 participation/models.py:935
 | 
					#: participation/models.py:717 participation/models.py:935
 | 
				
			||||||
#: registration/models.py:756
 | 
					#: registration/models.py:756
 | 
				
			||||||
@@ -36,6 +43,112 @@ msgstr "équipes"
 | 
				
			|||||||
msgid "tournament"
 | 
					msgid "tournament"
 | 
				
			||||||
msgstr "tournoi"
 | 
					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
 | 
					#: draw/admin.py:92 draw/models.py:234 draw/models.py:448
 | 
				
			||||||
#: participation/models.py:939
 | 
					#: participation/models.py:939
 | 
				
			||||||
msgid "round"
 | 
					msgid "round"
 | 
				
			||||||
@@ -213,12 +326,6 @@ msgstr "L'instance complète de la poule."
 | 
				
			|||||||
msgid "Pool {letter}{number}"
 | 
					msgid "Pool {letter}{number}"
 | 
				
			||||||
msgstr "Poule {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
 | 
					#: draw/models.py:430 participation/models.py:1435
 | 
				
			||||||
msgid "pools"
 | 
					msgid "pools"
 | 
				
			||||||
msgstr "poules"
 | 
					msgstr "poules"
 | 
				
			||||||
@@ -352,15 +459,6 @@ msgstr "Tirer un problème pour"
 | 
				
			|||||||
msgid "Pb."
 | 
					msgid "Pb."
 | 
				
			||||||
msgstr "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:287
 | 
				
			||||||
#: draw/templates/draw/tournament_content.html:288
 | 
					#: draw/templates/draw/tournament_content.html:288
 | 
				
			||||||
#: draw/templates/draw/tournament_content.html:289
 | 
					#: 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."
 | 
					msgid "The PDF file must not have more than 2 pages."
 | 
				
			||||||
msgstr "Le fichier PDF ne doit pas avoir plus de 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
 | 
					#: participation/models.py:41 participation/tables.py:39
 | 
				
			||||||
msgid "trigram"
 | 
					msgid "trigram"
 | 
				
			||||||
msgstr "trigramme"
 | 
					msgstr "trigramme"
 | 
				
			||||||
@@ -1219,16 +1312,6 @@ msgstr "Pas d'équipe définie"
 | 
				
			|||||||
msgid "Update"
 | 
					msgid "Update"
 | 
				
			||||||
msgstr "Modifier"
 | 
					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/create_team.html:11
 | 
				
			||||||
#: participation/templates/participation/tournament_form.html:14
 | 
					#: participation/templates/participation/tournament_form.html:14
 | 
				
			||||||
#: tfjm/templates/base.html:80
 | 
					#: tfjm/templates/base.html:80
 | 
				
			||||||
@@ -3484,11 +3567,55 @@ msgstr "Autorisation parentale de {student}.{ext}"
 | 
				
			|||||||
msgid "Payment receipt of {registrations}.{ext}"
 | 
					msgid "Payment receipt of {registrations}.{ext}"
 | 
				
			||||||
msgstr "Justificatif de paiement de {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"
 | 
					msgid "English"
 | 
				
			||||||
msgstr "Anglais"
 | 
					msgstr "Anglais"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#: tfjm/settings.py:168
 | 
					#: tfjm/settings.py:169
 | 
				
			||||||
msgid "French"
 | 
					msgid "French"
 | 
				
			||||||
msgstr "Français"
 | 
					msgstr "Français"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -3645,8 +3772,3 @@ msgstr "Aucun résultat."
 | 
				
			|||||||
#: tfjm/templates/sidebar.html:10 tfjm/templates/sidebar.html:21
 | 
					#: tfjm/templates/sidebar.html:10 tfjm/templates/sidebar.html:21
 | 
				
			||||||
msgid "Informations"
 | 
					msgid "Informations"
 | 
				
			||||||
msgstr "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 ?"
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
{% extends "base.html" %}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{% load i18n %}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{% block content %}
 | 
					 | 
				
			||||||
    <div class="alert alert-warning">
 | 
					 | 
				
			||||||
    {% blocktrans trimmed %}
 | 
					 | 
				
			||||||
        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.
 | 
					 | 
				
			||||||
    {% endblocktrans %}
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
{% endblock %}
 | 
					 | 
				
			||||||
@@ -2,7 +2,6 @@
 | 
				
			|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.urls import path
 | 
					from django.urls import path
 | 
				
			||||||
from django.views.generic import TemplateView
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .views import CreateTeamView, FinalNotationSheetTemplateView, GSheetNotificationsView, JoinTeamView, \
 | 
					from .views import CreateTeamView, FinalNotationSheetTemplateView, GSheetNotificationsView, JoinTeamView, \
 | 
				
			||||||
    MyParticipationDetailView, MyTeamDetailView, NotationSheetsArchiveView, NoteUpdateView, ParticipationDetailView, \
 | 
					    MyParticipationDetailView, MyTeamDetailView, NotationSheetsArchiveView, NoteUpdateView, ParticipationDetailView, \
 | 
				
			||||||
@@ -74,5 +73,4 @@ urlpatterns = [
 | 
				
			|||||||
    path("pools/passages/<int:pk>/update/", PassageUpdateView.as_view(), name="passage_update"),
 | 
					    path("pools/passages/<int:pk>/update/", PassageUpdateView.as_view(), name="passage_update"),
 | 
				
			||||||
    path("pools/passages/<int:pk>/solution/", SynthesisUploadView.as_view(), name="upload_synthesis"),
 | 
					    path("pools/passages/<int:pk>/solution/", SynthesisUploadView.as_view(), name="upload_synthesis"),
 | 
				
			||||||
    path("pools/passages/notes/<int:pk>/", NoteUpdateView.as_view(), name="update_notes"),
 | 
					    path("pools/passages/notes/<int:pk>/", NoteUpdateView.as_view(), name="update_notes"),
 | 
				
			||||||
    path("chat/", TemplateView.as_view(template_name="participation/chat.html"), name="chat")
 | 
					 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,13 +22,13 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfjm.settings')
 | 
				
			|||||||
django_asgi_app = get_asgi_application()
 | 
					django_asgi_app = get_asgi_application()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# useful since the import must be done after the application initialization
 | 
					# useful since the import must be done after the application initialization
 | 
				
			||||||
import draw.routing  # noqa: E402, I202
 | 
					import tfjm.routing  # noqa: E402, I202
 | 
				
			||||||
 | 
					
 | 
				
			||||||
application = ProtocolTypeRouter(
 | 
					application = ProtocolTypeRouter(
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "http": django_asgi_app,
 | 
					        "http": django_asgi_app,
 | 
				
			||||||
        "websocket": AllowedHostsOriginValidator(
 | 
					        "websocket": AllowedHostsOriginValidator(
 | 
				
			||||||
            AuthMiddlewareStack(URLRouter(draw.routing.websocket_urlpatterns))
 | 
					            AuthMiddlewareStack(URLRouter(tfjm.routing.websocket_urlpatterns))
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								tfjm/permissions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								tfjm/permissions.py
									
									
									
									
									
										Normal file
									
								
							@@ -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")
 | 
				
			||||||
							
								
								
									
										11
									
								
								tfjm/routing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tfjm/routing.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					# Copyright (C) 2024 by Animath
 | 
				
			||||||
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import chat.consumers
 | 
				
			||||||
 | 
					from django.urls import path
 | 
				
			||||||
 | 
					import draw.consumers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					websocket_urlpatterns = [
 | 
				
			||||||
 | 
					    path("ws/chat/", chat.consumers.ChatConsumer.as_asgi()),
 | 
				
			||||||
 | 
					    path("ws/draw/", draw.consumers.DrawConsumer.as_asgi()),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
@@ -68,6 +68,7 @@ INSTALLED_APPS = [
 | 
				
			|||||||
    'rest_framework.authtoken',
 | 
					    'rest_framework.authtoken',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    'api',
 | 
					    'api',
 | 
				
			||||||
 | 
					    'chat',
 | 
				
			||||||
    'draw',
 | 
					    'draw',
 | 
				
			||||||
    'registration',
 | 
					    'registration',
 | 
				
			||||||
    'participation',
 | 
					    'participation',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,8 +62,8 @@
 | 
				
			|||||||
                    </li>
 | 
					                    </li>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
            <li class="nav-item active d-none">
 | 
					            <li class="nav-item active">
 | 
				
			||||||
                <a class="nav-link" href="{% url "participation:chat" %}">
 | 
					                <a class="nav-link" href="{% url "chat:chat" %}">
 | 
				
			||||||
                    <i class="fas fa-comments"></i> {% trans "Chat" %}
 | 
					                    <i class="fas fa-comments"></i> {% trans "Chat" %}
 | 
				
			||||||
                </a>
 | 
					                </a>
 | 
				
			||||||
            </li>
 | 
					            </li>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,6 +37,7 @@ urlpatterns = [
 | 
				
			|||||||
    path('search/', AdminSearchView.as_view(), name="haystack_search"),
 | 
					    path('search/', AdminSearchView.as_view(), name="haystack_search"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    path('api/', include('api.urls')),
 | 
					    path('api/', include('api.urls')),
 | 
				
			||||||
 | 
					    path('chat/', include('chat.urls')),
 | 
				
			||||||
    path('draw/', include('draw.urls')),
 | 
					    path('draw/', include('draw.urls')),
 | 
				
			||||||
    path('participation/', include('participation.urls')),
 | 
					    path('participation/', include('participation.urls')),
 | 
				
			||||||
    path('registration/', include('registration.urls')),
 | 
					    path('registration/', include('registration.urls')),
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user