Introduce Guardian Portal and enhance portal API permissions - #451
Merged
Conversation
- Add ownership checks for student-scoped endpoints (programs, invoices, attendance), guard student group and assessment result access by doctype permission, and remove whitelist decorators from internal helpers (get_student_guardians, get_result).
Extend the SPA portal to support Guardian users in addition to Students.
A guardian lands on a student-selector page showing their wards and, after
picking one, reuses the existing Schedule/Grades/Fees/Attendance pages
scoped to the selected student. Also rebrands the portal route from
"student-portal" to the role-neutral "edu-portal".
Backend (education/education/api.py):
- Add get_portal_context() as the single role-aware bootstrap call,
returning {role, student} for students and {role, guardian, students}
for guardians
- Add get_guardian_info() and get_guardian_students() that resolve the
guardian strictly from the session user (no client-supplied id)
- Add get_student_context(student) and refactor get_student_info() to
delegate to it; both reuse get_current_enrollment/get_student_groups
- Add get_user_role() returning Guardian/Student/None
- Add get_guardian_for_user()/is_guardian_of() helpers and extend
check_permission() so a guardian linked via Student Guardian is
authorized for their wards (student-owner and Administrator still pass);
get_student_programs/get_student_invoices/get_student_attendance remain
permission-checked
Roles & provisioning:
- Add create_guardian_role patch (desk-access-less "Guardian" role) and
register it in patches.txt
- invite_guardian() now assigns the Guardian role to the created user
Routing & serving:
- Rename website route rule and page to /edu-portal (hooks.py), rename
www/student_portal.py -> www/edu_portal.py, and fix the build copy
target to www/edu-portal.html (package.json); update .gitignore
Frontend:
- Add stores/portal.js (role + ward list + persisted activeStudentId)
- Refactor stores/student.js to load the active student's context
reactively (self vs. selected ward)
- Add pages/Students.vue (ward cards) and the /edu-portal router guard
that loads portal context, redirects guardians without a selection to
the cards page, sends non-portal users to the desk, and fixes the
invalid next(false) call
- Update Navbar/Sidebar/SidebarLink/ProfileModal for the role-aware UI
Substantive changes:
- guardian.py: run the "Guardian" write permission check (has_permission
..., throw=True) before loading the Guardian document in invite_guardian,
so authorization is enforced prior to any document fetch
- api.py: in check_permission, replace the _(f"...{resource_type}") f-string
with _("...{0}").format(resource_type); f-strings inside _() cannot be
extracted for translation, this preserves the same message while keeping
it translatable
Add a read-only "Guardian" permission entry to the doctypes the guardian portal needs so guardian users can view their students' data. Each grant is read/print/email/export/report/share only - no write, create, delete, submit, or cancel - keeping guardians strictly read-only. Doctypes updated: - Assessment Result - Course Schedule - Guardian - Program - Program Enrollment - Student - Student Attendance - Student Leave Application
Confidence Score: 4/5The Guardian portal paths need fixes before merging. Broad DocPerms can expose unrelated student records, and role setup can leave guardians unable to enter the portal.
education/education/doctype/*.json Guardian permissions, education/education/doctype/guardian/guardian.py, education/install.py, frontend/src/components/ProfileModal.vue
|
| Filename | Overview |
|---|---|
| education/education/api.py | Adds role-aware portal endpoints and per-student checks for custom portal reads. |
| education/education/doctype/student/student.json | Adds broad Guardian read/export/report/share access to Student records. |
| education/education/doctype/guardian/guardian.py | Adds Guardian role assignment for newly created users but skips existing users. |
| education/install.py | Defines Guardian role setup but does not run it on fresh install. |
| frontend/src/components/ProfileModal.vue | Adapts the profile modal for guardian and student profiles, but reads async store state non-reactively. |
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 4
education/education/doctype/student/student.json:387-395
**Guardian Access Is Global**
This DocPerm gives every Guardian role direct read/export/report/share access to `Student` records through standard Frappe endpoints, without the linked-ward check used by the portal APIs. A guardian can enumerate unrelated students; the same permission pattern on attendance, results, leave, and schedules exposes those records too.
### Issue 2 of 4
education/education/doctype/guardian/guardian.py:48-52
**Existing Users Miss Role**
When the guardian email already has a `User`, this branch returns before adding the `Guardian` role. That invited guardian then reaches `get_user_role()` with no portal role and gets redirected away from the portal.
```suggestion
if guardian_as_user:
user = frappe.get_doc("User", guardian_as_user)
user.add_roles("Guardian")
user.save(ignore_permissions=True)
frappe.msgprint(
_("User {0} already exists").format(getlink("User", guardian_as_user))
)
return guardian_as_user
```
### Issue 3 of 4
education/install.py:7-13
**Fresh Installs Miss Role**
`create_guardian_role()` is defined but not called during install. On a fresh site, `invite_guardian()` can try to add a role that was never created, so guardian invitation or portal access fails until a patch is run.
```suggestion
def after_install():
setup_fixtures()
create_student_role()
create_guardian_role()
create_parent_assessment_group()
create_invoice_permissions()
create_custom_fields(get_custom_fields())
create_permissions(get_permissions())
```
### Issue 4 of 4
frontend/src/components/ProfileModal.vue:76-81
**Profile State Freezes Empty**
This reads portal and student refs once during setup, before the async portal context and active student data may finish loading. If the modal mounts early, `profileInfo` stays blank or uses the wrong branch after the store updates, so the profile dialog shows missing guardian/student data.
Reviews (1): Last reviewed commit: "Merge branch 'version-16' into version-1..." | Re-trigger Greptile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.