
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.

Overview
Migrate AttendanceSessionViewSet and AttendanceRecordViewSet from explicit values tuples to the serializer-derived pattern as part of the BaseValuesViewset refactor (#14036). The attendance app postdates #14036's original scoping, so it's missing from the affected-viewsets table and sub-issues. As the only issue touching this app, it also moves the code into kolibri/core/attendance/viewsets/ and deletes the old modules, per the File Organization convention.
Complexity: Medium
Target branch: develop
Context
Both viewsets live in kolibri/core/attendance/api.py, with AttendanceRecordSerializer and AttendanceSessionSerializer in serializers.py:
AttendanceSessionViewSet(ValuesViewset) has serializer_class = AttendanceSessionSerializer plus an explicit values tuple including the present_count/total_count annotations and the date_created/date_modified model fields — none of which are serializer fields. Conversely, the serializer's nested attendance_records (write path) is excluded from values.
AttendanceRecordViewSet(ReadOnlyValuesViewset) has no serializer_class at all — reads are driven purely by its values tuple, with user_name/user_username annotations.
- Both viewsets have custom actions (
recent, bulk_update) that call queryset.values(*self.values) directly and build their own Response, bypassing the standard serialize() path.
The Change
Make the serializers the source of truth for the read path:
- Extend
AttendanceSessionSerializer with the read-path fields currently only in values (present_count, total_count, date_created, date_modified), and mark the nested attendance_records write-only so the read output is unchanged.
- Define a read serializer for
AttendanceRecordViewSet covering its current output (id, user, present, attendance_session, user_name, user_username).
- Remove the explicit
values tuples, and rework the recent and bulk_update actions to serialize via the standard path rather than queryset.values(*self.values).
See #14036 for the overall pattern and FacilityUserViewSet for a concrete example (currently in kolibri/core/auth/api.py; moving to kolibri/core/auth/viewsets/facility_user.py in #14817).
Then relocate: migration commits first, in place; then a pure-move commit creating kolibri/core/attendance/viewsets/ and moving both viewsets with their serializers inline (plus filters and permission classes). Update imports (api_urls.py, tests) and delete api.py and serializers.py.
Moving code breaks GitHub blame (local git blame -C -C still works), so digest the history the move obscures:
- Run blame over the code being moved; flag hunks whose purpose isn't evident from the code or tests (odd guards, magic orderings, special cases).
- Chase each flagged hunk to its originating PR/issue; where the rationale is non-obvious and load-bearing, carry it forward as a code comment citing the reference.
- Where the rationale turns out to be obsolete, remove the code as part of the migration commits rather than annotating it.
Out of Scope
- Changes to the API response shape — the API contract must not change
- Modifications to existing tests — if tests need to change, the migration is wrong
- Write-path behavior changes —
create/update/bulk_update semantics stay identical; only bulk_update's response serialization is reworked
Acceptance Criteria
Testing
- Existing coverage is good (40 API tests in
kolibri/core/attendance/test/test_api.py); they must pass before and after without modification.
- Run the benchmark for both viewsets, baseline with the old dotted path, comparison with the new one:
python integration_testing/scripts/viewset_serialization_benchmark.py \
kolibri.core.attendance.api.AttendanceSessionViewSet \
--inherit-kolibri-home -o attendance_session_baseline.json
# ... migrate and move ...
python integration_testing/scripts/viewset_serialization_benchmark.py \
kolibri.core.attendance.viewsets.<module>.AttendanceSessionViewSet \
--inherit-kolibri-home --compare attendance_session_baseline.json
Likewise for AttendanceRecordViewSet. Both comparisons must pass; include the before/after output in the PR description.
- Benchmark data setup: populate users via
kolibri manage generateuserdata, then create attendance data in kolibri manage shell:
from django.utils import timezone
from kolibri.core.attendance.models import AttendanceRecord
from kolibri.core.attendance.models import AttendanceSession
from kolibri.core.auth.models import Classroom
from kolibri.core.auth.models import FacilityUser
classroom = Classroom.objects.first()
coach = FacilityUser.objects.first()
learners = list(FacilityUser.objects.filter(memberships__collection=classroom))
for i in range(20):
session = AttendanceSession.objects.create(
collection=classroom,
created_by=coach,
session_start_datetime=timezone.now(),
)
for user in learners:
AttendanceRecord.objects.create(
attendance_session=session,
user=user,
present=bool(i % 2),
)
AI usage
Drafted with AI assistance (Claude Code) from an interactive design discussion, section by section, with review and final edits by @rtibbles. Viewset internals, serializer contents, custom-action serialization paths, and test coverage were verified against the codebase during drafting.
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Overview
Migrate
AttendanceSessionViewSetandAttendanceRecordViewSetfrom explicitvaluestuples to the serializer-derived pattern as part of theBaseValuesViewsetrefactor (#14036). The attendance app postdates #14036's original scoping, so it's missing from the affected-viewsets table and sub-issues. As the only issue touching this app, it also moves the code intokolibri/core/attendance/viewsets/and deletes the old modules, per the File Organization convention.Complexity: Medium
Target branch: develop
Context
Both viewsets live in
kolibri/core/attendance/api.py, withAttendanceRecordSerializerandAttendanceSessionSerializerinserializers.py:AttendanceSessionViewSet(ValuesViewset)hasserializer_class = AttendanceSessionSerializerplus an explicitvaluestuple including thepresent_count/total_countannotations and thedate_created/date_modifiedmodel fields — none of which are serializer fields. Conversely, the serializer's nestedattendance_records(write path) is excluded fromvalues.AttendanceRecordViewSet(ReadOnlyValuesViewset)has noserializer_classat all — reads are driven purely by itsvaluestuple, withuser_name/user_usernameannotations.recent,bulk_update) that callqueryset.values(*self.values)directly and build their ownResponse, bypassing the standardserialize()path.The Change
Make the serializers the source of truth for the read path:
AttendanceSessionSerializerwith the read-path fields currently only invalues(present_count,total_count,date_created,date_modified), and mark the nestedattendance_recordswrite-only so the read output is unchanged.AttendanceRecordViewSetcovering its current output (id,user,present,attendance_session,user_name,user_username).valuestuples, and rework therecentandbulk_updateactions to serialize via the standard path rather thanqueryset.values(*self.values).See #14036 for the overall pattern and
FacilityUserViewSetfor a concrete example (currently inkolibri/core/auth/api.py; moving tokolibri/core/auth/viewsets/facility_user.pyin #14817).Then relocate: migration commits first, in place; then a pure-move commit creating
kolibri/core/attendance/viewsets/and moving both viewsets with their serializers inline (plus filters and permission classes). Update imports (api_urls.py, tests) and deleteapi.pyandserializers.py.Moving code breaks GitHub blame (local
git blame -C -Cstill works), so digest the history the move obscures:Out of Scope
create/update/bulk_updatesemantics stay identical; onlybulk_update's response serialization is reworkedAcceptance Criteria
valuestuples removed from both viewsetsAttendanceSessionSerializercarries all read-path fields, withattendance_recordswrite-only;AttendanceRecordViewSethas a read serializerrecentandbulk_updateserialize via the standard path, with unchanged response outputkolibri/core/attendance/viewsets/;api.pyandserializers.pyare deletedTesting
kolibri/core/attendance/test/test_api.py); they must pass before and after without modification.python integration_testing/scripts/viewset_serialization_benchmark.py \ kolibri.core.attendance.api.AttendanceSessionViewSet \ --inherit-kolibri-home -o attendance_session_baseline.json # ... migrate and move ... python integration_testing/scripts/viewset_serialization_benchmark.py \ kolibri.core.attendance.viewsets.<module>.AttendanceSessionViewSet \ --inherit-kolibri-home --compare attendance_session_baseline.jsonAttendanceRecordViewSet. Both comparisons must pass; include the before/after output in the PR description.kolibri manage generateuserdata, then create attendance data inkolibri manage shell:AI usage
Drafted with AI assistance (Claude Code) from an interactive design discussion, section by section, with review and final edits by @rtibbles. Viewset internals, serializer contents, custom-action serialization paths, and test coverage were verified against the codebase during drafting.