Skip to content

Commit

Permalink
Merge branches 'draw-perms' and 'api/sub-saves', remote-tracking bran…
Browse files Browse the repository at this point in the history
…ches 'origin/bug/2541' and 'ddh13/documentation' into develop
  • Loading branch information
tienne-B committed Dec 10, 2024
4 parents 109f097 + 0567df4 + 6067b42 + 4a2ac1d commit 2265ea3
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 97 deletions.
10 changes: 5 additions & 5 deletions docs/install/linux.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Short version
::

curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash - # add Node.js source repository
sudo apt install python3.9 python3-distutils pipenv postgresql libpq-dev nodejs gcc g++ make
sudo apt install python3.11 python3-distutils pipenv postgresql libpq-dev nodejs gcc g++ make
git clone https://github.com/TabbycatDebate/tabbycat.git
cd tabbycat
git checkout master
Expand Down Expand Up @@ -63,16 +63,16 @@ First, you need to install all of the software on which Tabbycat depends, if you

1(a). Python
------------
Tabbycat uses Python 3.9. You probably already have Python 3, but you'll also need the development package in order to install Psycopg2 later. You'll also want `Pipenv <https://pipenv.pypa.io/en/latest/>`_, if you don't already have it. Install::
Tabbycat uses Python 3.11. You probably already have Python 3, but you'll also need the development package in order to install Psycopg2 later. You'll also want `Pipenv <https://pipenv.pypa.io/en/latest/>`_, if you don't already have it. Install::

$ sudo apt install python3.9 python3-distutils pipenv
$ sudo apt install python3.11 python3-distutils pipenv

Check the version::

$ python3 --version
Python 3.9.12
Python 3.11.10

.. warning:: Tabbycat does not support Python 2. You must use Python 3.9.
.. warning:: Tabbycat does not support Python 2. You must use Python 3.11.

.. admonition:: Advanced users
:class: tip
Expand Down
6 changes: 3 additions & 3 deletions docs/install/osx.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ First, you need to install all of the software on which Tabbycat depends, if you

1(a). Python
--------------------------------------------------------------------------------
Tabbycat requires Python 3.9. macOS only comes with Python 2.7, so you'll need to install this. You can download the latest version from the `Python website <https://www.python.org/downloads/>`_.
Tabbycat requires Python 3.11. macOS only comes with Python 2.7, so you'll need to install this. You can download the latest version from the `Python website <https://www.python.org/downloads/>`_.

The executable will probably be called ``python3``, rather than ``python``. Check::

$ python3 --version
Python 3.9.12
Python 3.11.10

.. warning:: Tabbycat does not support Python 2. You must use Python 3.9.
.. warning:: Tabbycat does not support Python 2. You must use Python 3.11.

You'll also need to install `Pipenv <https://pipenv.pypa.io/en/latest/>`_::

Expand Down
2 changes: 1 addition & 1 deletion docs/install/windows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ To check that Python is installed correctly, open Windows PowerShell, type ``pyt

.. note:: **If you already have Python**, great! Some things to double-check:

- You must have at least Python 3.9.
- You must have at least Python 3.11.
- Your installation path must not have any spaces in it.
- If that doesn't work, note that the following must be part of your ``PATH`` environment variable: ``C:\Python38;C:\Python38\Scripts`` (or as appropriate for your Python version and installation directory). Follow `the instructions here <https://www.java.com/en/download/help/path.xml>`_ to add this to your path.

Expand Down
129 changes: 41 additions & 88 deletions tabbycat/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ def _validate_field(self, field, value):
return value


def save_related(serializer, data, context, save_fields):
s = serializer(many=isinstance(data, list), context=context)
s._validated_data = data
s._errors = []
s.save(**save_fields)


class RootSerializer(serializers.Serializer):
class RootLinksSerializer(serializers.Serializer):
v1 = serializers.HyperlinkedIdentityField(view_name='api-v1-root')
Expand Down Expand Up @@ -238,13 +245,8 @@ def create(self, validated_data):

round = super().create(validated_data)

if len(motions_data) > 0:
for i, motion in enumerate(motions_data, start=1):
motion['seq'] = i

motions = self.RoundMotionSerializer(many=True, context=self.context)
motions._validated_data = motions_data # Data was already validated
motions.save(round=round)
for i, motion in enumerate(motions_data, start=1):
save_related(self.RoundMotionSerializer, motion, self.context, {'round': round, 'seq': i})

return round

Expand Down Expand Up @@ -359,10 +361,7 @@ def create(self, validated_data):
rounds_data = validated_data.pop('roundmotion_set')
motion = super().create(validated_data)

