Skip to content

Commit

Permalink
Merge pull request #223 from rcpch/add-organisation-button-npdauser-form
Browse files Browse the repository at this point in the history
Add-remove-organisation-button-npdauser-form
  • Loading branch information
anchit-chandran committed Aug 1, 2024
2 parents 9d97a66 + c74393b commit 197d2b7
Show file tree
Hide file tree
Showing 44 changed files with 949 additions and 367 deletions.
16 changes: 7 additions & 9 deletions documentation/docs/developer/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,27 @@ Database structure has few tables. The models are as follows:

- **Patient**: There is one record for each child in the audit
- **Visit**: There are multiple records for each patient. In NPDA, children are seen 4 times a year in clinic and have additional contacts. During these visits details regarding care processes and diabetes management are captured.
- **Site**: There is only one active site at any one time, but more than one site might be responsible for the care of a patient over the audit year.

### Reporting tables

Tables also track the progress of each child through the audit, as well how they are scoring with regard to their key performance indicators (KPIs). These KPIs are aggregated periodically to feed reports on KPIs at different levels of abstractions (organisational, trust-level or health board, integrated care board, NHS England region and country level)

- **AuditProgress**: Has a one to one relationship with Registration. Stores information on how many fields in each form have been completed.
- **KPI**: scores each individual child against the national KPI standards. It stores information on whether a given measure has been passed, failed, has yet to be completed, or whether the child in not eligible to be scored.
- **KPIAggregation**: This base model stores results of aggregations of each measure. The base model is subclassed for models representing each geographical level of abstraction. Aggregations are run at scheduled intervals asynchronously and pulled into the dashboards.
- **OrganisationEmployer**: This model serves the middle model between NPDAUser and PaediatricDiabetesUnit. It tracks the number of organisations/PDUs a user is a member of and which is the primary organisation.
- **Submission**: This tracks all csv upload submissions and allocates them to audit years and quarters thereof. Only one active submission at a time can exist. New ones will overwrite the previous ones.
- **Transfer**: This tracks any transfers between paediatric diabetes units. It stores the reason for transferring and the date of transfer. It provide the middle table between Patient and Paediatric Diabetes Unit
- **VisitActivity**: Stores user access/visit activity, including number of login attempts and ISP address as well as timestamp

### Link Tables

There are some many to many relationships. Django normally handles this for you, but the development team chose to implement the link tables in these cases separately to be able to store information about the relationship between the tables.

- **Site**: The relationships here are complicated since one child may have their diabetes care in different Organisations across a year, though only one centre can be active in the care of a child at any one time. Each Case therefore can have a many to many relationship with the Organisation trust model (since one Organisation can have multiple Cases and one Case can have multiple Organisations). The Site model therefore is a link model between the two. It is used in this way, rather than relying on the Django built-in many-to-many solution, because additional information relating to the organisation can be stored per Case, for example whether the site is actively involved in diabetes care.

### Lookup Tables

These classes are used as look up tables throughout the NPDA application. They are seeded in the first migrations, either pulling content from the the ```constants``` folder, or from SNOMED CT. Note that the RCPCH NHS Organisation repository maintains the primary source list and these models are kept up to date against this periodically.
The RCPCH NHS Organisation repository maintains the primary source list and these models are kept up to date against this periodically.

- **NPDAUser**: The User base model in Django is too basic for the requirements of NPDA and therefore a custom class has been created to describe the different users who either administer or deliver the audit, either on behalf of RCPCH, or the hospital trusts.

### Schema / ERD

<div style="width: 640px; height: 480px; margin: 10px; position: relative;"><iframe allowfullscreen frameborder="0" style="width:640px; height:480px" src="https://lucid.app/documents/embedded/c09d5aa7-3b32-49b0-a704-ede60f8141f7" id="DJQYgxoCtQxo"></iframe></div>

#### Boundary files and geography extension pack

Expand Down
4 changes: 0 additions & 4 deletions documentation/docs/developer/organisations.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ These exist only in Wales and are both equivalent to Trust and ICB in England. O

Paediatric Diabetes Units (PSUs) are usually Organisations or Trusts which submit audit data for children. PDUs have unique PZ codes.

### OPENUK Networks

These are [networks](https://www.rcpch.ac.uk/resources/open-uk-organisation-paediatric-epilepsy-networks-uk) of NHS Health Boards and Trusts that provide care for children with epilepsies, organised regionally and overseen by a UK Working Group. Not all centres are members of an OPEN UK network. There are no boundary shapes to describe each region, but each one has its own identifier, and therefore there is an entity model to hold information on each OPEN UK network referenced by each organisation.

### NHS England Regions

There are 7 of these in England and their model is taken from NHS Digital. Each one has its own boundary code. ICBs fit neatly inside each one.
Expand Down
198 changes: 198 additions & 0 deletions documentation/docs/developer/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
---
title: Testing
reviewers: Dr Anchit Chandran
---

## Getting started

Run all tests from the root directory:

```shell
pytest
```

## Useful Pytest flags

Some useful flags to improve the DX of testing.

### Only re-run failing tests

```shell
pytest -lf
```

### Run a specific test file, e.g. run all tests in only `test_npda_user_model_actions.py`

```shell
pytest project/npda/tests/permissions_tests/test_npda_user_model_actions.py
```

Use `::` notation to run a specific test within a file e.g.:

```shell
pytest project/npda/tests/permissions_tests/test_npda_user_model_actions.py::test_npda_user_list_view_rcpch_audit_team_can_view_all_users
```

### Start a completely clean run, clearing the cache (usually not required)

```shell
pytest --cache-clear
```

### Run tests through keyword expression

NOTE: this is sometimes slightly slower.

```shell
pytest -k "MyClass and not method"
```

## `pytest.ini` config file

Explanations and notes on our global `pytest.ini` configuration.

### Creating new tests

Test files are found as any `.py` prepended with `test_`:

```ini
# SET FILENAME FORMATS OF TESTS
python_files = test_*.py
```

### Flags set for all `pytest` runs

```init
# RE USE TEST DB AS DEFAULT
addopts =
--reuse-db
-k "not examples"
```

- `--reuse-db` allows a specified starting state testing database to be used between tests. All tests begin with this seeded starting state. The testing db is rolled back to the starting state after each state.

## Test Database

Every first test in a file should include the following fixtures to ensure the test database is correctly set up for when a particular test file is run independently:

```python
@pytest.mark.django_db
def test_npda_user_list_view_users_can_only_see_users_from_their_pdu(
seed_users_fixture,
seed_groups_fixture,
seed_patients_fixture,
):
```

### `seed_users_fixture`

The testing database should include `8 NPDAUsers`.

At each of the `2` PDUs, GOSH and Alder Hey:

```py title='seed_users.py'
GOSH_PZ_CODE = "PZ196"
ALDER_HEY_PZ_CODE = "PZ074"
```

The following `4` user types are seeded:

```py title='seed_users.py'
users = [
test_user_audit_centre_reader_data,
test_user_audit_centre_editor_data,
test_user_audit_centre_coordinator_data,
test_user_rcpch_audit_team_data,
]
```

### `seed_groups_fixture`

Uses the `groups_seeder` to set `Groups` for `NPDAUsers`.

### `seed_patients_fixture`

*not yet implemented*

## Factories

Factories enable the intuitive and fast creation of testing instances, particularly in cases where multiple related models are required.

Factories are set up to set sensible defaults wherever possible, which can be overridden through keyword arguments.

Ideally, in all tests, if a model instance is being created, it should be done through its model Factory.

### `NPDAUserFactory`

Example usage below.

NOTE: we do not need to manually create `OrganisationEmployer`s and `PaediatricsDiabetesUnit` with associations.

Once an instance of `NPDAUserFactory` is created, the related models will also be created and assigned the relations. These are set using the `organisation_employers` kwarg, with the value being an array of `pz_codes` as strings.

```py title="seed_users.py"
# GOSH User
new_user_gosh = NPDAUserFactory(
first_name=first_name,
role=user.role,
# Assign flags based on user role
is_active=is_active,
is_staff=is_staff,
is_rcpch_audit_team_member=is_rcpch_audit_team_member,
is_rcpch_staff=is_rcpch_staff,
groups=[user.group_name],
view_preference=(
VIEW_PREFERENCES[2][0]
if user.role == RCPCH_AUDIT_TEAM
else VIEW_PREFERENCES[0][0]
),
organisation_employers=[GOSH_PZ_CODE],
)

# Alder hey user
new_user_alder_hey = NPDAUserFactory(
first_name=first_name,
role=user.role,
# Assign flags based on user role
is_active=is_active,
is_staff=is_staff,
is_rcpch_audit_team_member=is_rcpch_audit_team_member,
is_rcpch_staff=is_rcpch_staff,
groups=[user.group_name],
organisation_employers=[ALDER_HEY_PZ_CODE],
)
```

### `PatientFactory`

Once an instance of `PatientFactory` is created, a related `TransferFactory` instance is also generated with the associated `PaediatricsDiabetesUnitFactory` instance.

### `PaediatricsDiabetesUnitFactory`

Multiple parent factories require the instantiation of multiple `PaediatricsDiabetesUnitFactory`s.

As there is a composite unique constraint set on the [`pz_code`, `ods_code`] attributes, we do not want to create multiple instances with duplicate values; instead, we want to mimic the Django ORM's `.get_or_create()` method.

This is emulated using an override on this factory's `._create()` method:

```py title="paediatrics_diabetes_unit_factory.py"
@classmethod
def _create(cls, model_class, *args, **kwargs):
"""
Custom create method to handle get_or_create logic for PaediatricsDiabetesUnit.
Each PDU has a composite unique constraint for pz_code and ods_code. This mimics
a get or create operation every time a new PDUFactory instance is created.
"""
pz_code = kwargs.pop("pz_code", None)
ods_code = kwargs.pop("ods_code", None)

if pz_code and ods_code:
pdu, created = PaediatricDiabetesUnit.objects.get_or_create(
pz_code=pz_code,
ods_code=ods_code,
)
return pdu

return super()._create(model_class, *args, **kwargs)
```
1 change: 1 addition & 0 deletions documentation/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,4 @@ nav:
- 'developer/manual-setup.md'
- 'developer/organisations.md'
- 'developer/users.md'
- 'developer/testing.md'
13 changes: 13 additions & 0 deletions project/constants/pz_codes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
from dataclasses import dataclass
from enum import Enum

@dataclass
class PZCode:
pz_code: str
name: str

class DummyPZCodes(Enum):
RCPCH = PZCode(pz_code="PZ999", name="RCPCH")
NOT_FOUND = PZCode(pz_code="PZ998", name="NOT_FOUND")


pzs = [
"RM102",
"RXF05",
Expand Down
8 changes: 8 additions & 0 deletions project/npda/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.apps import apps
from django.contrib import admin

from .models import (
Expand All @@ -11,6 +12,8 @@
)
from django.contrib.sessions.models import Session

PaediatricDiabetesUnit = apps.get_model("npda", "PaediatricDiabetesUnit")


@admin.register(OrganisationEmployer)
class OrganisationEmployerAdmin(admin.ModelAdmin):
Expand All @@ -27,6 +30,11 @@ class PatientAdmin(admin.ModelAdmin):
search_fields = ("nhs_number_icontains", "pk")


@admin.register(PaediatricDiabetesUnit)
class PaediatricDiabetesUnitAdmin(admin.ModelAdmin):
search_fields = ("pk", "ods_code", "pz_code")


@admin.register(Transfer)
class TransferAdmin(admin.ModelAdmin):
search_fields = ("paediatric_diabetes_unit", "patient", "pk")
Expand Down
Loading

0 comments on commit 197d2b7

Please sign in to comment.