Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
40cb35e
Port across new decorator
mbarton Apr 20, 2026
1892dc7
Pass can_edit to fpa
mbarton Apr 20, 2026
3dc0ce8
can_edit for fpa
mbarton Apr 20, 2026
cce8d5c
can_edit for registration view
mbarton Apr 20, 2026
2389782
can_edit for epilepsy context
mbarton Apr 20, 2026
5ec2ce9
can_edit should check permission too
mbarton Apr 20, 2026
9485225
wip can_edit for multiaxial diagnosis
mbarton Apr 20, 2026
96dba1a
Need can_edit in the multiaxial_diagnosis callbacks
mbarton Apr 20, 2026
946204d
Add more special cases to lookup
mbarton Apr 20, 2026
f2c6145
Remove user_may_edit_this_child
mbarton Apr 20, 2026
bd0d319
can_edit in syndrome_views
mbarton Apr 20, 2026
61140b4
can_edit in assessment_views.py
mbarton Apr 20, 2026
1408b2a
Correctly disable Date of epilepsy surgery centre review is known
mbarton Apr 20, 2026
67eda78
can_edit in investigation_views.py
mbarton Apr 20, 2026
8c6730d
can_edit in management_views
mbarton Apr 20, 2026
bcd5385
can_edit in case_views
mbarton Apr 20, 2026
4c32646
Some ones missed earlier
mbarton Apr 20, 2026
cba1b27
Copilot typo typo
mbarton Apr 20, 2026
a19442e
can_edit for case form
mbarton Apr 21, 2026
ba917ca
Enable buttons on case list for closed cohorts
mbarton Apr 21, 2026
a0fdf8a
Deny change operations unless you can edit
mbarton Apr 21, 2026
b3d301d
Fixed decorator security bugs
mbarton Apr 21, 2026
989ee9d
Allow access to inactive organisations and add adverserial tests for …
mbarton Apr 21, 2026
6276822
Revert general fix as it breaks other tests
mbarton Apr 21, 2026
508e31c
Always register cases in the currently open cohort
mbarton Apr 21, 2026
ad03a1b
Register more cases in open cohort
mbarton Apr 21, 2026
b970826
Merge branch 'live' into mbarton/view-old-cohorts-2
mbarton Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 88 additions & 83 deletions epilepsy12/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,95 +225,100 @@ def wrapper(request, *args, **kwargs):
return decorator


def user_may_view_this_child():
# decorator receives case_id or registration_id from view as argument.
# access is granted only to users who are either:
def lookup_child_if_user_has_permission(request_kwargs, user):
# Guard against users with no active primary employer
if not user.organisation_employer:
return None

via_registration = lambda obj: obj.registration.case
via_multiaxial_dagnosis = lambda obj: obj.multiaxial_diagnosis.registration.case
via_management = lambda obj: obj.management.registration.case

lookup = {
"registration_id": (Registration, lambda reg: reg.case),
"management_id": (Management, via_registration),
"investigations_id": (Investigations, via_registration),
"first_paediatric_assessment_id": (FirstPaediatricAssessment, via_registration),
"epilepsy_context_id": (EpilepsyContext, via_registration),
"multiaxial_diagnosis_id": (MultiaxialDiagnosis, via_registration),
"episode_id": (Episode, via_multiaxial_dagnosis),
"syndrome_id": (Syndrome, via_multiaxial_dagnosis),
"comorbidity_id": (Comorbidity, via_multiaxial_dagnosis),
"antiepilepsy_medicine_id": (AntiEpilepsyMedicine, via_management),
"assessment_id": (Assessment, via_registration),
"case_id": (Case, lambda o: o),
}

for key, (model, via_fn) in lookup.items():
pk = request_kwargs.get(key)

if pk is not None:
obj = model.objects.get(pk=pk)
child = via_fn(obj)

org_filters = {
"cases": child,
"patient_sites__site_is_actively_involved_in_epilepsy_care": True,
"patient_sites__site_is_primary_centre_of_epilepsy_care": True,
# Access is sliced by trust - so members of other organisations in that trust can see data
"trust": user.organisation_employer.trust,
}

if Organisation.objects.filter(**org_filters).exists():
return child


def lookup_user_permissions_on_child(request, request_kwargs):
# Lookup sub object by id and walk backwards to case.
# Access is granted only to users who are either:
# 1. superusers
# 2. Active RCPCH audit members
# 3. Active trust level users where their trust is the same as the child
# 2. Active RCPCH audit members with confirmed email
# 3. Active trust level users with confirmed email where their trust is the same as the child
# Editing is allowed if the cohort is still open or if you are an RCPCH audit member
user = request.user

# Check user is active and has confirmed their email (unless superuser)
if not user.is_superuser and not (user.is_active and user.email_confirmed):
return {
"can_view": False,
"can_edit": False,
}

is_admin = user.is_rcpch_audit_team_member or user.is_rcpch_staff or user.is_superuser

