The exambank app owns exam archive collections and exam files. It replaced the previous archive.Collection(type="Exams") plus archive.Document rows used for exams.
ExamArchive stores the archive title, publication date, and migrated hide_for_gulis flag.ExamBankAccessSettings is a singleton configuration row for the whole public exam bank. It defaults to member sign-in, can disable sign-in, and can store an optional hashed shared password.ExamFile stores the uploaded file and display title.<year>/<archive>/<filename>.Exam views use the exambank.views.exam_bank_access_required gate instead of URL-level login_required. This keeps the same policy on the index, detail, and upload routes whether they are mounted through archive.urls, exambank.archive_urls, or exambank.urls.
When require_sign_in=True, access follows the historical member check. When it is false and a password is configured, successful password entry stores the current password hash in the session so changing the password invalidates existing grants. When sign-in is disabled and no password is configured, the exam bank routes are public.
For SF, ARCHIVE_ACCESS_REQUIRES_ELIGIBILITY makes the individual member flag authoritative on exam index and detail reads. A public or shared-password exam-bank configuration cannot bypass that SF rule. Other associations retain their configured sign-in, password, or public behavior, and superusers retain access.
Failed password submissions are rate-limited per session: after EXAM_BANK_PASSWORD_ATTEMPT_LIMIT (5) failures the gate returns HTTP 429 and refuses further attempts for EXAM_BANK_PASSWORD_LOCKOUT_SECONDS (15 minutes). A successful entry clears the counter.
ExamBankAccessSettings is edited through an Åtkomstinställningar tool link on the ExamArchiveAdmin changelist. The singleton settings model is hidden from the app index/sidebar so editors manage exams and their access policy from one exam-bank entry point.
Legacy exam-collection permissions also apply to inline exam files, preserving the complete editor workflow in classic admin and Unfold during the permission migration period.
Multi-file uploads are created from the form’s save_m2m() phase, after the new archive has been saved. This ordering is required by Django admin’s commit=False save flow.
archive.0008_remove_picture_collection_delete_examcollection_and_more copies legacy archive.Collection(type="Exams") rows into exambank_examarchive and related archive.Document rows into exambank_examfile.archive.0008_remove_picture_collection_delete_examcollection_and_more removes old exam proxy models from Django state after copying data and drops the copied legacy exam collection rows after the replacement rows exist in exambank.Public routes are still exposed through archive.urls under /archive/exams/... with the existing archive:exams, archive:exams_detail, archive:exam_upload, and archive:exam_archive_upload names.
Association variants that install exambank without the full archive app, such as Pulterit, mount exambank.archive_urls under /archive/ so the exam-only compatibility routes keep the same public paths and URL names.
The app intentionally renders the shared archive/... templates so the public archive pages keep their historical layout while the data ownership lives in exambank.
The staticpages.context_processors._visible_urls_queryset and the navigation processor hide nav entries whose URL starts with /archive/ when ARCHIVE_ENABLED=False. When exambank is in INSTALLED_APPS, entries under /archive/exams/ are kept visible so the exam compatibility routes remain reachable from the menu. The trailing slash is significant: only /archive/exams/... is exempted, not unrelated prefixes such as /archive/examined/.
DirectUploadField (see dev/uploads.md): with direct uploads enabled files go straight to the S3 endpoint and are finalized server-side on save; the signing scope for the exam bank reuses the exam_bank_access_is_allowed gate.