Repository: TabbycatDebate/tabbycat

Issue:
API: More straightforward access to checkin statuses
Related to #2087 

AFAIK currently the only way to get access to lists of missing or present participants via the API is by GETting one by one for each individual participant (and/or venue, in its case); as they're structured right now, each individual checkin is accessed by its corresponding URL, obtained in the `_link` section, but there is no access to a global checkin list. As so, to try to access something like that, it is required to do a call for every single participant which, in a tournament like EUDC or WUDC, can very easily scale to an enormous degree of calls.

That, I guess, can be easily fixed with a way to directly export them or, maybe even without the need to add another endpoint, just replace the current approach (in which the checkin data is hidden behind a URL thus calls) and instead just make it accesible in the corresponding participant list - but having it being relatively straightforward, I guess there was some design reasons for configuring this the way it currently is, so I also wanted to ask too in case I'm missing something here. 

Being maybe more clear, if this is the current speakers list: 

`[
    {
        "id": 1503,
        "url": "https://tournament.calicotab.com/api/v1/tournaments/tournament/speakers/1503",
        "team": "https://tournament.calicotab.com/api/v1/tournaments/tournament/teams/444",
        "_links": {
            "checkin": "https://tournament.calicotab.com/api/v1/tournaments/tournament/speakers/1503/checkin"
        },
        "name": "Speaker 1",
  ... ` 

just changing it to:

`[
    {
        "id": 1503,
        "url": "https://tournament.calicotab.com/api/v1/tournaments/tournament/speakers/1503",
        "team": "https://tournament.calicotab.com/api/v1/tournaments/tournament/teams/444",
        "checkin": {
            "barcode": "123456",
            "checked": false,
            "timestamp": null,
        },
        "name": "Speaker 1",
... ` 

wold entirely solve this, isn't it? If a security layer is needed, maybe it could be hidden if there's no token in the API call. If I remember correctly, this is already the way some key values are handled in other parts of the API structure.


Discussion:
I'd disagree about extending participant schemas to include checkin information, as checkins are a different resource to the participants with a very well-loaded set of verbs for the endpoints, discoverable through the link. I'd be more inclined to create a new list endpoint, and maybe even add shortcuts to check in entire teams or institutions at once.

Resolving patch:
diff --git a/tabbycat/api/serializers.py b/tabbycat/api/serializers.py
--- a/tabbycat/api/serializers.py
+++ b/tabbycat/api/serializers.py
@@ -63,7 +63,7 @@ class V1LinksSerializer(serializers.Serializer):
 
 class CheckinSerializer(serializers.Serializer):
     object = serializers.HyperlinkedIdentityField(view_name='api-root')
-    barcode = serializers.IntegerField()
+    barcode = serializers.CharField()
     checked = serializers.BooleanField()
     timestamp = serializers.DateTimeField()
 
@@ -456,6 +456,7 @@ class SpeakerLinksSerializer(serializers.Serializer):
         queryset=SpeakerCategory.objects.all(),
     )
     _links = SpeakerLinksSerializer(source='*', read_only=True)
+    barcode = serializers.CharField(source='checkin_identifier.barcode', read_only=True)
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -465,6 +466,7 @@ def __init__(self, *args, **kwargs):
             self.fields.pop('phone')
             self.fields.pop('pronoun')
             self.fields.pop('url_key')
+            self.fields.pop('barcode')
 
             if kwargs['context']['tournament'].pref('participant_code_names') == 'everywhere':
                 self.fields.pop('name')
@@ -519,6 +521,7 @@ class AdjudicatorLinksSerializer(serializers.Serializer):
     )
     venue_constraints = VenueConstraintSerializer(many=True, required=False)
     _links = AdjudicatorLinksSerializer(source='*', read_only=True)
+    barcode = serializers.CharField(source='checkin_identifier.barcode', read_only=True)
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -545,6 +548,7 @@ def __init__(self, *args, **kwargs):
             self.fields.pop('phone')
             self.fields.pop('pronoun')
             self.fields.pop('url_key')
+            self.fields.pop('barcode')
 
     class Meta:
         model = Adjudicator
diff --git a/tabbycat/api/views.py b/tabbycat/api/views.py
--- a/tabbycat/api/views.py
+++ b/tabbycat/api/views.py
@@ -330,7 +330,7 @@ def get_queryset(self):
         return super().get_queryset().select_related('tournament').prefetch_related(
             Prefetch(
                 'speaker_set',
-                queryset=Speaker.objects.all().prefetch_related(category_prefetch).select_related('team__tournament'),
+                queryset=Speaker.objects.all().prefetch_related(category_prefetch).select_related('team__tournament', 'checkin_identifier'),
             ),
             'institution_conflicts', 'venue_constraints__category__tournament',
             'break_categories', 'break_categories__tournament',
@@ -360,7 +360,7 @@ def get_queryset(self):
         if self.request.query_params.get('break') and self.get_break_permission():
             filters &= Q(breaking=True)
 
-        return super().get_queryset().prefetch_related(
+        return super().get_queryset().select_related('checkin_identifier').prefetch_related(
             'team_conflicts', 'team_conflicts__tournament',
             'adjudicator_conflicts', 'adjudicator_conflicts__tournament',
             'institution_conflicts', 'venue_constraints__category__tournament',
@@ -410,7 +410,7 @@ def get_queryset(self):
         if not self.request.user or not self.request.user.is_staff:
             category_prefetch.queryset = category_prefetch.queryset.filter(public=True)
 
-        return super().get_queryset().prefetch_related(category_prefetch)
+        return super().get_queryset().select_related('checkin_identifier').prefetch_related(category_prefetch)
 
 
 @extend_schema(tags=['venues'], parameters=[tournament_parameter])


Test patch: