Skip to content

Commit

Permalink
Merge branches 'ws-permissions', 'side-lengths' and 'BACKEND-C6X' int…
Browse files Browse the repository at this point in the history
…o develop
  • Loading branch information
tienne-B committed Jul 19, 2024
4 parents 2a55f06 + 0e2ff13 + 6e165ec + 00eb76c commit e822f25
Show file tree
Hide file tree
Showing 11 changed files with 42 additions and 17 deletions.
2 changes: 2 additions & 0 deletions tabbycat/adjallocation/consumers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from draw.consumers import BaseAdjudicatorContainerConsumer, EditDebateOrPanelWorkerMixin
from participants.prefetch import populate_win_counts
from tournaments.models import Round
from users.permissions import Permission

from .allocators.base import AdjudicatorAllocationError
from .allocators.hungarian import ConsensusHungarianAllocator, VotingHungarianAllocator
Expand All @@ -31,6 +32,7 @@ class PanelEditConsumer(BaseAdjudicatorContainerConsumer):
model = PreformedPanel
importance_serializer = SimplePanelImportanceSerializer
adjudicators_serializer = SimplePanelAllocationSerializer
access_permission = Permission.EDIT_PREFORMEDPANELS


class AdjudicatorAllocationWorkerConsumer(EditDebateOrPanelWorkerMixin):
Expand Down
2 changes: 2 additions & 0 deletions tabbycat/adjallocation/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class EditDebateAdjudicatorsView(BaseEditDebateOrPanelAdjudicatorsView):
prefetch_adjs = True # Fetched in full as get_serialised

view_permission = Permission.VIEW_DEBATEADJUDICATORS
edit_permission = Permission.EDIT_DEBATEADJUDICATORS

def get_extra_info(self):
info = super().get_extra_info()
Expand All @@ -108,6 +109,7 @@ class EditPanelAdjudicatorsView(BaseEditDebateOrPanelAdjudicatorsView):
page_title = gettext_lazy("Edit Panels")

view_permission = Permission.VIEW_PREFORMEDPANELS
edit_permission = Permission.EDIT_PREFORMEDPANELS

def get_extra_info(self):
info = super().get_extra_info()
Expand Down
5 changes: 4 additions & 1 deletion tabbycat/checkins/consumers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from options.utils import use_team_code_names_data_entry
from tournaments.mixins import TournamentWebsocketMixin
from users.permissions import has_permission, Permission

from .models import Event, Identifier
from .utils import get_unexpired_checkins
Expand All @@ -14,10 +15,12 @@ class CheckInEventConsumer(TournamentWebsocketMixin, JsonWebsocketConsumer):

group_prefix = 'checkins'

edit_permission = Permission.EDIT_PARTICIPANT_CHECKIN

def receive_json(self, content):
# Because the public can receive but not send checkins we need to
# re-authenticate here:
if not self.scope["user"].is_authenticated:
if not has_permission(self.scope["user"], self.edit_permission, self.tournament):
return

# Send message to room group about the new checkin
Expand Down
2 changes: 2 additions & 0 deletions tabbycat/draw/consumers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from actionlog.models import ActionLogEntry
from adjallocation.serializers import SimpleDebateAllocationSerializer, SimpleDebateImportanceSerializer
from tournaments.mixins import RoundWebsocketMixin
from users.permissions import Permission
from utils.mixins import SuperuserRequiredWebsocketMixin
from venues.serializers import SimpleDebateVenueSerializer

Expand Down Expand Up @@ -122,6 +123,7 @@ class DebateEditConsumer(BaseAdjudicatorContainerConsumer):
adjudicators_serializer = SimpleDebateAllocationSerializer
venues_serializer = SimpleDebateVenueSerializer
teams_serializer = EditDebateTeamsDebateSerializer
access_permission = Permission.EDIT_DEBATEADJUDICATORS

def receive_json(self, content):
for key in content.keys():
Expand Down
5 changes: 4 additions & 1 deletion tabbycat/draw/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,10 @@ def debateteams_ordered(self):
def get_team(self, side: int) -> 'Team':
if not hasattr(self, '_team_properties'):
self._populate_teams()
return self.teams[side]
try:
return self.teams[side]
except IndexError:
return None

def get_dt(self, side: int) -> 'DebateTeam':
"""dt = DebateTeam"""
Expand Down
4 changes: 1 addition & 3 deletions tabbycat/draw/tables.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from itertools import islice, zip_longest

from django.db.models import Max
from django.db.models.functions import Coalesce
from django.utils.encoding import force_str
from django.utils.html import format_html
from django.utils.translation import gettext as _
Expand Down Expand Up @@ -61,7 +59,7 @@ class PublicDrawTableBuilder(BaseDrawTableBuilder):

def add_debate_team_columns(self, debates, highlight=[]):
all_sides_confirmed = all(debate.sides_confirmed for debate in debates) # should already be fetched
n_cols = debates.aggregate(n=Coalesce(Max('debateteam__side'), 0))['n'] + 1
n_cols = max([dt.side for debate in debates for dt in debate.debateteams], default=self.tournament.pref('teams_in_debate') - 1) + 1

