Repository: learningequality/kolibri

Issue:
FacilityDataset.extra_fields fails validation with null/None pin_code
<!--
Instructions:
 * Fill out the sections below, replace …'s with information about your issue
 * Use the 'preview' function above this text box to verify formatting before submitting
-->

## Observed behavior
<!--
Description of the behavior that was observed, including screenshots or other references when applicable
-->
Going through the LOD setup flow, I tried to import a learner from a Facility that had set the pin code to `None` / `null` in JSON, but the sync failed to deserialize.

## Errors and logs
```
INFO     2023-03-30 12:40:43,754 Success! We connected to: http://172.27.64.38:8080/api/public/info/?v=3
INFO     2023-03-30 12:40:45,204 Receiving data (0/13, 0B)
INFO     2023-03-30 12:40:45,210 Receiving data (0/13, 0B)
INFO     2023-03-30 12:40:45,847 Receiving data (13/13, 56.24KB)
INFO     2023-03-30 12:40:45,855 Locally integrating received data
WARNING  2023-03-30 12:40:46,318 Error deserializing instance of FacilityDataset with id 7e5258574c0fad31877c19af6c3d2772: {'extra_fields': ["None does not match type 'string'"]}
```

In the `morango_store`, the serialized `FacilityDataset`
```
"extra_fields": "{\n  \"on_my_own_setup\":false,\n  \"pin_code\":null\n}"
```

## Expected behavior
<!--
Description of what behavior was expected but did not occur
-->
The pin code should allow to be `None`/`null`, but the `extra_fields` validation does not allow it

## Steps to reproduce
<!--
Precise steps that someone else can follow in order to see this behavior
-->
Two instances of Kolibri are required

### Setup
For Instance A
1) Provision a new full-facility Kolibri instance through the setup wizard: Group learning > Full device > Create new learning facility
2) Complete the setup
2) Navigate to Facility > Users
2) Add a new Learner, remember their password
3) Navigate to Facility > Settings
4) Set a PIN for the facility <kbd>Create PIN</kbd>
5) Keep this instance running

For Instance B
1) Provision a new learn-only Kolibri instance, running on a different port than Instance A: Group learning > Learn-only device > Create new learning facility > Import one or more existing user accounts from an existing facility
2) Select Instance A from the 'Select network address' modal
3) Input the Learner credentials you created on Instance A
4) Complete the setup

### Reproduction
1) On Instance A, navigate to Facility > Settings and remove the PIN
2) Restart Instance B, it should automatically sync with Instance A within ~1 minute after restarting
3) On Instance B, navigate to Device, and verify you're not prompted for a PIN
4) Additionally, after successful syncing there should be no log entries in Instance B's logs matching:
```
Error deserializing instance of FacilityDataset with id 7e5258574c0fad31877c19af6c3d2772: {'extra_fields': ["None does not match type 'string'"]}
```

## Context
<!--
Tell us about your environment, including:
 * Kolibri version
 * Operating system
 * Browser
-->
Kolibri 0.16.0a11



Discussion:
I would like to work on this issue @bjester 
Hi @photon0205,

Thanks for you interest in contributing to Kolibri. This issues is available to be worked on, but it is for an upcoming release and therefore is time sensitive. If you are unable to work on this issue in the next two weeks, it will be reassigned to someone on our team.

If you are looking for a less-time sensitive issue, you can choose from [the help wanted tag on Kolibri,](https://github.com/learningequality/kolibri/issues?q=is%3Aopen+is%3Aissue+label%3A%22TAG%3A+help+wanted%22) or [the beginner friendly issues on Studio](https://github.com/learningequality/studio/issues?q=is%3Aopen+is%3Aissue+label%3A%22TAG%3A+beginner+friendly%22).

Thanks again for your interest, and comment on the issue if you have any questions along the way.
Alright @akolson, I will try my best to resolve this issue on time and will get back to you soon
@akolson how could I join the Kolibri WAN ZeroTier network, I am not having the network ID of the Kolibri WAN ZeroTier network?
@photon0205 Please write a regression unit test which sets the `pin_code` to `None`/`null`, and then calls `.clean_fields()` on the model. Calling that method should invoke the field validator, which isn't invoked by default, but is invoked during a sync. The test should currently fail without any fix and pass after the fix. I will also re-write the steps to reproduce, but be aware that it will be much more involved than writing a test.
Hi @photon0205. Could you please confirm you are able to proceed with resolving this issue after our latest comment? If unable, it will be reassigned to someone on our team to resolve. Thanks
Yes @akolson , I am currently working on resolving the issue and have started writing the regression unit test as requested. If I encounter any issues, I will reach out for further assistance. Thank you.

Resolving patch:
diff --git a/kolibri/core/auth/models.py b/kolibri/core/auth/models.py
--- a/kolibri/core/auth/models.py
+++ b/kolibri/core/auth/models.py
@@ -89,7 +89,7 @@
     "properties": {
         "facility": {"type": "object", "optional": True},
         "on_my_own_setup": {"type": "boolean", "optional": True},
-        "pin_code": {"type": "string", "optional": True},
+        "pin_code": {"type": ["string", "null"], "optional": True},
     },
 }
 


Test patch:
diff --git a/kolibri/core/auth/test/test_datasets.py b/kolibri/core/auth/test/test_datasets.py
--- a/kolibri/core/auth/test/test_datasets.py
+++ b/kolibri/core/auth/test/test_datasets.py
@@ -135,6 +135,10 @@ def test_dataset_incompatible_setting(self):
                 learner_can_edit_password=True, learner_can_login_with_no_password=True
             )
 
+    def test_null_pin_code(self):
+        facility_dataset = FacilityDataset(extra_fields={"pin_code": None})
+        facility_dataset.clean_fields()
+
 
 class MultipleDatasetAssignmentTestCase(TestCase):
     """