diff --git a/.gitignore b/.gitignore
index be2a45a..894663e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,7 @@ doc/source/sourcecode
 zun_ui/test/.secret_key_store
 .venv
 .tox
-*.pyc
+*.py[cod]
 *.lock
 *.egg*
 *.swp
diff --git a/.gitreview b/.gitreview
index 41dff74..0628ce8 100644
--- a/.gitreview
+++ b/.gitreview
@@ -1,4 +1,4 @@
 [gerrit]
 host=review.openstack.org
-port= # need to set for zun-ui
+port= 29418
 project=openstack/zun-ui.git
diff --git a/setup.cfg b/setup.cfg
index 24cbcb5..bd56863 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -21,3 +21,8 @@ classifier =
 [files]
 packages =
     zun_ui
+
+[build_sphinx]
+all_files = 1
+build-dir = doc/build
+source-dir = doc/source
diff --git a/tox.ini b/tox.ini
index ed72ed2..7e8c056 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
-envlist = py27,py27dj18,pep834
-minversion = 1.6
+envlist = py34,py27,py27dj18,pep8
+minversion = 2.0
 skipsdist = True
 
 [testenv]
@@ -11,72 +11,28 @@ setenv = VIRTUAL_ENV={envdir}
          NOSE_OPENSTACK_RED=0.05
          NOSE_OPENSTACK_YELLOW=0.025
          NOSE_OPENSTACK_SHOW_ELAPSED=1
-# Note the hash seed is set to 0 until horizon can be tested with a
-# random hash seed successfully.
-         PYTHONHASHSEED=0
 install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -U {opts} {packages}
 deps = -r{toxinidir}/requirements.txt
        -r{toxinidir}/test-requirements.txt
-commands = /bin/bash run_tests.sh -N --no-pep8 {posargs}
+commands = python manage.py test {posargs} --settings=zun_ui.test.settings
 
 [testenv:pep8]
-commands =
-  /bin/bash run_tests.sh -N --pep8
-  /bin/bash run_tests.sh -N --makemessages --check-only
+commands = flake8 {posargs}
 
 [testenv:venv]
-# NOTE(shu-mutou) The setting of the install_command in this location
-# is only required because currently infra does not actually
-# support constraints files for the post job, and while
-# the environment variable UPPER_CONSTRAINTS_FILE is set, there is
-# no file there. It can be removed when infra changes this.
-install_command = pip install -U {opts} {packages}
 commands = {posargs}
 
 [testenv:cover]
-# NOTE(shu-mutou) The setting of the install_command in this location
-# is only required because currently infra does not actually
-# support constraints files for the post job, and while
-# the environment variable UPPER_CONSTRAINTS_FILE is set, there is
-# no file there. It can be removed when infra changes this.
-install_command = pip install -U {opts} {packages}
-commands = /bin/bash run_tests.sh -N --no-pep8 --coverage {posargs}
+commands = python setup.py test --coverage --testr-args='{posargs}'
 
 [testenv:py27dj18]
-# NOTE(shu-mutou) The setting of the install_command in this location
-# is only required because currently infra does not actually
-# support constraints files for the post job, and while
-# the environment variable UPPER_CONSTRAINTS_FILE is set, there is
-# no file there. It can be removed when infra changes this.
-install_command = pip install -U {opts} {packages}
 basepython = python2.7
-commands = pip install django>=1.8,<1.9
-           /bin/bash run_tests.sh -N --no-pep8 {posargs}
-
-[testenv:py27integration]
-# NOTE(shu-mutou) The setting of the install_command in this location
-# is only required because currently infra does not actually
-# support constraints files for the post job, and while
-# the environment variable UPPER_CONSTRAINTS_FILE is set, there is
-# no file there. It can be removed when infra changes this.
-install_command = pip install -U {opts} {packages}
-basepython = python2.7
-commands = /bin/bash run_tests.sh -N --integration --selenium-headless {posargs}
-
-[testenv:eslint]
-passenv = *
-commands = nodeenv -p
-           npm install
-           /bin/bash run_tests.sh -N --eslint
+commands =
+    pip install django>=1.8,<1.9
+    python manage.py test {posargs} --settings=zun_ui.test.settings
 
 [testenv:docs]
-# NOTE(shu-mutou) The setting of the install_command in this location
-# is only required because currently infra does not actually
-# support constraints files for the post job, and while
-# the environment variable UPPER_CONSTRAINTS_FILE is set, there is
-# no file there. It can be removed when infra changes this.
-install_command = pip install -U {opts} {packages}
-setenv = DJANGO_SETTINGS_MODULE=openstack_dashboard.test.settings
+setenv = DJANGO_SETTINGS_MODULE=zun_ui.test.settings
 commands = python setup.py build_sphinx
 
 [flake8]
