- EmployeeAttendanceSerializer: add meeting_title (source: meeting.title)
fixes attendance.js rendering r.meeting_title as undefined
- MeetingParticipantSerializer: add user_email and user_display_name
so meeting detail participant list shows human-readable identity
- OrganizationProfileListCreateView: add GET handler to list org profiles
(previously POST-only, returning 405 on GET)
- UserProfileSerializer: add org_ids derived from role_bindings
so frontend can resolve which org profile to load for branding
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Move daphne before django.contrib.staticfiles in INSTALLED_APPS so Daphne
system check passes and makemigrations --check can run cleanly
- Generate tracking/0002: rename auto-generated AuditEvent index names to
match Django 5.2 format
- Generate accounts/0005: sync AccountUser managers and ManyToMany field
help_text to match current Django/guardian defaults
The CallSession / CallParticipant / CallEvent migration (training/0004)
already existed from the groupcall backend commit; this commit ensures
makemigrations --check reports no pending migrations across all apps.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Backend:
- Add migration 0002 for Meeting, MeetingParticipant, EmployeeAttendance models
- Add EmployeeAttendanceSerializer with meeting_title helper field
- Add MeetingDetailView (GET /meetings/{id}/ with participants embedded)
- Add EmployeeAttendanceListView (GET /training/attendance/) with
role-based access: own records always visible; other users require
progress:view_all / progress:view_team / users:manage capability
- Register meeting-detail and attendance-list routes in urls.py
Frontend:
- attendance.html + attendance.js: JWT login flow, attendance history
table with status badges, token refresh via sessionStorage only
(no localStorage for access tokens)
- meetings.html + meetings.js: meeting list, create-meeting form,
per-meeting participant management, inline attendance recording
with select + save per row; UUID input validated before submit
- attendance.css: shared stylesheet for both pages
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- AttendanceSession model with open/closed status, org_id, timestamps
- POST /api/v1/attendance/check-in/ – creates session, rejects duplicate open check-ins (409)
- POST /api/v1/attendance/check-out/ – closes open session, validates checkout > checkin
- GET /api/v1/attendance/status/ – current open session for requesting user
- GET /api/v1/attendance/history/ – paginated session history per user
- GET /api/v1/attendance/admin/sessions/ – admin view for managers/admins with filters
- Initial migration with composite indexes on (user, status) and (user, checked_in_at)
- 18 test cases covering core flows and edge cases
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Introduces a production Docker Compose stack that places nginx in front
of gunicorn. nginx serves the frontend SPA and Django static files
directly, and proxies all backend routes (/api, /admin, /healthz, etc.)
to the Django container. SECURE_SSL_REDIRECT is now env-configurable so
plain-HTTP nginx deployments work without separate settings files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Before creating a UserRoleBinding for a specific org_id, check whether
the org's OrganizationProfile.license_user_limit has been reached. New
users attempting to join a full org receive HTTP 403 with a descriptive
message. Re-assigning a user already in the org (role change) is
unaffected, as is assignment to an org with no OrganizationProfile.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- OrganizationProfileListCreateView (admin-only) handles POST
- OrganizationProfileCreateSerializer drops auto UniqueValidator so the
view can return 409 (instead of 400) for duplicate org_id
- Returns 201 on success, 400 on validation errors, 409 on duplicate
- Tests: create success, defaults, duplicate 409, missing field 400, non-admin 403
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Replace the choices-constrained field (20/50/100/1000 only) with a plain
PositiveIntegerField so any positive integer is accepted. Adds migration
0004 to apply the schema change. Adds a test asserting arbitrary values
(e.g. 75) are accepted. Updates README with a field table and curl example
that documents common values without enforcing them.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Release stale Django migration advisory locks before migrating
- Run migrate via docker exec (not in container startup command)
- Update container CMD to start gunicorn directly (no migrate)
- This unblocks the 15-min migrate hang caused by leftover pg advisory lock
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Replace single healthz curl (no timeout) with polling loop (30x10s)
- Add --connect-timeout and --max-time to all curl calls
- Print container logs when gunicorn fails to start
- Print container logs in always() step for visibility
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Waitlist deployment uses requirements/waitlist.txt which does not include celery.
Making the import conditional allows the app to start without celery installed.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Use COMPOSE_PROJECT_NAME=training-software so container names are
predictable: training-software-waitlist-web-1
- Fix Dockerfile collectstatic: inject dummy env vars so the build
step does not fail when celery or other settings are not installed
- Fix verify step to use correct container name
Co-Authored-By: Paperclip <noreply@paperclip.ing>
A11y:
- Add alt_text field to MediaAsset and ContentBlock (WCAG 2.1 AA 1.1.1)
- Expose alt_text in MediaAssetSerializer and ContentBlockSerializer so
frontends can render <img alt="..."> and <video aria-label="..."> correctly
- Migration 0002 adds the two alt_text columns
i18n:
- Add LocaleMiddleware to MIDDLEWARE stack (after SessionMiddleware per Django docs)
- Add LANGUAGES = [("de", ...), ("en", ...)] and LOCALE_PATHS = [BASE_DIR/"locale"]
- Add USE_L10N = True
- Seed locale/de/LC_MESSAGES/django.po and locale/en/LC_MESSAGES/django.po with
translations for all user-facing API strings (upload errors, notification titles)
Tests (tests/a11y/test_a11y_i18n.py):
- alt_text field round-trip on MediaAsset and ContentBlock
- Serializer exposes and accepts alt_text on create, list, PATCH
- LANGUAGES/LOCALE_PATHS/LocaleMiddleware settings assertions
- Accept-Language header switching smoke test against /api/v1/notifications/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MediaAsset model with file metadata and AV scan status tracking
- ContentBlock model (richtext/image/video/embed/download) with ordered
blocks per page and unique constraint on (page, order)
- CourseTheme one-to-one per course with primary/secondary color and logo
- validate_upload/save_upload helpers with extension and size enforcement
- scan_media_asset_task Celery stub (marks clean; replace with ClamAV)
- REST API: media upload, page content blocks CRUD, block patch/delete
- Admin registrations for all three models
- Factory-boy factories and pytest test suite for views, upload, and task
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TrainingRecord model with completion_mode (online/offline/blended) and CompletionStatus state machine
- TrainerSignoff model with decision, notes, trainer FK, and audit timestamp
- SignoffEvidence model for uploaded proof files
- services.py state transitions:
- mark_in_progress: not_started → in_progress
- mark_online_passed: online → completed; blended → pending_signoff
- submit_trainer_signoff: offline/blended approved → completed; rejected → in_progress
- InvalidTransitionError on illegal state moves
- IsTrainer permission class based on training:signoff capability
- API: record detail, start, mark-online-passed, trainer-signoff, pending-signoff list
- 20 unit + integration tests covering all mode paths, invalid transitions, and access control
- Blended completion requires both online pass AND trainer approval
Co-Authored-By: Paperclip <noreply@paperclip.ing>