# Backend Architecture Overview Author: Zhenyu Yang Last updated: Apr 24, 2026 This document describes the shared structure and cross-module constraints of the Django backend. For specific business rules, see the corresponding app pages. ## Directory Structure ```text backend/ ├── apps/ │ ├── accounts/ │ ├── classrooms/ │ ├── courses/ │ ├── signage/ │ ├── checkins/ │ ├── borrowings/ │ ├── repairs/ │ ├── abuse/ │ └── logs/ ├── classroom_manager/ │ ├── settings/ │ └── urls.py ├── common/ ├── services/ └── manage.py ``` ## Configuration & Runtime - The default settings module is `classroom_manager.settings.base`; `manage.py`, `wsgi.py`, and `asgi.py` all point directly to it. - The database defaults to MySQL; connection parameters come from `MYSQL_*` environment variables. - The default language is `zh-hans`; the default timezone is `Asia/Shanghai`. - REST Framework requires authentication by default; the default pagination class is `common.pagination.DefaultPagination` with a max page size of 200. - Login throttling uses `LOGIN_THROTTLE_RATE`, defaulting to `5/minute`. - The upload directory defaults to `LOCAL_UPLOAD_DIR=/data/uploads`; the access prefix defaults to `/media/uploads/`. ## Global Routing - Django admin entry: `/django-admin/` - Business API common prefix: `/api/v1/` - The root URL configuration is defined in `backend/classroom_manager/urls.py`. - Routes are organized in two ways: - DRF `router.register(...)` manages standard resources such as `users`, `classrooms`, `borrow`, and `courses`. - `path(...)` manages standalone actions such as authentication, uploads, system config, and signage activation. ## Authentication Model - Default authentication classes: - `apps.accounts.authentication.CookieJWTAuthentication` - `rest_framework.authentication.SessionAuthentication` - After successful email or WebAuthn login on the web, tokens are returned and `token`, `refresh_token`, `logged_in`, and `csrftoken` cookies are set. - The WeChat mini program login does not use cookies; it returns access/refresh tokens in the response body. - `CookieJWTAuthentication` reads the `Authorization` header first, then falls back to the `token` cookie. - The user's active role `current_role` is not persisted; it comes from the JWT claim or the `X-Current-Role` request header. Middleware validates that it exists in `user.roles`. - Signage devices do not use user JWTs; they use `apps.signage.authentication.SignageDeviceAuthentication` with the header format `Authorization: Bearer `. ## Cross-Module Dependencies - `courses` provides the source of truth for classroom schedules; `classrooms`, `signage`, `borrowings`, and `checkins` all depend on `CourseOccurrence`. - `borrowings` conflict detection checks both manual reservations and course occupancy. - `checkins` anchors `CheckinSession` creation to `CourseOccurrence`. - `common.SystemConfig` provides seasonal schedules and semester start dates to `courses`. - `logs` is reused across business modules via `OperationLogContextMiddleware` and `log_operation()`. ## Responses & Pagination - Two response styles coexist in the project: - The `{code, message, data}` wrapper from `common.responses.success/error` - DRF's default pagination or serializer responses - Most custom actions use the unified wrapper. - Endpoints that rely on the default `ModelViewSet.list/retrieve/update` implementation may return DRF's native structure. - During frontend-backend integration, do not assume all endpoints uniformly return `{code, message, data}`. ## Middleware & Shared Capabilities - `apps.logs.utils.OperationLogContextMiddleware` - Stores the current request in a `ContextVar`, making it available to the service layer for logging. - `apps.accounts.middleware.CurrentRoleMiddleware` - Resolves the active role from the JWT or `X-Current-Role` header. - `common.middleware.request_timezone.RequestTimezoneMiddleware` - Switches the current request timezone based on the `X-Timezone` header. ## Current Implementation Caveats - The log permission class `apps.logs.permissions.IsLogAdmin` still reads the legacy `user.role` field, so non-Django `is_superuser/is_staff` accounts typically cannot reliably access log endpoints. - `services/file_storage.py` already has a local/S3 dual-backend abstraction, but `common.views.upload_file` still writes directly to the local filesystem. - Signage already has independent device authentication, but it is currently used only for information display and is not integrated into the check-in write flow.