if len(rounds_data) > 0:
rounds = self.RoundsSerializer(many=True, context=self.context)
rounds._validated_data = rounds_data # Data was already validated
rounds.save(motion=motion)
save_related(self.RoundsSerializer, rounds_data, self.context, {'motion': motion})

return motion

Expand Down Expand Up @@ -622,10 +621,7 @@ def create(self, validated_data):

adj = super().create(validated_data)

if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=adj)
save_related(VenueConstraintSerializer, venue_constraints, self.context, {'subject': adj})

if url_key is None: # If explicitly null (and not just an empty string)
populate_url_keys([adj])
Expand All @@ -639,11 +635,7 @@ def create(self, validated_data):
return adj

def update(self, instance, validated_data):
venue_constraints = validated_data.pop('venue_constraints', [])
if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=instance)
save_related(VenueConstraintSerializer, validated_data.pop('venue_constraints', []), self.context, {'subject': instance})

if 'base_score' in validated_data and validated_data['base_score'] != instance.base_score:
AdjudicatorBaseScoreHistory.objects.create(
Expand Down Expand Up @@ -768,32 +760,17 @@ def create(self, validated_data):
).exclude(pk__in=[bc.pk for bc in break_categories])) + break_categories)

# The data is passed to the sub-serializer so that it handles categories
if len(speakers_data) > 0:
speakers = SpeakerSerializer(many=True, context=self.context)
speakers._validated_data = speakers_data # Data was already validated
speakers.save(team=team)

if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=team)
save_related(SpeakerSerializer, speakers_data, self.context, {'team': team})
save_related(VenueConstraintSerializer, venue_constraints, self.context, {'subject': team})

if team.institution is not None:
team.teaminstitutionconflict_set.get_or_create(institution=team.institution)

return team

def update(self, instance, validated_data):
speakers_data = validated_data.pop('speakers', [])
venue_constraints = validated_data.pop('venue_constraints', [])
if len(speakers_data) > 0:
speakers = SpeakerSerializer(many=True, context=self.context)
speakers._validated_data = speakers_data # Data was already validated
speakers.save(team=instance)
if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=instance)
save_related(SpeakerSerializer, validated_data.pop('speakers', []), self.context, {'team': instance})
save_related(VenueConstraintSerializer, validated_data.pop('venue_constraints', []), self.context, {'subject': instance})

if self.partial:
# Avoid removing conflicts if merely PATCHing
Expand Down Expand Up @@ -824,20 +801,12 @@ def create(self, validated_data):

institution = super().create(validated_data)

if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=institution)
save_related(VenueConstraintSerializer, venue_constraints, self.context, {'subject': institution})

return institution

def update(self, instance, validated_data):
venue_constraints = validated_data.pop('venue_constraints', [])
if len(venue_constraints) > 0:
vc = VenueConstraintSerializer(many=True, context=self.context)
vc._validated_data = venue_constraints # Data was already validated
vc.save(subject=instance)

save_related(VenueConstraintSerializer, validated_data.pop('venue_constraints', []), self.context, {'subject': instance})
return super().update(instance, validated_data)


Expand Down Expand Up @@ -970,7 +939,9 @@ class Meta:
fields = ('team', 'side')

def save(self, **kwargs):
kwargs['side'] = kwargs.get('side', kwargs['seq'])
seq = kwargs.pop('seq')
if 'side' not in self.validated_data:
self.validated_data['side'] = kwargs.get('side', seq)
return super().save(**kwargs)

class PairingLinksSerializer(serializers.Serializer):
Expand Down Expand Up @@ -1007,15 +978,11 @@ def create(self, validated_data):
validated_data['round'] = self.context['round']
debate = super().create(validated_data)

teams = self.DebateTeamSerializer()
for i, team in enumerate(teams_data):
teams._validated_data = teams_data # Data was already validated
teams.save(debate=debate, seq=i)
save_related(self.DebateTeamSerializer, team, self.context, {'debate': debate, 'seq': i})

if adjs_data is not None:
adjudicators = DebateAdjudicatorSerializer()
adjudicators._validated_data = adjs_data
adjudicators.save(debate=debate)
save_related(DebateAdjudicatorSerializer, adjs_data, self.context, {'debate': debate})

return debate

Expand All @@ -1028,10 +995,8 @@ def update(self, instance, validated_data):
except (IntegrityError, TypeError) as e:
raise serializers.ValidationError(e)