if is_admin:
return {
"can_view": True,
"can_edit": True,
}

child = lookup_child_if_user_has_permission(request_kwargs, request.user)

if child:
return {
"can_view": True,
"can_edit": child.editable(),
}

return {
"can_view": False,
"can_edit": False,
}


def user_may_view_this_child():
def decorator(view):
def wrapper(request, *args, **kwargs):
user = request.user
if (user.is_active and user.email_confirmed) or user.is_superuser:
# user is registered and active or a superuser
if kwargs.get("registration_id") is not None:
registration = Registration.objects.get(
pk=kwargs.get("registration_id")
)
child = registration.case
elif kwargs.get("management_id") is not None:
management = Management.objects.get(pk=kwargs.get("management_id"))
child = management.registration.case
elif kwargs.get("investigations_id") is not None:
investigations = Investigations.objects.get(
pk=kwargs.get("investigations_id")
)
child = investigations.registration.case
elif kwargs.get("first_paediatric_assessment_id") is not None:
first_paediatric_assessment = FirstPaediatricAssessment.objects.get(
pk=kwargs.get("first_paediatric_assessment_id")
)
child = first_paediatric_assessment.registration.case
elif kwargs.get("epilepsy_context_id") is not None:
epilepsy_context = EpilepsyContext.objects.get(
pk=kwargs.get("epilepsy_context_id")
)
child = epilepsy_context.registration.case
elif kwargs.get("multiaxial_diagnosis_id") is not None:
multiaxial_diagnosis = MultiaxialDiagnosis.objects.get(
pk=kwargs.get("multiaxial_diagnosis_id")
)
child = multiaxial_diagnosis.registration.case
elif kwargs.get("episode_id") is not None:
episode = Episode.objects.get(pk=kwargs.get("episode_id"))
child = episode.multiaxial_diagnosis.registration.case
elif kwargs.get("syndrome_id") is not None:
syndrome = Syndrome.objects.get(pk=kwargs.get("syndrome_id"))
child = syndrome.multiaxial_diagnosis.registration.case
elif kwargs.get("comorbidity_id") is not None:
comorbidity = Comorbidity.objects.get(
pk=kwargs.get("comorbidity_id")
)
child = comorbidity.multiaxial_diagnosis.registration.case
elif kwargs.get("antiepilepsy_medicine_id") is not None:
antiepilepsy_medicine = AntiEpilepsyMedicine.objects.get(
pk=kwargs.get("antiepilepsy_medicine_id")
)
child = antiepilepsy_medicine.management.registration.case
elif kwargs.get("assessment_id") is not None:
assessment = Assessment.objects.get(pk=kwargs.get("assessment_id"))
child = assessment.registration.case
elif kwargs.get("case_id") is not None:
case = Case.objects.get(pk=kwargs.get("case_id"))
child = case

if user.is_rcpch_audit_team_member:
organisation = Organisation.objects.filter(
cases=child,
patient_sites__site_is_actively_involved_in_epilepsy_care=True,
patient_sites__site_is_primary_centre_of_epilepsy_care=True,
)
else:
# filter for object where trust (not just organisation) where case is registered is the same as that of user
organisation = Organisation.objects.filter(
cases=child,
patient_sites__site_is_actively_involved_in_epilepsy_care=True,
patient_sites__site_is_primary_centre_of_epilepsy_care=True,
trust=request.user.organisation_employer.trust,
)
permissions = lookup_user_permissions_on_child(request, kwargs)

if (
organisation.exists()
or user.is_rcpch_audit_team_member
or user.is_rcpch_staff
or user.is_superuser
):
return view(request, *args, **kwargs)
else:
raise PermissionDenied()
else:
if request.method not in ["GET", "HEAD", "OPTIONS"] and not permissions["can_edit"]:
raise PermissionDenied()

if permissions["can_view"]:
return view(request, permissions["can_edit"], *args, **kwargs)

raise PermissionDenied()

return wrapper

return decorator
Expand Down
8 changes: 8 additions & 0 deletions epilepsy12/forms_folder/case_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def __init__(self, *args, **kwargs) -> None:
self.organisation_id = kwargs.pop(
"organisation_id", None
) # This is the organisation_id
can_edit = kwargs.pop("can_edit", True) # Default to True if not provided

# set a flag to check if this is Jersey
self.is_jersey = (
Expand All @@ -98,6 +99,13 @@ def __init__(self, *args, **kwargs) -> None:
super(CaseForm, self).__init__(*args, **kwargs)
self.existing_nhs_number = self.instance.nhs_number
self.fields["ethnicity"].widget.attrs.update({"class": "ui rcpch dropdown"})

# Disable all fields if user cannot edit
if not can_edit:
for field_name, field in self.fields.items():
field.widget.attrs['disabled'] = True
field.widget.attrs['readonly'] = True

if self.is_jersey:
# this is Jersey - hide the NHS number field
self.fields["nhs_number"].widget = forms.HiddenInput()
Expand Down
Loading
Loading