for side in range(n_cols):
# For BP team names are often longer than the full position label
Expand Down
4 changes: 2 additions & 2 deletions tabbycat/results/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ def clean(self):
# The motion must be from the relevant round
super().clean()
if self.motion is not None and self.debate.round not in self.motion.rounds.all():
raise ValidationError(_("Debate is in round %(round)d but motion (%(motion)s) is not in round") % {
'round': self.debate.round,
raise ValidationError(_("Debate is in %(round)s but motion (%(motion)s) is not in round") % {
'round': self.debate.round.name,
'motion': self.motion.reference})

if self.confirmed and self.discarded:
Expand Down
4 changes: 2 additions & 2 deletions tabbycat/results/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.core.exceptions import ValidationError
from django.db import ProgrammingError
from django.db.models import Count, Max, Q, Window
from django.db.models.functions import Rank
from django.db.models.functions import Coalesce, Rank
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils import timezone
Expand Down Expand Up @@ -83,7 +83,7 @@ def get_table(self):
if self.tournament.pref('enable_postponements'):
table.add_debate_postponement_column(draw)
table.add_debate_venue_columns(draw, for_admin=True)
table.add_debate_results_columns(draw, iron=True, n_cols=self._get_draw().aggregate(n=Max('debateteam__side'))['n']+1)
table.add_debate_results_columns(draw, iron=True, n_cols=self._get_draw().aggregate(n=Coalesce(Max('debateteam__side'), self.tournament.pref('teams_in_debate')-1))['n']+1)
table.add_debate_adjudicators_column(draw, show_splits=True, for_admin=True)
return table

Expand Down
18 changes: 13 additions & 5 deletions tabbycat/users/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ class UserPermissionInline(admin.TabularInline):
fields = ('permission', 'tournament')


class MembershipInline(admin.TabularInline):
model = Membership
fields = ('group',)


class CustomUserLabelsMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand All @@ -30,33 +35,36 @@ def __init__(self, *args, **kwargs):


class UserChangeFormExtended(CustomUserLabelsMixin, UserChangeForm):
membership = ModelMultipleChoiceField(queryset=Membership.objects.all(), required=False)
group_set = ModelMultipleChoiceField(queryset=Group.objects.all(), required=False)


class UserCreationFormExtended(CustomUserLabelsMixin, UserCreationForm):
membership = ModelMultipleChoiceField(queryset=Membership.objects.all(), required=False)
group_set = ModelMultipleChoiceField(queryset=Group.objects.all(), required=False)


class UserAdmin(BaseUserAdmin):
list_display = ('username', 'email', 'is_active', 'is_staff', 'is_superuser')
inlines = (UserPermissionInline,)
inlines = (UserPermissionInline, MembershipInline)

fieldsets = ( # Hide groups and user permission fields
(_('Personal info'), {'fields': ('username', 'email', 'password')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'membership')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)

add_fieldsets = ( # Set permissions when creating
(None, {
'fields': ('username', 'password1', 'password2', 'email', 'is_staff', 'is_superuser', 'membership'),
'fields': ('username', 'password1', 'password2', 'email', 'is_staff', 'is_superuser', 'group_set'),
}),
)

add_form_template = 'admin/change_form.html'
add_form = UserCreationFormExtended
form = UserChangeFormExtended

def get_queryset(self, request):
return super().get_queryset(request).prefetch_related('group_set')


@admin.register(UserPermission)
class UserPermissionAdmin(admin.ModelAdmin):
Expand Down
1 change: 1 addition & 0 deletions tabbycat/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Group(models.Model):
tournament = models.ForeignKey('tournaments.Tournament', models.CASCADE, verbose_name=_("tournament"))
permissions = ChoiceArrayField(blank=True, default=list,
base_field=models.CharField(max_length=50, choices=Permission.choices), verbose_name=_("permissions"))
users = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Membership', related_name='group_set')

class Meta:
constraints = [UniqueConstraint(fields=['name', 'tournament'])]
Expand Down
12 changes: 9 additions & 3 deletions tabbycat/utils/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,13 @@ def get_edit_permission(self) -> Optional['permission_type']:
def test_func(self) -> bool:
if not hasattr(self, 'tournament'):
return self.request.user.is_superuser

view_perm = False
if self.request.method == 'GET' and self.get_view_permission() is not None:
return has_permission(self.request.user, self.get_view_permission(), self.tournament)
if self.request.method in ['POST', 'PUT'] and self.get_edit_permission() is not None:
view_perm = has_permission(self.request.user, self.get_view_permission(), self.tournament)
if view_perm:
return True
if (not view_perm or self.request.method in ['POST', 'PUT']) and self.get_edit_permission() is not None:
return has_permission(self.request.user, self.get_edit_permission(), self.tournament)
return self.request.user.is_superuser

Expand Down Expand Up @@ -108,8 +112,10 @@ def access_permitted(self):

class SuperuserRequiredWebsocketMixin(AccessWebsocketMixin):

access_permission = False

def access_permitted(self):
return self.scope["user"].is_superuser
return has_permission(self.scope["user"], self.access_permission, self.tournament)


# ==============================================================================
Expand Down

0 comments on commit e822f25

Please sign in to comment.