Repository: jupyterhub/jupyterhub

Issue:
user_options returning empty at users rest api after jupyterhub restart

### Bug description
I’ve noticed that after jupyterhub is restarted the user_options are empty i.e. {}
when submitting /hub/api/users request
(before restart I’m getting user_options which are persisted in db due implemented in persist user_options (https://github.com/jupyterhub/jupyterhub/pull/2446)

from looking at code it might be connected to the fact that in apihandler’s [server_model takes user_options from spawner and not spawner.orm_spawner] (https://github.com/jupyterhub/jupyterhub/blob/f5bb0a2622be51b02a64eaa23b10b5f66461ba2b/jupyterhub/apihandlers/base.py#L204)

I've patched as suggested above our private jupyterhub, then tested manually that it indeed solves the problem

#### Expected behaviour
my expectation that user_options will be properly returned after jupyter hub restart

#### Actual behaviour
user_options are empty {}

### How to reproduce
1. create pod with user_options
2. make sure it's up
3. access jupyterhub api of users
4. make sure user_options are returned as part of response with custom values inside
4. restart jupyter hub
4. access jupyterhub api of users
5. make sure user_options are returned but empty {}

### Your personal set up
zero-to-jupyterhub


Discussion:
Thank you for opening your first issue in this project! Engagement like this is essential for open source projects! :hugs:
<br>If you haven't done so already, check out [Jupyter's Code of Conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md). Also, please try to follow the issue template as it helps other other community members to contribute more effectively.
![welcome](https://raw.githubusercontent.com/jupyterhub/.github/master/images/welcome.jpg)
You can meet the other [Jovyans](https://jupyter.readthedocs.io/en/latest/community/content-community.html?highlight=jovyan#what-is-a-jovyan) by joining our [Discourse forum](http://discourse.jupyter.org/). There is also an intro thread there where you can stop by and say Hi! :wave:
<br>Welcome to the Jupyter community! :tada:


Resolving patch:
diff --git a/jupyterhub/user.py b/jupyterhub/user.py
--- a/jupyterhub/user.py
+++ b/jupyterhub/user.py
@@ -376,6 +376,7 @@ def _new_spawner(self, server_name, spawner_class=None, **kwargs):
             oauth_client_id=client_id,
             cookie_options=self.settings.get('cookie_options', {}),
             trusted_alt_names=trusted_alt_names,
+            user_options=orm_spawner.user_options or {},
         )
 
         if self.settings.get('internal_ssl'):


Test patch:
diff --git a/jupyterhub/tests/test_spawner.py b/jupyterhub/tests/test_spawner.py
--- a/jupyterhub/tests/test_spawner.py
+++ b/jupyterhub/tests/test_spawner.py
@@ -81,6 +81,18 @@ async def test_spawner(db, request):
     assert isinstance(status, int)
 
 
+def test_spawner_from_db(app, user):
+    spawner = user.spawners['name']
+    user_options = {"test": "value"}
+    spawner.orm_spawner.user_options = user_options
+    app.db.commit()
+    # delete and recreate the spawner from the db
+    user.spawners.pop('name')
+    new_spawner = user.spawners['name']
+    assert new_spawner.orm_spawner.user_options == user_options
+    assert new_spawner.user_options == user_options
+
+
 async def wait_for_spawner(spawner, timeout=10):
     """Wait for an http server to show up