Add new facility setting fields to support picture password login. #14400
Add new facility setting fields to support picture password login. #14400AllanOXDi wants to merge 19 commits intolearningequality:developfrom
Conversation
rtibblesbot
left a comment
There was a problem hiding this comment.
New facility settings for picture-password login: model fields, migration, serializer validation, login enforcement, and incompatibility guards.
CI still in progress — multiple Python unit test and Morango integration jobs are failing or pending. PR description is empty (placeholder … in all sections) and no issues are linked.
- blocking:
additionalProperties: Falseon syncable JSON field risks breaking morango sync (see inline) - blocking: No tests for the login enforcement logic — the security-critical auth changes (see inline)
- suggestion: PR targets
release-v0.19.x, notdevelop— is this intentional for a new feature? - suggestion: Double-negation in learner role check is hard to follow (see inline)
- praise: Serializer
validatecorrectly handles PATCH by falling back to instance values - praise: Login blocking uses a generic
INVALID_CREDENTIALSerror to prevent username enumeration
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Reviewed the pull request diff checking for:
- Correctness: bugs, edge cases, undocumented behavior, resource leaks, hardcoded values
- Design: unnecessary complexity, naming, readability, comment accuracy, redundant state
- Architecture: duplicated concerns, minimal interfaces, composition over inheritance
- Testing: behavior-based assertions, mocks only at hard boundaries, accurate coverage
- Completeness: missing dependencies, unupdated usages, i18n, accessibility, security
- Principles: DRY (same reason to change), SRP, Rule of Three (no premature abstraction)
- Checked CI status and linked issue acceptance criteria
- For UI changes: inspected screenshots for layout, visual completeness, and consistency
rtibblesbot
left a comment
There was a problem hiding this comment.
Delta review — only the [pre-commit.ci lite] apply automatic fixes commit landed since the prior review. All prior findings remain unaddressed.
CI failing: makemigrations --check detects the migration is out of sync (missing validator on picture_password_settings). Morango integration tests are still running.
Prior-review status
| # | Prior finding | Status |
|---|---|---|
| 1 | blocking: additionalProperties: False on syncable JSON field |
UNADDRESSED — see inline |
| 2 | blocking: No tests for login enforcement logic | UNADDRESSED — see inline |
| 3 | suggestion: PR targets release-v0.19.x, not develop |
UNADDRESSED |
| 4 | suggestion: Double-negation in learner role check | UNADDRESSED — see inline |
New findings
- blocking: Migration 0033 is missing the
JSON_Schema_Validatoronpicture_password_settings, causing CI to fail (see inline) - praise:
FacilityUsernameViewSetqueryset correctly usesQOR to include picture-password learners
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
kolibri/core/auth/migrations/0033_facilitydataset_picture_password.py
Outdated
Show resolved
Hide resolved
Build Artifacts
Smoke test screenshot |
2d0dee5 to
55dc794
Compare
rtibblesbot
left a comment
There was a problem hiding this comment.
Delta review — all four prior blocking findings are now resolved. CI still pending.
Prior-review status
| # | Prior finding | Status |
|---|---|---|
| 1 | blocking: additionalProperties: False on syncable JSON field |
RESOLVED — removed from schema |
| 2 | blocking: No tests for login enforcement logic | RESOLVED — PicturePasswordLoginTestCase added with 5 tests |
| 3 | suggestion: PR targets release-v0.19.x, not develop |
UNADDRESSED |
| 4 | suggestion: Double-negation in learner role check | RESOLVED — rewritten as if user is None or user.is_superuser or user.roles.exists() |
| 5 | blocking: Migration 0033 missing JSON_Schema_Validator |
RESOLVED — validator now present in migration |
New findings
- suggestion: Missing dataset-level test for
no_password + picture_passwordincompatibility (see inline) - suggestion: Facility presets may conflict with
ensure_compatibility(see inline) - suggestion: PR description still empty (see inline)
- praise: Login enforcement tests are thorough, including edge cases like disabled flag and admin-via-token rejection
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
LianaHarris360
left a comment
There was a problem hiding this comment.
Thank you for your work on this @AllanOXDi! There's a few changes that can be completely removed, and RTibblesbot caught some things that I didn't. I resolved the comments that aren't necessary to fix due to changes that will be removed.
eb8545a to
ecafdd1
Compare
rtibblesbot
left a comment
There was a problem hiding this comment.
Delta review — author addressed some prior feedback (removed learner_can_login_with_picture_password boolean field and PicturePasswordLoginTestCase), but maintainer-requested changes remain partially unaddressed.
CI: all unit tests passing, Morango integration tests and some builds still pending.
Prior-review status
| # | Prior finding | Status |
|---|---|---|
| 1 | blocking: additionalProperties: False on syncable JSON field |
RESOLVED |
| 2 | blocking: No tests for login enforcement logic | CONTESTED — maintainer says remove the login enforcement code entirely |
| 3 | suggestion: PR targets release-v0.19.x, not develop |
UNADDRESSED |
| 4 | suggestion: Double-negation in learner role check | RESOLVED — rewritten as positive check |
| 5 | blocking: Migration 0033 missing JSON_Schema_Validator |
RESOLVED |
| 6 | suggestion: Missing dataset-level test for no_password + picture_password |
CONTESTED — maintainer says this incompatibility isn't needed |
| 7 | suggestion: Facility presets need picture_password_settings |
Maintainer confirmed — see below |
| 8 | suggestion: PR description still empty | UNADDRESSED |
New findings
- blocking:
CreateSessionSerializerlogin enforcement changes still present — maintainer requested removal (see inline) - blocking:
PicturePasswordLoginTestCasetests removed but no tests remain for the token-path gating that's still in the code (see inline) - suggestion:
facility_configuration_presets.jsonstill missingpicture_password_settings— maintainer confirmed this is needed - praise: Good work removing the separate boolean field and deriving the flag from
picture_password_settings is not None
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
rtibblesbot
left a comment
There was a problem hiding this comment.
Delta review — prior blocking findings are all resolved. Login enforcement code correctly removed per maintainer request. Two items remain.
CI: black formatting failure in serializers.py — pre-commit.ci lite is running and should auto-fix. Most test/build jobs still pending.
Prior-review status
Resolved:
additionalProperties: Falseon syncable JSON field — removed- No tests for login enforcement — login enforcement code removed (moved to separate task)
- Double-negation in learner role check — code removed with login enforcement
- Migration missing
JSON_Schema_Validator— validator now present CreateSessionSerializerlogin enforcement still present — removed in1ea226dc7f- No tests for token-path gating — token-path code removed
Contested (maintainer decision):
- Missing dataset-level test for
no_password + picture_passwordincompatibility — @LianaHarris360 confirmed this incompatibility isn't needed
Unaddressed:
facility_configuration_presets.jsonmissingpicture_password_settings— @LianaHarris360 confirmed this is needed (see inline)- PR description still empty
New findings
- blocking: Presets file still missing
picture_password_settings(see inline) - suggestion: PR description is still placeholder text
- praise: Clean removal of
CreateSessionSerializerchanges to keep scope focused
Comments on lines not in diff:
kolibri/core/auth/constants/facility_configuration_presets.json:11 — blocking (from prior review — UNADDRESSED): @LianaHarris360 confirmed that picture_password_settings should be added to the presets. Without it, reset_to_default_settings() won't clear picture-password when resetting to a preset where learner_can_edit_password=True (like nonformal or informal), causing ensure_compatibility to raise IncompatibleDeviceSettingError.
Add "picture_password_settings": null to each preset:
"formal": {
"mappings": {
...
"picture_password_settings": null
}
}@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
| if settings is not None: | ||
| if settings.get("icon_style") not in ("standard", "colorful"): | ||
| raise serializers.ValidationError( | ||
| { |
There was a problem hiding this comment.
suggestion: This validate method only checks picture_password_settings when it's explicitly in attrs. For a PATCH that sends only picture_password_settings (without learner_can_edit_password), the cross-field incompatibility is still caught by ensure_compatibility in save() — which is correct. But consider whether the serializer should also validate against the instance's existing learner_can_edit_password to give a cleaner error message at the serializer level rather than letting it bubble up from the model's IncompatibleDeviceSettingError. Not blocking since the model-level check prevents invalid state.
There was a problem hiding this comment.
We don't need to bubble up the model so I think we can handle this better in the UI
66da11b to
4e6626a
Compare
rtibblesbot
left a comment
There was a problem hiding this comment.
Delta review — all prior blocking findings are now resolved, including the presets file which now has picture_password_settings: null in all three presets.
CI: linting passed, builds still pending.
Prior findings — resolved:
additionalProperties: Falseon syncable JSON field- No tests for login enforcement logic (code removed per maintainer)
- Double-negation in learner role check (code removed)
- Migration missing
JSON_Schema_Validator CreateSessionSerializerlogin enforcement still present (removed)- Presets file missing
picture_password_settings(now added)
Prior findings — contested (maintainer decision):
- Missing dataset-level test for
no_password + picture_passwordincompatibility
Prior findings — unaddressed:
- PR targets
release-v0.19.x, notdevelop— is this intentional for a new feature? - PR description is still placeholder text
New findings:
- blocking: Serializer
validatecrashes withAttributeErroron non-dict input (see inline) - suggestion: PR description still empty
- praise: Presets correctly include
picture_password_settings: nullin all three configurations - praise: Good test coverage for incompatibility, validation, and round-trip scenarios
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
|
|
||
| def validate(self, attrs): | ||
| settings = attrs.get("picture_password_settings") | ||
| if settings is not None: |
There was a problem hiding this comment.
blocking: If picture_password_settings is sent as a non-dict JSON value (e.g., "hello", 42, [1,2]), DRF's JSONField will accept it, but settings.get("icon_style") on line 199 will raise AttributeError — resulting in a 500 error instead of a 400 validation error.
Add a type check before accessing dict methods:
def validate(self, attrs):
settings = attrs.get("picture_password_settings")
if settings is not None:
if not isinstance(settings, dict):
raise serializers.ValidationError(
{"picture_password_settings": "Must be an object or null"}
)
if settings.get("icon_style") not in ("standard", "colorful"):
...
kolibri/core/auth/serializers.py
Outdated
|
|
||
| class PublicFacilitySerializer(serializers.ModelSerializer): | ||
| learner_can_login_with_no_password = serializers.SerializerMethodField() | ||
| learner_can_login_with_picture_password = serializers.SerializerMethodField() |
There was a problem hiding this comment.
Just flagging that learner_can_login_with_picture_password and get_learner_can_login_with_picture_password should be removed from this file as well.
rtibblesbot
left a comment
There was a problem hiding this comment.
Delta review — all prior blocking findings are resolved. CI passing.
Prior findings — resolved:
additionalProperties: Falseon syncable JSON field — removed- No tests for login enforcement — code removed per maintainer
- Double-negation in learner role check — code removed
- Migration missing
JSON_Schema_Validator— validator present CreateSessionSerializerlogin enforcement — removed- Presets missing
picture_password_settings— added to all three presets - Serializer
validateon non-dict input —isinstance(settings, dict)check handles this - PR description empty — now filled in
- Maintainer flag about
learner_can_login_with_picture_password/get_learner_can_login_with_picture_password— already removed (not present in codebase)
Prior findings — contested (maintainer decision):
- Missing dataset-level test for
no_password + picture_passwordincompatibility
Prior findings — unaddressed:
- PR targets
release-v0.19.xbut issue #14351 says "Target branch: develop" and the repo default branch isdevelop— see inline
All acceptance criteria from #14351 are met.
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
| raise serializers.ValidationError( | ||
| {"picture_password_settings": "show_icon_text must be a boolean"} | ||
| ) | ||
| return attrs |
There was a problem hiding this comment.
praise: Good structural validation — checks type (isinstance) before accessing dict methods, handles both null and non-dict inputs cleanly.
| return FacilityUser.objects.filter( | ||
| dataset__learner_can_login_with_no_password=True, roles=None | ||
| Q(dataset__learner_can_login_with_no_password=True) | ||
| | Q(dataset__picture_password_settings__isnull=False), |
There was a problem hiding this comment.
praise: Clean use of Q OR to include picture-password learners in the username list endpoint, correctly paralleling the existing learner_can_login_with_no_password check.
| # '"optional":True' is obsolete but needed while we keep using an | ||
| # old json_schema_validator version compatible with python 2.7. | ||
| # "additionalProperties": False must be avoided for backwards compatibility | ||
| picture_password_settings_schema = { |
There was a problem hiding this comment.
suggestion (from prior review — UNADDRESSED): Issue #14351 states "Target branch: develop" but this PR targets release-v0.19.x. New features (new model field + migration) typically land on the default branch. Is this intentional?
There was a problem hiding this comment.
We are targeting release-v0.19.x
0c87a95 to
28db704
Compare
rtibblesbot
left a comment
There was a problem hiding this comment.
Delta review — all prior blocking findings remain resolved. CI passing. PR now correctly targets develop and description is filled in.
Prior findings — all resolved:
additionalProperties: Falseon syncable JSON field- No tests for login enforcement (code removed per maintainer)
- Double-negation in learner role check (code removed)
- Migration missing
JSON_Schema_Validator CreateSessionSerializerlogin enforcement (removed)- Presets missing
picture_password_settings(added) - Serializer
validateon non-dict input (isinstance check added) - PR description empty (now filled in)
- PR targets
release-v0.19.x(now targetsdevelop)
Prior findings — contested (maintainer decision):
- Missing dataset-level test for
no_password + picture_passwordincompatibility
New findings:
- blocking: 33 out-of-scope files from unrelated features (see inline)
- praise: Clean serializer validation and comprehensive test coverage for picture-password settings
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
kolibri/core/attendance/api.py
Outdated
| @@ -1,46 +1,37 @@ | |||
| import datetime | |||
|
|
|||
| from django.db import transaction | |||
There was a problem hiding this comment.
blocking: This PR includes 33 files from unrelated features — attendance tracking, content import pagination, SparklineBar, coach frontend views, i18n strings, etc. Issue #14351 explicitly scopes this to backend-only picture-password settings and states "Out of Scope: Frontend changes of any kind."
The branch has 63 commits, but only ~15 are picture-password related. The rest appear to be work from other features that were committed to this branch.
Please rebase this branch onto develop and drop the unrelated commits, or cherry-pick only the picture-password commits onto a clean branch. Out-of-scope changes bypass issue tracking and make the PR much harder to review (reviewers need to assess 44 files instead of 11).
| "preset", | ||
| ) | ||
|
|
||
| def validate(self, attrs): |
There was a problem hiding this comment.
praise: Solid structural validation — checks isinstance before accessing dict methods, handles both null and non-dict inputs cleanly, and the save() wrapper translates IncompatibleDeviceSettingError into a DRF ValidationError for a proper 400 response.
| ) | ||
| self.assertEqual(response.status_code, 400) | ||
|
|
||
| def test_learner_can_edit_password_incompatible_with_existing_picture_password( |
There was a problem hiding this comment.
praise: Good coverage of the reverse-direction incompatibility (picture password already enabled, then PATCH learner_can_edit_password=True). This catches regressions that a forward-only test would miss.
28db704 to
8b3e068
Compare
…re passwords instead of text passwords
…cture_password combo in ensure_compatibility
…lling back to instance values
…s already enabled
…_settings null check
8b3e068 to
5313f45
Compare
753ef81 to
4766937
Compare
rtibblesbot
left a comment
There was a problem hiding this comment.
Delta review — the 33 out-of-scope files from the prior review have been removed, but one out-of-scope model field remains.
CI pending. All acceptance criteria from #14351 are met.
Prior findings
8 prior findings unchanged (7 resolved, 1 contested by maintainer).
Partially resolved:
- 33 out-of-scope files (blocking) — files removed, but
enable_mark_attendancefield remains inmodels.py(see inline)
8/9 prior findings resolved. 1 re-raised below.
New findings
- blocking: Out-of-scope
enable_mark_attendancefield (see inline) - praise: Comprehensive test coverage for picture-password settings (see inline)
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
kolibri/core/auth/models.py
Outdated
| learner_can_delete_account = models.BooleanField(default=True) | ||
| learner_can_login_with_no_password = models.BooleanField(default=False) | ||
| show_download_button_in_learn = models.BooleanField(default=True) | ||
| enable_mark_attendance = models.BooleanField(default=False) |
There was a problem hiding this comment.
blocking: enable_mark_attendance is not on develop, is not mentioned in issue #14351, has no migration (will fail makemigrations --check), and is not exposed in any serializer or viewset. This appears to be a leftover from the batch of out-of-scope changes flagged in the prior review. Remove this line — if this field is needed, it should be added in a separate PR with its own migration.
There was a problem hiding this comment.
This happened after rebase.
| } | ||
| self.facility.dataset.save() | ||
| self.client.login(username=self.admin.username, password=DUMMY_PASSWORD) | ||
| response = self.client.patch( |
There was a problem hiding this comment.
praise: Good coverage of the reverse-direction incompatibility (picture password already enabled, then PATCH learner_can_edit_password=True). This catches a real edge case that a simpler test suite would miss.
rtibblesbot
left a comment
There was a problem hiding this comment.
Delta review — all prior findings resolved. CI: unit tests and linting passing; builds and Morango integration tests still pending.
Prior findings
All 13 prior findings remain resolved or acknowledged — no changes since last review.
additionalProperties: Falseon syncable JSON field — RESOLVED- No tests for login enforcement — RESOLVED (code removed per maintainer)
- PR targets
release-v0.19.x— RESOLVED (now targetsdevelop) - Double-negation in learner role check — RESOLVED (code removed)
- Migration missing
JSON_Schema_Validator— RESOLVED - Missing dataset-level test for
no_password + picture_password— CONTESTED (maintainer decision) - Presets missing
picture_password_settings— RESOLVED CreateSessionSerializerlogin enforcement still present — RESOLVED- No tests for token-path gating — RESOLVED
- Serializer
validateon non-dict input — RESOLVED - PR description empty — RESOLVED
- 33 out-of-scope files — RESOLVED
- Out-of-scope
enable_mark_attendancefield — RESOLVED
All acceptance criteria from #14351 are met:
picture_password_settingsJSONField present withdefault=None- Non-null value is the source of truth for picture login enabled
- Migration 0034 created with proper
JSON_Schema_Validator - Field serialized and returned by
FacilityDatasetViewSet,FacilityViewSet, andPublicFacilitySerializer - Tests cover: default value, incompatibility, API response round-trip, invalid
icon_style, non-booleanshow_icon_text
No new issues found. Clean, focused PR.
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Compared the current PR state against findings from a prior review:
- Retrieved prior bot reviews deterministically via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Reviewed the pull request diff checking for correctness, design, architecture, testing, completeness, and adherence to DRY/SRP principles
- Checked CI status and linked issue acceptance criteria
| raise serializers.ValidationError( | ||
| {"picture_password_settings": "show_icon_text must be a boolean"} | ||
| ) | ||
| return attrs |
There was a problem hiding this comment.
praise: Clean serializer validation — the isinstance(settings, dict) guard, enum check, and boolean type check cover all the structural validation needed without over-engineering.
| {"learner_can_edit_password": True}, | ||
| format="json", | ||
| ) | ||
| self.assertEqual(response.status_code, 400) |
There was a problem hiding this comment.
praise: Good test coverage for the reverse-direction incompatibility (picture password already enabled, then PATCH learner_can_edit_password=True). This catches a real edge case that the forward-direction test alone wouldn't cover.
LianaHarris360
left a comment
There was a problem hiding this comment.
Logic is correct and match the intent; this is good to go, thank you Allan! I'll wait to approve & merge it, after the off-cycle patch release has been tagged.
Summary
This PR adds picture_password_settings JSONField on FacilityDataset and update Facility Dataset Serializers and Viewsets to include the new field. Whether picture login is enabled for a facility is determined by checking whether picture_password_settings is null, a non-null value means the setting is active.
picture_password_settingsJSONField toFacilityDataset(default None); a non-null value indicates picture login is enabled for the facilityFacilityDatasetSerializerto expose the field with structural validation (icon_style must be"standard"or"colorful",show_icon_textmust be a boolean)PublicFacilitySerializerto expose the field so the login screen can determine which login UI to showFacilityDatasetViewSetandFacilityViewSetto includepicture_password_settingsin their responsescloses #14351
References
#14351
Reviewer guidance
AI usage
Gemini help a lot in planing and writing some test.