diff --git a/zun_ui/api/zunclient.py b/zun_ui/api/client.py
similarity index 100%
rename from zun_ui/api/zunclient.py
rename to zun_ui/api/client.py
diff --git a/zun_ui/api/zun_rest_api.py b/zun_ui/api/rest_api.py
similarity index 88%
rename from zun_ui/api/zun_rest_api.py
rename to zun_ui/api/rest_api.py
index 0e787c5..87fe047 100644
--- a/zun_ui/api/zun_rest_api.py
+++ b/zun_ui/api/rest_api.py
@@ -12,7 +12,7 @@
 
 from django.views import generic
 
-from zun_ui.api import zunclient
+from zun_ui.api import client
 
 from openstack_dashboard.api.rest import urls
 from openstack_dashboard.api.rest import utils as rest_utils
@@ -36,7 +36,7 @@ class Container(generic.View):
     @rest_utils.ajax()
     def get(self, request, id):
         """Get a specific container"""
-        return change_to_id(zunclient.container_show(request, id).to_dict())
+        return change_to_id(client.container_show(request, id).to_dict())
 
 
 @urls.register
@@ -51,7 +51,7 @@ class Containers(generic.View):
         The returned result is an object with property 'items' and each
         item under this is a Container.
         """
-        result = zunclient.container_list(request)
+        result = client.container_list(request)
         return {'items': [change_to_id(n.to_dict()) for n in result]}
 
     @rest_utils.ajax(data_required=True)
@@ -61,7 +61,7 @@ class Containers(generic.View):
         Returns HTTP 204 (no content) on successful deletion.
         """
         for id in request.DATA:
-            zunclient.container_delete(request, id)
+            client.container_delete(request, id)
 
     @rest_utils.ajax(data_required=True)
     def post(self, request):
@@ -69,7 +69,7 @@ class Containers(generic.View):
 
         Returns the new Container object on success.
         """
-        new_container = zunclient.container_create(request, **request.DATA)
+        new_container = client.container_create(request, **request.DATA)
         return rest_utils.CreatedResponse(
             '/api/zun/container/%s' % new_container.uuid,
             new_container.to_dict())
diff --git a/zun_ui/content/container/__init__.py b/zun_ui/content/container/__init__.py
index e69de29..6957ce5 100644
--- a/zun_ui/content/container/__init__.py
+++ b/zun_ui/content/container/__init__.py
@@ -0,0 +1,14 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Register the REST API URLs so they can be called from the JavaScript files
+import zun_ui.api.rest_api  # noqa
diff --git a/zun_ui/content/container/containers/panel.py b/zun_ui/content/container/containers/panel.py
index 14fb57c..e6adfb0 100644
--- a/zun_ui/content/container/containers/panel.py
+++ b/zun_ui/content/container/containers/panel.py
@@ -15,7 +15,7 @@ import horizon
 
 # This panel will be loaded from horizon, because specified in enabled file.
 # To register REST api, import below here.
-from zun_ui.api import zun_rest_api  # noqa
+from zun_ui.api import rest_api  # noqa
 
 class Containers(horizon.Panel):
     name = _("Containers")
diff --git a/zun_ui/enabled/__init__.py b/zun_ui/enabled/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/zun_ui/test/settings.py b/zun_ui/test/settings.py
new file mode 100644
index 0000000..04013f5
--- /dev/null
+++ b/zun_ui/test/settings.py
@@ -0,0 +1,37 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+# Default to Horizons test settings to avoid any missing keys
+from horizon.test.settings import *  # noqa
+from openstack_dashboard.test.settings import *  # noqa
+
+# pop these keys to avoid log warnings about deprecation
+# update_dashboards will populate them anyway
+HORIZON_CONFIG.pop('dashboards', None)
+HORIZON_CONFIG.pop('default_dashboard', None)
+
+# Update the dashboards with zun_ui
+import zun_ui.enabled
+import openstack_dashboard.enabled
+from openstack_dashboard.utils import settings
+
+settings.update_dashboards(
+    [
+        zun_ui.enabled,
+        openstack_dashboard.enabled,
+    ],
+    HORIZON_CONFIG,
+    INSTALLED_APPS
+)
+
+# Ensure any duplicate apps are removed after the update_dashboards call
+INSTALLED_APPS = list(set(INSTALLED_APPS))