if 'adjudicators' in validated_data and validated_data['adjudicators'] is not None:
adjudicators = DebateAdjudicatorSerializer()
adjudicators._validated_data = validated_data.pop('adjudicators')
adjudicators.save(debate=instance)
if (adjs_data := validated_data.pop('adjudicators', None)) is not None:
save_related(DebateAdjudicatorSerializer, adjs_data, self.context, {'debate': instance})

return super().update(instance, validated_data)

Expand Down Expand Up @@ -1300,15 +1265,13 @@ def save(self, **kwargs):
args.insert(0, kwargs.get('adjudicator'))
result.add_winner(*args)

speech_serializer = self.SpeechSerializer(context=self.context)
for i, speech in enumerate(self.validated_data.get('speeches', []), 1):
speech_serializer._validated_data = speech
speech_serializer.save(
result=result,
side=side,
seq=i,
adjudicator=kwargs.get('adjudicator'),
)
save_related(self.SpeechSerializer, speech, self.context, {
'result': result,
'side': side,
'seq': i,
'adjudicator': kwargs.get('adjudicator'),
})
return result

teams = TeamResultSerializer(many=True)
Expand All @@ -1335,10 +1298,12 @@ def validate_teams(self, value):
return value

def save(self, **kwargs):
team_serializer = self.TeamResultSerializer(context=self.context)
for i, team in enumerate(self.validated_data.get('teams', [])):
team_serializer._validated_data = team
team_serializer.save(result=kwargs['result'], adjudicator=self.validated_data.get('adjudicator'), seq=i)
save_related(self.TeamResultSerializer, team, self.context, {
'result': kwargs['result'],
'adjudicator': self.validated_data.get('adjudicator'),
'seq': i,
})
return kwargs['result']

sheets = SheetSerializer(many=True, required=True)
Expand All @@ -1359,10 +1324,8 @@ def validate(self, data):
def create(self, validated_data):
result = DebateResult(validated_data['ballot'], tournament=self.context.get('tournament'))

sheets = self.SheetSerializer(context=self.context)
for sheet in validated_data['sheets']:
sheets._validated_data = sheet
sheets.save(result=result)
save_related(self.SheetSerializer, sheet, self.context, {'result': result})

try:
result.save()
Expand Down Expand Up @@ -1455,16 +1418,10 @@ def create(self, validated_data):

ballot = super().create(validated_data)

result = self.ResultSerializer(context=self.context)
result._validated_data = result_data
result._errors = []
result.save(ballot=ballot)
save_related(self.ResultSerializer, result_data, self.context, {'ballot': ballot})

if veto_data:
vetos = self.VetoSerializer(context=self.context, many=True)
vetos._validated_data = veto_data
vetos._errors = []
vetos.save(ballot_submission=ballot, preference=3)
save_related(self.VetoSerializer, veto_data, self.context, {'ballot_submission': ballot, 'preference': 3})

return ballot

Expand Down Expand Up @@ -1501,17 +1458,13 @@ def create(self, validated_data):
debate = super().create(validated_data)

if adjs_data is not None:
adjudicators = DebateAdjudicatorSerializer()
adjudicators._validated_data = adjs_data
adjudicators.save(debate=debate)
save_related(DebateAdjudicatorSerializer, adjs_data, self.context, {'debate': debate})

return debate

def update(self, instance, validated_data):
if validated_data.get('adjudicators', None) is not None:
adjudicators = DebateAdjudicatorSerializer()
adjudicators._validated_data = validated_data.pop('adjudicators')
adjudicators.save(debate=instance)
save_related(DebateAdjudicatorSerializer, validated_data.pop('adjudicators'), self.context, {'debate': instance})

return super().update(instance, validated_data)

Expand Down
2 changes: 2 additions & 0 deletions tabbycat/draw/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ def post(self, request, *args, **kwargs):


class ConfirmDrawCreationView(DrawStatusEdit):
edit_permission = Permission.GENERATE_DEBATE
action_log_type = ActionLogEntry.ActionType.DRAW_CONFIRM

def post(self, request, *args, **kwargs):
Expand All @@ -737,6 +738,7 @@ def post(self, request, *args, **kwargs):
class ConfirmDrawRegenerationView(LogActionMixin, AdministratorMixin, RoundMixin, FormView):
template_name = "draw_confirm_regeneration.html"
view_permission = Permission.DELETE_DEBATE
edit_permission = Permission.DELETE_DEBATE
form_class = ConfirmDrawDeletionForm

action_log_type = ActionLogEntry.ActionType.DRAW_REGENERATE
Expand Down

0 comments on commit 2265ea3

Please sign in to comment.