Repository: andrew-hossack/dash-tools (language: python)

Issue:
updated readme troubleshoot section



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c96d779..91434f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,21 @@
 
 All notable changes to this project will be documented in this file.
 
+## [1.5] - 2022-05-29
+
+### Added / Changed
+
+- Added `packages.txt` file to template root directory. Specify required packages on each new line, like a requirements.txt file, and user will be prompted to install if it is not found when they do `dashtools init <template>`
+- Cleaned up Docs directory and README (huge thanks to https://github.com/Coding-with-Adam)
+- Fixed `run` command to try different python shell commands
+- Added config file to store python shell settings
+- Added `run --set-python-shell-cmd` to set the python shell command in config
+- General prompting hint fixes and updates
+
+### Templates
+
+- Added leaflet template [Dash Leaflet](https://github.com/thedirtyfew/dash-leaflet)
+
 ## [1.4] - 2022-05-22
 
 **dash-tools command is now dashtools via command line**
@@ -30,4 +45,3 @@ Major changes are an overhaul to the CLI entry to use subcommands. This looks li
 
 - Added fast-dash template https://fastdash.app/
 - Added dash-iconify template https://github.com/snehilvj/dash-iconify
-
diff --git a/MANIFEST.in b/MANIFEST.in
index 321c9ac..3373550 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,7 @@
 global-include *.json
 global-include *.include
 global-include *.html
+global-include *.ini
 global-include *.css
 global-include *.csv
 global-include *.eot
@@ -17,4 +18,5 @@ global-include *.map
 global-include *.js
 global-include *.ts
 global-include *.template
+global-include packages.txt
 graft dash_tools/templating/templates
\ No newline at end of file
diff --git a/README.md b/README.md
index 68733b8..943cc0d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 <div align="center">
 
-![](docs/logo.png)
+![](docs/readme/logo.png)
 
 <h3>
 <b><i>Create, Run and Deploy Templated Plotly Dash Apps from Terminal</i></b>
@@ -16,7 +16,7 @@
 
 [**dashtools**](https://github.com/andrew-hossack/dash-tools) is an open-source toolchain for [Plotly Dash](https://dash.plotly.com/introduction). With a user-friendly command line interface, creating Dash applications and deploying them to [Heroku](https://heroku.com/) has never been quicker.
 
-Includes user and developer-friendly app templates where generating a new app only takes seconds. In fact, it will take longer to install this tool than it will to use it!
+Includes user and developer-friendly app templates where generating a new app only takes seconds. In fact, it will take longer to install this tool than it will to use it.
 
 ## **Installation**
 
@@ -30,9 +30,10 @@ pip install dash-tools
 
 #### Requires:
 
-- **Heroku CLI** which can be downloaded [Here](https://devcenter.heroku.com/articles/heroku-cli#install-the-heroku-cli)
 - **Git CLI** which can be downloaded [Here](https://git-scm.com/downloads)
-- **OS** - Linux, MacOS, Windows currently supported
+- **Heroku CLI** which can be downloaded [Here](https://devcenter.heroku.com/articles/heroku-cli#install-the-heroku-cli)
+- **OS** Linux, MacOS, Windows
+- **Python Version** ≥ 3.6
 
 ## **Usage Examples**
 
@@ -78,8 +79,16 @@ pip install dash-tools
 
 5. Run your app to ensure it works:
 
+   Linux and Mac OS
+
    ```bash
-   dashtools run
+   python src/app.py
+   ```
+
+   Windows
+
+   ```bash
+   python src\app.py
    ```
 
 #### Deploy App to Web with Heroku
@@ -90,7 +99,20 @@ pip install dash-tools
    dashtools heroku --deploy
    ```
 
-![deploy-app](./deploy-app.gif)
+   <details>
+     <summary>Alert!</summary>
+     After the `requirements.txt` file is created, compare the libraries your app is using with the libraries in the `requirements.txt` file. If your app uses additional libraries, make sure to add them to the requirements file before moving forward with the subsequent steps of deployment. 
+   </details>
+
+![deploy-app](docs/readme/deploy-app.gif)
+
+#### Pushing App Changes to Heroku (Optional)
+
+7. If you make changes to your app after deploying it, push them to Heroku using:
+
+   ```bash
+   dashtools heroku --update
+   ```
 
 ### B. Create an App with Local CSV sheet
 
@@ -105,8 +127,8 @@ A common use for Dash is to display CSV data that is located inside the project
 2. Replace the code in `app.py` with your own app's code, like shown in **example A** above. Make sure to keep code lines 13, 26, and 27.
 
    - 13: `server = app.server`
-   - 26: `PATH = pathlib.Path(__file__).parent`
-   - 27: `DATA_PATH = PATH.joinpath("data").resolve()`
+   - 23: `PATH = pathlib.Path(__file__).parent`
+   - 24: `DATA_PATH = PATH.joinpath("data").resolve()`
 
 3. Replace the default CSV file in the `data` folder with your own CSV file
 
@@ -118,8 +140,16 @@ A common use for Dash is to display CSV data that is located inside the project
 
 5. Run your app to ensure it works:
 
+   Linux and Mac OS
+
+   ```bash
+   python src/app.py
+   ```
+
+   Windows
+
    ```bash
-   dashtools run
+   python src\app.py
    ```
 
 #### Deploy App to Web with Heroku
@@ -130,140 +160,160 @@ A common use for Dash is to display CSV data that is located inside the project
    dashtools heroku --deploy
    ```
 
----
-
-## **Templates**
-
-Templates contain boilerplate code for projects, making it much easier to start with useful baseline apps.
+    <details>
+     <summary>Alert!</summary>
+     After the `requirements.txt` file is created, compare the libraries your app is using with the libraries in the `requirements.txt` file. If your app uses additional libraries, make sure to add them to the requirements file before moving forward with the subsequent steps of deployment. 
+   </details>
 
-### **Usage Examples**
+#### Pushing App Changes to Heroku (Optional)
 
-1. Create an app "MyWonderfulApp" with template "tabs"
+7. If you make changes to your app after deploying it, push them to Heroku using:
 
    ```bash
-   dashtools init MyWonderfulApp tabs
+   dashtools heroku --update
    ```
 
+### C. Additional Examples
+
+[Tutorial - Making Changes and Deploying to Heroku](docs/Configuring-Your-App.md)
+
+---
+
+## **Templates**
+
+Templates contain boilerplate code for projects, making it much easier to start with useful baseline apps. Example A shown above uses the "default" template, as no template argument was specified. Example B shown above uses the "csv" template.
+
+### Available Templates
+
 1. To list out available templates, use the `templates --list` command:
 
    ```bash
    dashtools templates --list
    ```
 
-### **Format**
+   _Click the dropdowns below to see screenshots._
 
-Templates may include different components, modules and layouts. They have the following format:
+   <details><summary>Template: 'advanced'</summary>
 
-```
-AppName
-│-- Procfile
-│-- README.md
-│-- runtime.txt
-│
-└── src
-    |-- app.py
-    |-- __init__.py
-    |-- assets/     [*]
-    |-- containers/ [*]
-    |-- components/ [*]
-    └-- data/       [*]
-```
+   _To use this template, type: `dashtools init MyFuturisticApp advanced`_
 
-**[*]** _Not included in all templates_
+   Advanced multi-page template. Includes examples of ClientsideCallbacks, multi-page routing, external stylesheets, header, footer, and 404 page.
+   ![](docs/templates/advanced_theme.png)
 
-### **Available Templates**
+   </details>
 
-_Click the dropdowns below to see screenshots._
+   <details><summary>Template: 'csv'</summary>
 
-<details><summary>Template: 'advanced'</summary>
+   _To use this template, type: `dashtools init MyCSVLoaderApp csv`_
 
-_To use this template, type: `dashtools init MyFuturisticApp advanced`_
+   Example of CSV file loading. Includes the default CSV load example from Plotly.
+   ![](docs/templates/csv_template.png)
 
-Advanced multi-page template. Includes examples of ClientsideCallbacks, multi-page routing, external stylesheets, header, footer, and 404 page.
-![](docs/advanced_theme.png)
+   </details>
 
-</details>
+   <details><summary>Template: 'default'</summary>
 
-<details><summary>Template: 'csv'</summary>
+   _To use this template, type: `dashtools init MyAmazingApp default`_
 
-_To use this template, type: `dashtools init MyCSVLoaderApp csv`_
+   Basic Dash template. See [Dash Docs](https://dash.plotly.com/layout)
+   ![](docs/templates/default_theme.png)
 
-Example of CSV file loading. Includes the default CSV load example from Plotly.
-![](docs/csv_template.png)
+   </details>
 
-</details>
+   <details><summary>Template: 'fastdash'</summary>
 
-<details><summary>Template: 'default'</summary>
+   _To use this template, type: `dashtools init StellarDashApp fastdash`_
 
-_To use this template, type: `dashtools init MyAmazingApp default`_
+   Fast Dash template. See [Fast Dash Docs](https://fastdash.app/)
+   ![](docs/templates/fastdash_template.png)
 
-Basic Dash template. See [Dash Docs](https://dash.plotly.com/layout)
-![](docs/default_theme.png)
+   </details>
 
-</details>
+   <details><summary>Template: 'iris'</summary>
 
-<details><summary>Template: 'fastdash'</summary>
+   _To use this template, type: `dashtools init MyFantasticApp iris`_
 
-_To use this template, type: `dashtools init StellarDashApp fastdash`_
+   Iris theme. See [Faculty.ai Example](https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/)
+   ![](docs/templates/iris_theme.png)
 
-Fast Dash template. See [Fast Dash Docs](https://fastdash.app/)
-![](docs/fastdash_template.png)
+   </details>
 
-</details>
+   <details><summary>Template: 'leaflet'</summary>
 
-<details><summary>Template: 'iconify'</summary>
+   _To use this template, type: `dashtools init BestMapApp leaflet`_
 
-_To use this template, type: `dashtools init DashingApp iconify`_
+   Dash leaflet plugin. See [Dash Leaflet](https://github.com/thedirtyfew/dash-leaflet)
+   ![](docs/templates/leaflet.png)
 
-Dash Iconify - a Dash component library which brings over 100,000 vector icons. See [Dash Iconify](https://github.com/snehilvj/dash-iconify)
-![](docs/iconify.png)
+   </details>
 
-</details>
+   <details><summary>Template: 'mantine'</summary>
 
-<details><summary>Template: 'iris'</summary>
+   _To use this template, type: `dashtools init MyGreatApp mantine`_
 
-_To use this template, type: `dashtools init MyFantasticApp iris`_
+   Basic mantine template. See [Dash Mantine](https://www.dash-mantine-components.com/)
+   ![](docs/templates/mantine_theme.png)
 
-Iris theme. See [Faculty.ai Example](https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/)
-![](docs/iris_theme.png)
+   </details>
 
-</details>
+   <details><summary>Template: 'multipage'</summary>
 
-<details><summary>Template: 'mantine'</summary>
+   _To use this template, type: `dashtools init MyPristineApp multipage`_
 
-_To use this template, type: `dashtools init MyGreatApp mantine`_
+   New multipage theme. See [Multipage Plugin](https://github.com/plotly/dash-labs/blob/main/docs/08-MultiPageDashApp.md)
+   ![](docs/templates/multipage_new_theme.png)
 
-Basic mantine template. See [Dash Mantine](https://www.dash-mantine-components.com/)
-![](docs/mantine_theme.png)
+   </details>
 
-</details>
+   <details><summary>Template: 'sidebar'</summary>
 
-<details><summary>Template: 'multipage'</summary>
+   _To use this template, type: `dashtools init MySnazzyApp sidebar`_
 
-_To use this template, type: `dashtools init MyPristineApp multipage`_
+   Sidebar theme. See [Faculty.ai Example](https://dash-bootstrap-components.opensource.faculty.ai/examples/simple-sidebar/)
+   ![](docs/templates/sidebar_theme.png)
 
-New multipage theme. See [Multipage Plugin](https://github.com/plotly/dash-labs/blob/main/docs/08-MultiPageDashApp.md)
-![](docs/multipage_new_theme.png)
+   </details>
 
-</details>
+   <details><summary>Template: 'tabs'</summary>
 
-<details><summary>Template: 'sidebar'</summary>
+   _To use this template, type: `dashtools init MyBeautifulApp tabs`_
 
-_To use this template, type: `dashtools init MySnazzyApp sidebar`_
+   Tabs theme with dynamically generated content. See [Faculty.ai Example](https://dash-bootstrap-components.opensource.faculty.ai/examples/graphs-in-tabs/)
+   ![](docs/templates/tabs_theme.png)
 
-Sidebar theme. See [Faculty.ai Example](https://dash-bootstrap-components.opensource.faculty.ai/examples/simple-sidebar/)
-![](docs/sidebar_theme.png)
+   </details>
 
-</details>
+2. To use a certain template, simply choose a directory name, followed by one of the template names listed above. Here, we will choose "tabs":
 
-<details><summary>Template: 'tabs'</summary>
+   ```bash
+   dashtools init MyWonderfulApp tabs
+   ```
 
-_To use this template, type: `dashtools init MyBeautifulApp tabs`_
+3. Follow the steps in Example A to replace default app with your own app and deploy to heroku.
 
-Tabs theme with dynamically generated content. See [Faculty.ai Example](https://dash-bootstrap-components.opensource.faculty.ai/examples/graphs-in-tabs/)
-![](docs/tabs_theme.png)
+### **Format**
 
-</details>
+Templates may include different components, modules and layouts. They have the following format:
+
+```
+AppName
+│-- Procfile
+│-- README.md
+│-- requirements.txt [1]
+│-- runtime.txt
+│
+└── src
+    |-- app.py
+    |-- __init__.py
+    |-- assets/      [2]
+    |-- containers/  [2]
+    |-- components/  [2]
+    └-- data/        [2]
+```
+
+**[1]** _Created during deployment_
+
+**[2]** _Not included in all templates_
 
 ## **Commands**
 
@@ -279,6 +329,7 @@ Tabs theme with dynamically generated content. See [Faculty.ai Example](https://
 - **`init <app name> [template (OPTIONAL)]`** Create a new app
   - **`--dir, -d`** Specify alternative create location
 - **`run`** Run app locally from the current directory
+  - **`--set-python-shell-cmd`** Specify the python shell command, e.g. python3 or python3.exe, etc.
 - **`templates`** List and create templates
   - **`--init <directory to convert>`** Creates a template from specified directory
   - **`--list`** List available templates
@@ -290,27 +341,73 @@ Tabs theme with dynamically generated content. See [Faculty.ai Example](https://
 
 ## **Troubleshooting**
 
-Running into issues? Outlined below are common errors and solutions. If you do not find an answer below, please [Submit an Issue Ticket](https://github.com/andrew-hossack/dash-tools/issues).
+Running into issues? Outlined below are common errors and solutions. If you do not find an answer below, please [Submit an Issue Ticket](https://github.com/andrew-hossack/dash-tools/issues/new/choose).
 
-<details><summary>Common init Issues</summary>
+<details><summary>Common heroku --deploy Issues</summary>
 
-<details><summary>No write permission</summary>
+<details><summary>&ensp;&ensp;&ensp;&ensp;Error when creating requirements.txt file</summary>
 
-_Problem:_ You receive a 'write permission' error while trying to init a new app
+_Problem:_ You encounter an error when generating a requirements.txt file
 
-_Solution:_ Please check your write permissions for the current directory. Try the init command from a different directory.
+_Solution:_ Verify that you are running the `dashtools heroku --deploy` command from a valid plotly dash app directory. E.g. there is a `src/app.py` file.
 
 </details>
 
+<details><summary>&ensp;&ensp;&ensp;&ensp;No webpage displayed after Heroku deployment, no error messages</summary>
+
+_Problem:_ You are able to deploy your project online to Heroku, but nothing is displayed on the page
+
+_Solution1:_ This may be due to missing libraries that your app needs to run successfully. Check the requirements.txt. file.
+
+_Solution2:_ This may due to the fact that you forgot to add `server = app.server` to you main app.py file
+
 </details>
 
-<details><summary>Common heroku --deploy Issues</summary>
+<details><summary>&ensp;&ensp;&ensp;&ensp;Procfile is incorrect</summary>
 
-<details><summary>Error when creating requirements.txt file</summary>
+_Problem:_ When deploying, you get an error "Procfile is incorrect"
 
-_Problem:_ You encounter an error when generating a requirements.txt file
+_Solution:_ When deploying an app, the Procfile is checked for validity. Make sure that your Procfile points to the correct server entrypoint, e.g. `server = app.server`.
 
-_Solution:_ Verify that you are running the `dashtools heroku --deploy` command from a valid plotly dash app directory. E.g. there is a `src/app.py` file.
+</details>
+   
+<details><summary>&ensp;&ensp;&ensp;&ensp;No solution found</summary>
+
+_Solution:_ Try typing into the terminal or command prompt `heroku logs --tail`. This will give you access to the official heroku logs of your app that enable monitoring your stack error troubleshooting
+
+</details>
+
+</details>
+
+<details><summary>Common heroku --update Issues</summary>
+   
+<details><summary>&ensp;&ensp;&ensp;&ensp; Your account has reached its concurrent build limit.</summary>
+
+_Problem:_ When you try to update and redeploy your app to Heroku too many times within 10 minutes, you will get the above error message
+
+_Solution:_ First, wait a few minutes and try again. If that doesn't work, check out a few possible solutions [in this thread](https://stackoverflow.com/questions/47028871/heroku-your-account-has-reached-its-concurrent-build-limit).
+
+</details>
+
+<details><summary>&ensp;&ensp;&ensp;&ensp;Unable to update heroku app</summary>
+
+_Problem:_ When you try to update your app, you get an error "Unable to update heroku app. Is the project already deployed?"
+
+_Solution:_ Make sure you have already run `git init` in the project root directory, and that you have already followed steps to deploy your project to heroku with `dashtools heroku --deploy`
+
+If both of these steps do not work, verify that the `heroku` remote was added by running `git remote`. If you do not see it, try re-deploying your app or manually push to the correct remote with the `dashtools heroku --update <remote>` option, where `<remote>` is replaced with the correct remote.
+
+</details>
+
+</details>
+
+<details><summary>Common init Issues</summary>
+
+<details><summary>&ensp;&ensp;&ensp;&ensp;No write permission</summary>
+
+_Problem:_ You receive a 'write permission' error while trying to init a new app
+
+_Solution:_ Please check your write permissions for the current directory. Try the init command from a different directory.
 
 </details>
 
@@ -318,7 +415,15 @@ _Solution:_ Verify that you are running the `dashtools heroku --deploy` command
 
 <details><summary>Common run Issues</summary>
 
-<details><summary>No such file or directory</summary>
+<details><summary>&ensp;&ensp;&ensp;&ensp;No valid python command found for your system</summary>
+
+_Problem:_ You encounter an error: No valid python command found for your system when trying to run your app
+
+_Solution:_ Set the python shell command with "dashtools run --set-python-shell-cmd <command>". The correct command will be the python command that runs python, eg. python, python.exe, python3, python3.exe on your system. Note that although you may be able to run 'python' from your terminal, this may be an alias command for your terminal, and not the correct command.
+
+</details>
+
+<details><summary>&ensp;&ensp;&ensp;&ensp;No such file or directory</summary>
 
 _Problem:_ You encounter an error: 'No such file or directory' when trying to `dashtools run` your app
 
@@ -326,6 +431,14 @@ _Solution:_ Verify that you are running the `dashtools run` command from within
 
 </details>
 
+<details><summary>&ensp;&ensp;&ensp;&ensp;Invalid Procfile</summary>
+
+_Problem:_ When you try to run, you get an error "Invalid Procfile"
+
+_Solution:_ When you run an app, the Procfile is checked for validity. Make sure that your Procfile points to the correct server entrypoint, e.g. `server = app.server`.
+
+</details>
+
 </details>
 
 ## **Development**
diff --git a/config.ini b/config.ini
new file mode 100644
index 0000000..74ce85f
--- /dev/null
+++ b/config.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+python_shell_cmd = python3
+
diff --git a/dashtools/cli/cli.py b/dashtools/cli/cli.py
index ae95f75..a4b85d0 100644
--- a/dashtools/cli/cli.py
+++ b/dashtools/cli/cli.py
@@ -7,8 +7,8 @@
 import os
 import sys as _sys
 from dashtools.deploy import deployHeroku
-from dashtools.runtime import runtimeUtils
 from dashtools.templating import buildApp, buildAppUtils, createTemplate
+from dashtools.runtime import runtimeUtils
 from dashtools.version import __version__
 
 
@@ -29,8 +29,9 @@ def print_help(self, file=None):
     {'init <app name> [template]':<29}Create a new app
         {'--dir, -d':<25}Specify alternative create location
     
-    {'run':<29}Run app locally from the current directory
-    
+    {'run':<29}Run the app (experimental)
+        {'--set-python-shell-cmd':<25}Set the python shell command
+
     {'templates':<29}List and create templates
         {'--init <directory>':<25}Creates a template from specified directory
         {'--list':<25}List available templates
@@ -109,7 +110,7 @@ def init(args):
         app_name=args.init[0],
         template=buildAppUtils.get_template_from_args(args))
     print(
-        f'dashtools: Run your app using the "cd {args.init[0]} && dashtools run" command')
+        f'dashtools: Run your app using the "python {os.path.join(args.init[0], "src", "app.py")}" command')
     print(f'dashtools: For an in-depth guide on configuring your app, see https://dash.plotly.com/layout')
 
 
@@ -158,7 +159,7 @@ def heroku(args):
     if args.deploy:
         deployHeroku.deploy_app_to_heroku(os.getcwd())
     elif args.update:
-        deployHeroku.update_heroku_app(args.update)
+        deployHeroku.update_heroku_app(os.getcwd(), remote=args.update)
     else:
         print('dashtools: heroku error: too few arguments')
         exit('dashtools: Available heroku options: --deploy, --update')
@@ -171,9 +172,16 @@ def heroku(args):
             help="Run the app locally. Uses Procfile if available, else recursive search for app.py",
             default=False,
             action="store_true"),
+        argument(
+            '--set-python-shell-cmd',
+            help='Set the python shell command. Args: REQUIRED: <python shell command>',
+            metavar='<python shell command>',
+            nargs=1)
     ])
 def run(args):
-    if args.run:
+    if args.set_python_shell_cmd:
+        runtimeUtils.set_python_shell_cmd(args.set_python_shell_cmd[0])
+    elif args.run:
         try:
             runtimeUtils.run_app(os.getcwd())
         except RuntimeError as e:
diff --git a/dashtools/data/configUtils.py b/dashtools/data/configUtils.py
new file mode 100644
index 0000000..85e56fd
--- /dev/null
+++ b/dashtools/data/configUtils.py
@@ -0,0 +1,40 @@
+'''
+ # @ Author: Andrew Hossack
+ # @ Create Time: 2022-05-28 13:56:14
+'''
+
+import configparser
+from typing import Union
+
+
+def _get_config() -> str:
+    """
+    Get the config file
+    """
+    config = configparser.ConfigParser()
+    config.read('config.ini')
+    return config
+
+
+def get_config_value(key: str, section: str = "DEFAULT") -> Union[str, None]:
+    """
+    Get the value of a key in a section or None if not found
+    """
+    config = _get_config()
+    try:
+        value = config[section][key]
+        if len(value) > 0:
+            return value
+        return None
+    except KeyError:
+        return None
+
+
+def set_config_value(key: str, value: str, section: str = "DEFAULT") -> None:
+    """
+    Set the value of a key in a section
+    """
+    config = _get_config()
+    config.set(section, key, value)
+    with open('config.ini', 'w') as configfile:
+        config.write(configfile)
diff --git a/dashtools/deploy/deployHeroku.py b/dashtools/deploy/deployHeroku.py
index d36ad75..11ba680 100644
--- a/dashtools/deploy/deployHeroku.py
+++ b/dashtools/deploy/deployHeroku.py
@@ -31,11 +31,9 @@ def _check_required_files_exist(root_path: os.PathLike) -> bool:
             deploy_should_continue = False
     # Check for the Requirements file
     if (not fileUtils.check_file_exists(root_path, 'requirements.txt')) and deploy_should_continue:
-        if prompt_user_choice(
-                'dashtools: Required file requirements.txt not found. Create one automatically?'):
-            fileUtils.create_requirements_txt(root_path)
-        else:
-            deploy_should_continue = False
+        fileUtils.create_requirements_txt(root_path, update=False)
+    else:
+        fileUtils.create_requirements_txt(root_path, update=True)
     return deploy_should_continue
 
 
@@ -73,15 +71,14 @@ def _add_changes_and_push_to_heroku(heroku_app_name: str, remote: str = 'heroku'
         False (Failure)
     """
     # Create a commit to push to Heroku
-    print(f'dashtools: Creating commit to push to {remote}')
-    os.system(f'git add .')
-    os.system(
-        f'git commit -m "Deploy to Heroku for app {heroku_app_name} - dashtools"')
-    # Push to Heroku
-    print(f'dashtools: Pushing to Heroku')
     try:
+        subprocess.check_output(f'git add .', shell=True)
+        print(f'dashtools: Created commit to push to {remote}')
         subprocess.check_output(
-            f'git push {remote} HEAD:master', shell=True)
+            f'git commit -m "Deploy to Heroku for app {heroku_app_name} - dashtools"', shell=True)
+        # Push to Heroku
+        print(f'dashtools: Pushing to Heroku')
+        subprocess.check_output(f'git push {remote} HEAD:master', shell=True)
     except subprocess.CalledProcessError:
         return False
     return True
@@ -142,16 +139,20 @@ def _success_message(heroku_app_name: str):
     print('dashtools: heroku: deploy: Finished')
 
 
-def update_heroku_app(remote: str = 'heroku'):
+def update_heroku_app(project_root: os.PathLike, remote: str = 'heroku'):
     """
     Updates the existing heroku app
 
     Args:
         remote(str): Remote to update. Default 'heroku'
     """
+    # (Re)generate requirements file
+    if fileUtils.check_file_exists(project_root, 'requirements.txt'):
+        fileUtils.create_requirements_txt(project_root, update=True)
     if not _add_changes_and_push_to_heroku('update', remote=remote):
-        exit('dashtools: heroku: deploy: Failed to push to heroku')
-    print('dashtools: Changes pushed to heroku remote')
+        print(f'dashtools: Unable to update heroku app. Is the project already deployed?')
+        exit('dashtools: heroku: update: Failed')
+    print('dashtools: Changes pushed to {remote} remote')
     exit('dashtools: heroku: update: Success')
 
 
@@ -208,7 +209,7 @@ def deploy_app_to_heroku(project_root_dir: os.PathLike):
 
     # Check that git is initialized in the current repo
     if not gitUtils.is_git_repository():
-        print(f'dashtools: Current directory is not a git repository!')
+        print(f'dashtools: A git repository is needed for deployment. Current directory is not a git repository!')
         # Prompt user to init git
         if prompt_user_choice('dashtools: Would you like to init git?'):
             os.system('git init')
@@ -226,7 +227,7 @@ def deploy_app_to_heroku(project_root_dir: os.PathLike):
         while True:
             response = input('dashtools: Choice (1, 2, 3) > ')
             if response == '1':
-                update_heroku_app()
+                update_heroku_app(project_root_dir)
             elif response == '2':
                 _remove_heroku_remote()
                 break
@@ -240,15 +241,17 @@ def deploy_app_to_heroku(project_root_dir: os.PathLike):
 
     # Check that the project has necessary files
     if not _check_required_files_exist(project_root_dir):
-        print('dashtools: Procfile, runtime.txt, and requirements.txt are needed for Heroku deployment.')
+        print('dashtools: Procfile, runtime.txt are needed for Heroku deployment.')
         exit('dashtools: heroku: deploy: Aborted')
 
     # Check procfile is correct
     procfile = fileUtils.verify_procfile(project_root_dir)
     if not procfile['valid']:
+        print(f'dashtools: Invalid Procfile!')
         print(
-            f'dashtools: Procfile is incorrect. Did you include "{procfile["hook"]} = app.server" after instantiating "app = Dash(...)" in {procfile["dir"]}/{procfile["module"]}?')
-        exit('dashtools: heroku: deploy: Failed')
+            f'dashtools: Did you include "{procfile["hook"]} = app.server" after instantiating "app = Dash(...)" in {procfile["dir"]}/{procfile["module"]}?')
+        if not prompt_user_choice('dashtools: App will not start without a valid Procfile. Continuing is not recommended.'):
+            exit('dashtools: heroku: deploy: Aborted')
 
     # Log into Heroku
     if not herokuUtils.login_heroku_successful():
diff --git a/dashtools/deploy/fileUtils.py b/dashtools/deploy/fileUtils.py
index 657914a..96eaea0 100644
--- a/dashtools/deploy/fileUtils.py
+++ b/dashtools/deploy/fileUtils.py
@@ -18,23 +18,39 @@ def check_file_exists(root_path: os.PathLike, file_name: str) -> bool:
     return True
 
 
-def create_requirements_txt(root_path: os.PathLike):
+def _add_requirement(root_path: os.PathLike, requirement: str):
+    """
+    Adds a requirement to the requirements.txt file if it doesn't already exist
+    """
+    # Append gunicorn to requirements.txt
+    with open(os.path.join(root_path, 'requirements.txt'), 'r+') as requirements_file:
+        if requirement not in requirements_file.read():
+            requirements_file.write(f'{requirement}\n')
+
+
+def create_requirements_txt(root_path: os.PathLike, update=False):
     """
     Creates requirements.txt file using pipreqs
     """
-    print('dashtools: deploy-heroku: Creating requirements.txt')
+    print(
+        f'dashtools: {"Updating" if update else "Creating"} requirements.txt')
     try:
-        subprocess.check_output(f'pipreqs {root_path}', shell=True)
+        if update:
+            subprocess.check_output(
+                f'pipreqs --force {root_path}', shell=True)
+        else:
+            subprocess.check_output(
+                f'pipreqs {root_path}', shell=True)
     except subprocess.CalledProcessError:
         # pipreqs throws a SyntaxError if it encounters a non-ASCII character
         # One reason may be that the user is not in a valid dash app directory
-        print(
-            f'dashtools: deploy-heroku: Error creating requirements.txt')
-        print('dashtools: deploy-heroku: Did you run --deploy-heroku in a valid dash app directory?')
-        exit('dashtools: deploy-heroku: Failed')
-    # Append gunicorn to requirements.txt
-    with open(os.path.join(root_path, 'requirements.txt'), 'a') as requirements_file:
-        requirements_file.write('gunicorn')
+        print('dashtools: Error creating requirements.txt')
+        print('dashtools: Did you run heroku --deploy in a valid dash app directory?')
+        exit('dashtools: heroku: deploy: Failed')
+
+    # Add requirements that might not be in requirements.txt
+    for req in ['gunicorn', 'pandas']:
+        _add_requirement(root_path, req)
 
 
 def create_runtime_txt(root_path: os.PathLike):
@@ -44,7 +60,7 @@ def create_runtime_txt(root_path: os.PathLike):
     """
     with open(os.path.join(root_path, 'runtime.txt'), 'w') as runtime_file:
         runtime_file.write('python-3.8.10')
-    print('dashtools: deploy-heroku: Created runtime.txt using python-3.8.10')
+    print('dashtools: Created runtime.txt using python-3.8.10')
 
 
 def create_procfile(root_path: os.PathLike):
@@ -54,7 +70,7 @@ def create_procfile(root_path: os.PathLike):
     with open(os.path.join(root_path, 'Procfile'), 'w') as procfile:
         procfile.write(
             f'web: gunicorn --timeout 600 --chdir src app:server')
-    print(f'dashtools: deploy-heroku: Created Procfile')
+    print(f'dashtools: Created Procfile')
 
 
 def verify_procfile(root_path: os.PathLike) -> dict:
@@ -76,7 +92,7 @@ def verify_procfile(root_path: os.PathLike) -> dict:
         procfile_contents = procfile.read()
 
     # Look for --chdir somedir
-    chdir_regex = r"--chdir [a-zA-Z]+"
+    chdir_regex = r"--chdir [a-zA-Z\/\\]+"
     try:
         chdir = re.search(chdir_regex, procfile_contents).group(0)
         chdir = chdir.replace('--chdir ', '')
diff --git a/dashtools/deploy/herokuUtils.py b/dashtools/deploy/herokuUtils.py
index b1c3c01..af18e47 100644
--- a/dashtools/deploy/herokuUtils.py
+++ b/dashtools/deploy/herokuUtils.py
@@ -32,7 +32,7 @@ def login_heroku_successful() -> bool:
     """
     Try to log into Heroku
     """
-    print(f'dashtools: deploy-heroku: Logging into Heroku...')
+    print(f'dashtools: Logging into Heroku...')
     regex = r'Error: quit'
     loginWasSuccessful = True
     try:
@@ -57,7 +57,7 @@ def check_heroku_app_name_available(heroku_app_name: str) -> bool:
         if requests.get(f'https://{heroku_app_name}.herokuapp.com/', headers={'User-Agent': 'Custom'}).status_code == 404:
             return True
     except requests.exceptions.ConnectionError:
-        exit('dashtools: deploy-heroku: Invalid Heroku app name')
+        exit('dashtools: heroku: deploy: Invalid Heroku app name')
     return False
 
 
@@ -68,7 +68,7 @@ def create_app_on_heroku(app_name: str) -> bool:
     Returns:
         Success of the operation (True/False)
     """
-    print(f'dashtools: deploy-heroku: Creating {app_name} on Heroku')
+    print(f'dashtools: Creating {app_name} on Heroku')
     url_regex = r'https:\/\/git\.heroku\.com\/[a-zA-Z0-9-_]*\.git'
     try:
         heroku_command_output = subprocess.check_output(
@@ -89,14 +89,14 @@ def get_heroku_app_name():
     """
     Create or generate app name if one isn't provided
     """
-    print('dashtools: deploy-heroku: Please type a unique app name or press enter to generate one automatically.')
+    print('dashtools: Please type a unique app name or press enter to generate one automatically.')
     app_name = input('dashtools: App Name (Optional) > ')
     if app_name == '':
         # Generate a random app name with three words
         app_name = '-'.join(randomWords.get_words(3))
         # Append alphanumeric characters to the end of the app name
         app_name += f"-{''.join(random.choices(string.ascii_lowercase + string.digits, k=4))}"
-    print(f'dashtools: deploy-heroku: Using app name "{app_name}"')
+    print(f'dashtools: Using app name "{app_name}"')
     return app_name
 
 
diff --git a/dashtools/runtime/runtimeUtils.py b/dashtools/runtime/runtimeUtils.py
index 0f7ba71..070177c 100644
--- a/dashtools/runtime/runtimeUtils.py
+++ b/dashtools/runtime/runtimeUtils.py
@@ -6,41 +6,107 @@
 
 
 import os
+import subprocess
+from typing import Union
 from dashtools.deploy.fileUtils import verify_procfile, check_file_exists
+from dashtools.data import configUtils
+
+
+def _is_correct_python_command(command: str) -> bool:
+    """
+    Try running the python command
+    Return True if it works, else False
+    """
+    try:
+        subprocess.check_output(
+            f'{command} --version',
+            shell=True,
+            stderr=subprocess.DEVNULL)
+        return True
+    except subprocess.CalledProcessError:
+        return False
+
+
+def _try_all_commands() -> Union[str, None]:
+    """
+    Try all python commands
+    Return the first one that works or None if none work
+    """
+    command = None
+    for cmd in ['python', 'python3', 'python.exe', 'python3.exe']:
+        if _is_correct_python_command(cmd):
+            command = cmd
+            break
+    return command
+
+
+def set_python_shell_cmd(command: str):
+    """
+    Set the python shell command
+    """
+    if _is_correct_python_command(command):
+        configUtils.set_config_value('python_shell_cmd', command)
+        print(
+            f'dashtools: run: success: Python shell command set to {command}')
+    else:
+        print(
+            f'dashtools: run: error: Command {command} is not valid for running Python')
+    return
+
+
+def _python_shell_cmd() -> str:
+    """
+    Get the python shell command
+    """
+    command = configUtils.get_config_value('python_shell_cmd')
+    if not command:
+        command = _try_all_commands()
+        if command:
+            configUtils.set_config_value('python_shell_cmd', command)
+        else:
+            print('dashtools: run: error: None of the following commands were found on your system: python, python.exe, python3, python3.exe')
+            print('dashtools: run: Please set the python shell command with "dashtools run --set-python-shell-cmd <command>", or fall back to running your app from the app.py file')
+            exit('dashtools: View Troubleshooting docs for more info')
+    return command
+
+
+def _run_from_app(root_path: os.PathLike):
+    """
+    Try looking for an app.py file in the directory recursively
+    """
+    # Find app.py file in the root_path directory
+    for root, _, files in os.walk(root_path):
+        if 'app.py' in files:
+            break
+    try:
+        print(
+            f'dashtools: Running From {root + "/" if root else ""}app.py')
+        os.chdir(root)
+        # Has to run as os.system() to get the output
+        # This means that if no app file is found, an error will be printed to screen
+        # And cannot be handled in a cleaner way
+        os.system(f'{_python_shell_cmd()} app.py')
+    except Exception:
+        exit('dashtools: run: No app.py file found')
 
 
 def run_app(root_path: os.PathLike):
     '''
     Look for a Procfile to run the app, else recursive search for app.py file
-    # TODO verify this works for all systems. Not sure if python3 is correct cmd
     '''
     # Check Procfile exists
     if check_file_exists(root_path, 'Procfile'):
-        # Look for a Procfile to run the app, else recursive search for app.py file
         proc = verify_procfile(root_path)
         if proc['valid']:
-            try:
-                print('dashtools: Running From Procfile')
-                os.chdir(root_path)
-                # NOTE Not too sure if python3 is the right command for all systems, it might need to be changed
-                os.system(
-                    f'python3 -m {proc["dir"]}.{proc["module"].replace(".py","")}')
-            except Exception as e:
-                print(e)
+            print('dashtools: Running From Procfile')
+            os.chdir(root_path)
+            modpath = proc["dir"].replace("/", ".").replace("\\", ".")
+            modname = proc["module"].replace(".py", "")
+            os.system(f'{_python_shell_cmd()} -m {modpath}.{modname}')
+        else:
+            # Invalid Procfile, try running app.py
+            _run_from_app(root_path)
     else:
-        # Find app.py file in the root_path directory
-        for root, dirs, files in os.walk(root_path):
-            if 'app.py' in files:
-                break
-        try:
-            print(
-                f'dashtools: Running From {root + "/" if root else ""}app.py')
-            os.chdir(root)
-            # NOTE Not too sure if python3 is the right command for all systems, it might need to be changed
-            os.system(f'python3 app.py')
-            # Has to run as os.system() to get the output
-            # This means that if no app file is found, an error will be printed to screen
-            # And cannot be handled in a cleaner way
-        except Exception:
-            exit('dashtools: run: No app.py file found')
+        # No Procfile, try running app.py
+        _run_from_app(root_path)
     return
diff --git a/dashtools/templating/Templates.py b/dashtools/templating/Templates.py
index b642ce3..1bcdb86 100644
--- a/dashtools/templating/Templates.py
+++ b/dashtools/templating/Templates.py
@@ -20,7 +20,7 @@ class Template(Enum):
     CSV = 'csv'
     DEFAULT = 'default'
     FASTDASH = 'fastdash'
-    ICONIFY = 'iconify'
+    LEAFLET = 'leaflet'
     IRIS = 'iris'
     MANTINE = 'mantine'
     MULTIPAGE = 'multipage'
diff --git a/dashtools/templating/buildApp.py b/dashtools/templating/buildApp.py
index 6f03c17..85ee0d1 100644
--- a/dashtools/templating/buildApp.py
+++ b/dashtools/templating/buildApp.py
@@ -8,7 +8,7 @@
 import os
 import shutil
 
-from dashtools.templating import buildAppUtils
+from dashtools.templating import buildAppUtils, pipUtils
 
 
 def _format_file(name: os.PathLike, app_name: str, dest: os.PathLike):
@@ -30,6 +30,14 @@ def _format_file(name: os.PathLike, app_name: str, dest: os.PathLike):
                 f.write(content)
 
 
+def _get_template_path(template_value: str) -> str:
+    """
+    Get path to template directory
+    """
+    template_dir = os.path.join('templates', template_value)
+    return buildAppUtils.get_templates_data_path(template_dir)
+
+
 def create_app(target_dir: os.PathLike, app_name: str, template: buildAppUtils.Template):
     '''
     Create a new app in the target directory.
@@ -46,8 +54,7 @@ def create_app(target_dir: os.PathLike, app_name: str, template: buildAppUtils.T
         exit(f'dashtools: init: Failed')
 
     # Copy files from template directory
-    template_dir = os.path.join('templates', template.value)
-    template_base_path = buildAppUtils.get_templates_data_path(template_dir)
+    template_base_path = _get_template_path(template.value)
     for path, _, files in os.walk(template_base_path):
         for name in files:
             # Skip non .template files
@@ -75,5 +82,7 @@ def create_app(target_dir: os.PathLike, app_name: str, template: buildAppUtils.T
             # Format the file
             _format_file(name, app_name, dest)
 
+    # Handle pip requirements
+    pipUtils.handle_template_requirements(template.value)
     print(
         f'dashtools: init: Created new app {app_name} at {os.path.join(target_dir, app_name)} with {template.name} template')
diff --git a/dashtools/templating/createTemplate.py b/dashtools/templating/createTemplate.py
index c50f160..183c17e 100644
--- a/dashtools/templating/createTemplate.py
+++ b/dashtools/templating/createTemplate.py
@@ -50,7 +50,7 @@ def create_template(src: os.PathLike, dest: os.PathLike):
             exit(f'dashtools: templates: init: Aborted')
 
     # Check runtime exists
-    if not fileUtils.check_file_exists(src, 'runtime.py'):
+    if not fileUtils.check_file_exists(src, 'runtime.txt'):
         print(
             f'dashtools: templates: init: No runtime.py found in {src}')
         if deployHeroku.prompt_user_choice("dashtools: templates: init: Create runtime.py?"):
@@ -84,3 +84,5 @@ def create_template(src: os.PathLike, dest: os.PathLike):
 
             # Copy the file
             shutil.copyfile(rel_src, dest)
+    print(
+        f'dashtools: templates: init: Created template {template_base_dir}')
diff --git a/dashtools/templating/pipUtils.py b/dashtools/templating/pipUtils.py
new file mode 100644
index 0000000..4dbb46c
--- /dev/null
+++ b/dashtools/templating/pipUtils.py
@@ -0,0 +1,64 @@
+'''
+ # @ Author: Andrew Hossack
+ # @ Create Time: 2022-05-24 17:46:41
+'''
+
+from dashtools.templating import buildApp
+from dashtools.deploy import deployHeroku
+import os
+import pkg_resources
+
+
+def _install_pip_requirement(requirement: str):
+    '''
+    Install a pip requirement
+    '''
+    print(f'dashtools: Installing {requirement}')
+    os.system(f'pip install {requirement}')
+
+
+def _check_pip_requirement_installed(requirement: str) -> bool:
+    '''
+    Check if a pip requirement is installed
+    '''
+    try:
+        pkg_resources.get_distribution(requirement)
+    except pkg_resources.DistributionNotFound:
+        return False
+    except pkg_resources.VersionConflict:
+        return False
+    return True
+
+
+def _check_pip_requirements(requirements: list, template_value: str):
+    '''
+    Check if pip requirements are installed
+    '''
+    for req in requirements:
+        if not _check_pip_requirement_installed(req):
+            print(
+                f'dashtools: Template {template_value} requires pip module {req}, which is not installed')
+            if deployHeroku.prompt_user_choice(f'dashtools: Install {req}?'):
+                _install_pip_requirement(req)
+                print()
+
+
+def _get_template_required_packages(template_value: str) -> list:
+    """
+    Looks for 'packages' file in template directory. If one is found,
+    return a list of pip requirements.
+    """
+    packages_file = os.path.join(
+        buildApp._get_template_path(template_value), 'packages.txt')
+    if os.path.exists(packages_file):
+        with open(packages_file, 'r') as f:
+            return f.read().splitlines()
+    return []
+
+
+def handle_template_requirements(template_value: str):
+    """
+    Handle pip requirements for template
+    """
+    _check_pip_requirements(_get_template_required_packages(
+        template_value), template_value)
diff --git a/dashtools/templating/templates/csv/src/app.py.template b/dashtools/templating/templates/csv/src/app.py.template
index 01f7700..3b047e7 100644
--- a/dashtools/templating/templates/csv/src/app.py.template
+++ b/dashtools/templating/templates/csv/src/app.py.template
@@ -19,14 +19,10 @@ app.layout = html.Div([
     dcc.Graph(id="graph"),
 ])
 
-# Declare server for Heroku deplpoyment. Needed for Procfile.
-server = app.server
-
 # get relative data folder
 PATH = pathlib.Path(__file__).parent
 DATA_PATH = PATH.joinpath("data").resolve()
 
-
 @app.callback(
     Output("graph", "figure"),
     Input("button", "n_clicks"))
diff --git a/dashtools/templating/templates/fastdash/packages.txt b/dashtools/templating/templates/fastdash/packages.txt
new file mode 100644
index 0000000..64398fc
--- /dev/null
+++ b/dashtools/templating/templates/fastdash/packages.txt
@@ -0,0 +1,1 @@
+fast-dash
diff --git a/dashtools/templating/templates/iconify/README.md.template b/dashtools/templating/templates/iconify/README.md.template
deleted file mode 100644
index 82e270f..0000000
--- a/dashtools/templating/templates/iconify/README.md.template
+++ /dev/null
@@ -1,13 +0,0 @@
-# Dash Iconify Template: {appName}
-
-Created on {createTime}
-
-Welcome to your [Plotly Dash](https://plotly.com/dash/) App! This is a template for your {appName} app.
-
-See the [Dash Documentation](https://dash.plotly.com/introduction) for more information on how to get your app up and running.
-
-Check out [Dash Iconify](https://github.com/snehilvj/dash-iconify) for more details on this template.
-
-## Running the App
-
-Run `src/app.py` and navigate to http://127.0.0.1:8050/ in your browser.
\ No newline at end of file
diff --git a/dashtools/templating/templates/iconify/src/__init__.py.template b/dashtools/templating/templates/iconify/src/__init__.py.template
deleted file mode 100644
index 0487626..0000000
--- a/dashtools/templating/templates/iconify/src/__init__.py.template
+++ /dev/null
@@ -1,1 +0,0 @@
-# Auto-generated at {createTime}
\ No newline at end of file
diff --git a/dashtools/templating/templates/iconify/src/app.py.template b/dashtools/templating/templates/iconify/src/app.py.template
deleted file mode 100644
index 91c6cd3..0000000
--- a/dashtools/templating/templates/iconify/src/app.py.template
+++ /dev/null
@@ -1,23 +0,0 @@
-'''
- # @ Create Time: {createTime}
-'''
-
-from dash_iconify import DashIconify
-from dash import Dash
-
-app = Dash(__name__, title="{appName}")
-
-# Declare server for Heroku deployment. Needed for Procfile.
-server = app.server
-
-app.layout = DashIconify(
-    icon="ion:logo-github",
-    width=30,
-    height=30,
-    rotate=1,
-    flip="horizontal",
-)
-
-if __name__ == "__main__":
-    app.run_server(debug=True)
-
diff --git a/dashtools/templating/templates/iconify/Procfile.template b/dashtools/templating/templates/leaflet/Procfile.template
similarity index 100%
rename from dashtools/templating/templates/iconify/Procfile.template
rename to dashtools/templating/templates/leaflet/Procfile.template
diff --git a/dashtools/templating/templates/leaflet/README.md.template b/dashtools/templating/templates/leaflet/README.md.template
new file mode 100644
index 0000000..2a6c7a1
--- /dev/null
+++ b/dashtools/templating/templates/leaflet/README.md.template
@@ -0,0 +1,11 @@
+# Dash Leaflet Template: {appName}
+
+Created on {createTime}
+
+Welcome to your [Plotly Dash](https://plotly.com/dash/) App! This is a template for your app.
+
+See the [Dash Leaflet](https://github.com/thedirtyfew/dash-leaflet) for more information on how to get your app up and running.
+
+## Running the App
+
+Run `src/app.py` and navigate to http://127.0.0.1:8050/ in your browser.
diff --git a/dashtools/templating/templates/iconify/runtime.txt.template b/dashtools/templating/templates/leaflet/runtime.txt.template
similarity index 100%
rename from dashtools/templating/templates/iconify/runtime.txt.template
rename to dashtools/templating/templates/leaflet/runtime.txt.template
diff --git a/dashtools/templating/templates/leaflet/src/__init__.py.template b/dashtools/templating/templates/leaflet/src/__init__.py.template
new file mode 100644
index 0000000..fa6a762
--- /dev/null
+++ b/dashtools/templating/templates/leaflet/src/__init__.py.template
@@ -0,0 +1,1 @@
+# Auto-generated at 2022-05-24 17:23:04.568704
\ No newline at end of file
diff --git a/dashtools/templating/templates/leaflet/src/app.py.template b/dashtools/templating/templates/leaflet/src/app.py.template
new file mode 100644
index 0000000..840ec21
--- /dev/null
+++ b/dashtools/templating/templates/leaflet/src/app.py.template
@@ -0,0 +1,47 @@
+'''
+ # @ Create Time: {createTime}
+'''
+
+from dash import html, dash, dcc
+import dash_leaflet as dl
+import pandas as pd
+import numpy as np
+
+# Create example data frame.
+lats = [56, 56, 56]
+lons = [10, 11, 12]
+df = pd.DataFrame(columns=["lat", "lon"], data=np.column_stack((lats, lons)))
+
+# Create markers from data frame.
+markers = [dl.Marker(position=[row["lat"], row["lon"]])
+           for i, row in df.iterrows()]
+
+# Create example app.
+app = dash.Dash(
+    title="{appName}",
+    external_stylesheets=[
+        'https://codepen.io/chriddyp/pen/bWLwgP.css'])
+
+# Declare server for Heroku deployment. Needed for Procfile.
+server = app.server
+
+app.layout = html.Div([
+    html.H1(children='Dash Leaflet App'),
+
+    html.Div(children='''
+        Dash: A web application framework for your data.
+    '''),
+
+    html.Hr(),
+
+    dl.Map(children=[dl.TileLayer(url="https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"), dl.LayerGroup(markers)],
+           style={'width': "100%", 'height': "100%"}, center=[56, 11], zoom=9, id="map"),
+
+    dcc.Markdown(
+        "Learn more about [Dash Leaflet](https://github.com/thedirtyfew/dash-leaflet)")
+
+], style={'width': '1000px', 'height': '500px'})
+
+
+if __name__ == '__main__':
+    app.run_server(debug=True)
diff --git a/dashtools/templating/templates/mantine/packages.txt b/dashtools/templating/templates/mantine/packages.txt
new file mode 100644
index 0000000..a968fbe
--- /dev/null
+++ b/dashtools/templating/templates/mantine/packages.txt
@@ -0,0 +1,1 @@
+dash-mantine-components
diff --git a/dashtools/templating/templates/sidebar/src/app.py.template b/dashtools/templating/templates/sidebar/src/app.py.template
index 0083f3b..f8270d6 100644
--- a/dashtools/templating/templates/sidebar/src/app.py.template
+++ b/dashtools/templating/templates/sidebar/src/app.py.template
@@ -77,7 +77,7 @@ def render_page_content(pathname):
     elif pathname == "/page-2":
         return html.P("Oh cool, this is page 2!")
     # If the user tries to reach a different page, return a 404 message
-    return dbc.Jumbotron(
+    return html.Div(
         [
             html.H1("404: Not found", className="text-danger"),
             html.Hr(),
diff --git a/dashtools/version.py b/dashtools/version.py
index 0f66308..fcb6b5d 100644
--- a/dashtools/version.py
+++ b/dashtools/version.py
@@ -1,1 +1,1 @@
-__version__ = '1.4'
+__version__ = '1.5'
diff --git a/docs/Configuring-Your-App.md b/docs/Configuring-Your-App.md
index 53dc287..8b3ed79 100644
--- a/docs/Configuring-Your-App.md
+++ b/docs/Configuring-Your-App.md
@@ -1,160 +1,169 @@
-# 🛠️ **dash-tools** Tutorial - _Create and Upload Your App_
+# **dashtools** Tutorial - _Making Changes and Deploying to Heroku_
 
-The following sections will provide a beginner guide on how to modify your dash-tools project. This is a beginner tutorial, so more advanced topics such as [callbacks](https://dash.plotly.com/basic-callbacks) will not be discussed. Check out the [Plotly Dash Documentation](https://dash.plotly.com/) for more detailed information on dash features.
+> The following tutorial will explain how `dashtools heroku --deploy` works, and how you can use `dashtools heroku --update` to push new changes to your deployed app. Check out the _[Usage Examples](../README.md#usage-examples)_ section in the Readme before you begin.
 
-## 1. Creating Your App
+### Creating an App
 
-> _Already have a plotly dash project you want to publish to Heroku? Check out the [Uploading an Existing App](#41-optional-uploading-an-existing-app) section!_
+1. Create a Dash project in a new directory called "MyApp" (using your terminal or command prompt) using the "sidebar" template:
+   <details>
+     <summary>Note</summary>
+     "MyApp" can be changed to any name. However, for the purpose of this tutorial, we recommend keeping it as "MyApp".
+   </details>
 
-Welcome to the Plotly Dash and dash-tools tutorial! If you are like me, you are interested in learning how Dash can be useful for creating Python data-vis applications (or any type of web app). On its own, Plotly Dash provides a very simple framework for making that happen. With dash-tools, most of the boilerplate dash code is handled for you, making it easier to create amazing apps with even fewer keystrokes.
+   ```bash
+   dashtools init MyApp sidebar
+   ```
 
-Let's begin. We are going to create a new dash application called "MyApp". When you create an app, a directory will be created from where you call the command.
+2. Open the default `app.py` file that comes with this project:
+   <details>
+     <summary>Windows</summary>
 
-**To create the app, type the following command in your console:**
+   ```bash
+    .\MyApp\src\app.py
+   ```
 
-```bash
-dash-tools --init MyApp
-```
+   </details>
+   <details>
+     <summary>Linux and Mac OS</summary>
 
-The newly created "MyApp" directory will look something like this:
+   ```bash
+    ./MyApp/src/app.py
+   ```
 
-```
-MyApp
-│-- README.md
-│-- Procfile
-│-- runtime.txt
-│
-└───src
-    |-- __init__.py
-    └-- app.py
-```
+   </details>
 
-Don't know what the files are? That's okay, we will get to that later in the [Project Files](#5-additional-info---project-files) section. First, we will run the dash app locally in your browser to see what it looks like!
+3. Make sure you are in your project's root directory:
 
-## 2. Running Your App
+   ```bash
+   cd MyApp
+   ```
 
-> _Running your app in-browser is the best way to make sure everything works correctly. You can even see changes in real time!_
+4. Run your app to ensure it works:
+   <details>
+     <summary>Note</summary>
+     The run command can be used instead of the traditional "python ./src/app.py" command. In effect, they do the same thing.
+   </details>
 
-Let's run your newly created dash application to see what is looks like. The main application file is called `app.py` located in the `src` folder in your MyApp project.
+   ```bash
+   dashtools run
+   ```
 
-**To see your project in browser, type the following command in your console:**
+   Visiting http://127.0.0.1:8050/ in your browser will show local changes to the app in real-time
 
-```bash
-cd MyApp
-python src/app.py
-```
+   ![iris image](../docs/templates/sidebar_theme.png)
 
-The output of the second command will look something like this:
+### Deploy App to Web with Heroku
 
-```
-Dash is running on http://127.0.0.1:8050/
+6. Deploy to Heroku:
+   <details>
+     <summary>Note</summary>
+     The heroku --deploy command creates a new heroku project in your account, adds a git remote called heroku, and pushes changes to the remote. You can use the heroku --update command, discussed below, to push new changes.
+   </details>
 
- * Serving Flask app 'app' (lazy loading)
- * Environment: production
-   WARNING: This is a development server. Do not use it in a production deployment.
-   Use a production WSGI server instead.
- * Debug mode: on
-```
+   ```bash
+   dashtools heroku --deploy
+   ```
 
-Navigate to [http://127.0.0.1:8050/](http://127.0.0.1:8050/) in your browser to view your app. Any changes (or errors) you make to the project will be displayed in real-time here! Next, we will be exploring how to make changes to your new app.
+7. Visit your app online to view what it looks like. The URL will be provided when you deploy your app after **Application Page**
 
-## 3. Changing Your App
+![deploy success message](../docs/readme/deploy_success_msg.png)
 
-> _Making changes to your app is easy. Make sure to have the application running in your browser to view any changes you make._
+### Make Changes
 
-Congrats! If you have made it this far, you were able to create and view a python web application! Feel free to give yourself a pat on the back and grab a coffee for the troubles. In this section, we will talk about what the included project files are for, and how to change your application to make it your own.
+8.  Change the tab title
 
-Let's start by looking in the `src/app.py` file. As mentioned below in the [Additional Info - Project Files](#5-additional-info---project-files) section, this file is the main dash application file. Any changes you make here will affect the layout of your application.
+    On line 20, replace `MyApp` in `title="MyApp"` with any name you want. Observe that the tab title in your browser will update automatically
 
-**To change your application header and text, open your src/app.py file and make the following changes to app.layout:**
-
-```
-app.layout = html.Div(children=[
-    html.H1(children='Hey There! I'm a new header!'),
-
-    html.Div(children='''
-        I can add any text that I want here.
-    '''),
-
-    dcc.Graph(
-        id='example-graph',
-        figure=fig
+    ```python
+    app = dash.Dash(
+        title="My Homepage",
+        external_stylesheets=[dbc.themes.BOOTSTRAP],
     )
-])
-```
-
-With the magic of programming, you will see the new header and text appear in real time in your browser. Cool, right? If you want to learn more about how you can modify your app in better detail, check out the [Plotly Dash Docs](https://dash.plotly.com/layout)!
-
-> _Note: `server = app.server` in your `src/app.py` file is necessary to upload to Heroku - do not change this!_
-
-Now that you made some changes, let's publish them online to Heroku.
-
-## 4. Deploy to Heroku
-
-> _Heroku is an application host. By hosting your app on Heroku, you will be able to access it from anywhere with an internet connection!_
-
-Stick in there - you're doing great! Let's publish your changes online with Heroku. Like the idea of having a free python webhost? Me too!
-
-Begin by creating a Heroku Account if you have not done so already: [Heroku Account Signup](https://signup.heroku.com/login). To upload your dash-tools application, run the following commands from the root of your MyApp directory.
-
-**Your project needs to be a git repository, type the following command in your console:**
-
-```bash
-git init
-```
-
-**Next, to begin the upload process, type the following command in your console:**
-
-```bash
-dash-tools --deploy-heroku
-```
-
-You will be prompted to choose either a unique Heroku name with the following criteria or have one generated for you. If you choose to create a name, make sure that:
-
-- Name is unique (not already taken)
-- At least 3 characters long
-- Starts with a letter, ends with a letter or digit
-- Only contains lowercase letters
-- Only contains letters, numbers and dashes
-
-That's really it. After you follow the on-screen instructions, your app will be uploaded for you, and you will be able to view it online!
-
-### 4.1 (Optional) Uploading an Existing App
-
-Uploading an existing application without using a dash-tools template is easy. You need to make sure of the following:
-
-- Your project contains `src/app.py` application file
-- Your `src/app.py` file contains the `server = app.server` declaration for heroku
+    ```
+
+    <details>
+    <summary>Receiving a "Site can’t be reached" error?</summary>
+    If you receive this error, make sure to correct any python syntax errors you might have and re-run the `dashtools run` command. Since your site is being re-rendered after each change you make, if your app isn't valid python syntax, it will cause this error
+    </details>
+
+9.  Change the sidebar
+
+    On line 46, the `sidebar` object defines the layout of the sidebar. Replace the object with the python snippet below to change the name of the sidebar links and title
+
+    ```python
+    sidebar = html.Div(
+        [
+            html.H2("My Site", className="display-4"),
+            html.Hr(),
+            html.P(
+                "Navigation Menu", className="lead"
+            ),
+            dbc.Nav(
+                [
+                    dbc.NavLink("Home", href="/", active="exact"),
+                    dbc.NavLink("About", href="/about", active="exact"),
+                    dbc.NavLink("Contact", href="/contact", active="exact"),
+                ],
+                vertical=True,
+                pills=True,
+            ),
+        ],
+        style=SIDEBAR_STYLE,
+    )
+    ```
 
-Since the deploy command will create your Procfile, runtime.txt and requirements.txt files for you, there is no need to worry about creating them yourself.
+    <details>
+    <summary>What happens if you click the About or Contact button?</summary>
+    You will also notice an error message in the right hand corner of the screen: "Callback error updating page-content.children".
+    <br><br>
+    Clicking either of these buttons will try to redirect you to the /about or /contact pages, which have not been created yet. In the next section, we will create those pages. 
+    </details>
 
-To deploy, the next steps will be the same [as above](#4-deploy-to-heroku).
+10. Add an _/about_ and _/contact_ Page
 
-## 5. Additional Info - Project Files
+    The `def render_page_content(pathname):` method on line 72 is responsible for routing traffic in your app. Here, we will change `page-1` and `page-2` to work with the Home and About pages, as seen above.
 
-As shown above in the [Creating Your App](#1-creating-your-app) section, the project directory contains boilerplate files:
+    Copy the following python snippet and replace the `render_page_content` method:
 
-#### **Dash Files:**
+    ```python
+    def render_page_content(pathname):
+        if pathname == "/":
+            return html.P("This is the content of the home page!")
+        elif pathname == "/about":
+            return html.P("This is your about page!")
+        elif pathname == "/contact":
+            return html.P("This is your contact page!")
+        # If the user tries to reach a different page, return a 404 message
+        return html.Div(
+            [
+                html.H1("404: Not found", className="text-danger"),
+                html.Hr(),
+                html.P(f"The pathname {pathname} was not recognised..."),
+            ]
+        )
+    ```
 
-These files are necessary for your app to run.
+    <details>
+    <summary>What happens if you click the About or Contact button?</summary>
+    Congratulations! Your sidebar buttons will work now as you have linked the navigation links with the new page routing.
+    </details>
 
-- `src/__init__.py` - _Declares the src directory as a python module_
-- `src/app.py` - _Main dash application file_
+### Pushing App Changes to Heroku
 
-#### **Heroku Files:**
+11. Make sure you are still in your project's root directory "MyApp"
 
-Files that are necessary to deploy your project to Heroku.
+12. After you are happy with your changes and verify that they work in browser, run the following command:
 
-- `Procfile` - _Specifies the commands that are executed by the app on Heroku startup_
-- `runtime.txt` - _Declares the python runtime version for heroku_
+    ```bash
+    dashtools heroku --update
+    ```
 
-> In the [Deploy to Heroku](#4-deploy-to-heroku) section, another necessary file will be generated automatically for you that isn't already included:
->
-> `requirements.txt` **(Not Created Yet)** - _List of required packages to include for Heroku_
+    The command will create a new commit and push it to the _heroku_ remote, created in the deploy step above. After the changes are received by heroku, your project will be re-built and re-deployed to the same web URL as before.
 
-#### **Other Files:**
+Congratulations, you have successfully made changes and updated your Heroku app!
 
-Other files that are not required by either dash or Heroku.
+## More Resources
 
-- `README.md`
-  - Markdown documentation file
+- [Readme](../README.md)
+- [Submit an Issue Ticket](https://github.com/andrew-hossack/dash-tools/issues/new/choose)
+- [Troubleshooting](../README.md#troubleshooting)
diff --git a/docs/iconify.png b/docs/iconify.png
deleted file mode 100644
index 9ec9638..0000000
Binary files a/docs/iconify.png and /dev/null differ
diff --git a/deploy-app.gif b/docs/readme/deploy-app.gif
similarity index 100%
rename from deploy-app.gif
rename to docs/readme/deploy-app.gif
diff --git a/docs/readme/deploy_success_msg.png b/docs/readme/deploy_success_msg.png
new file mode 100644
index 0000000..7beb9e1
Binary files /dev/null and b/docs/readme/deploy_success_msg.png differ
diff --git a/docs/intro_gif.gif b/docs/readme/intro_gif.gif
similarity index 100%
rename from docs/intro_gif.gif
rename to docs/readme/intro_gif.gif
diff --git a/docs/logo.png b/docs/readme/logo.png
similarity index 100%
rename from docs/logo.png
rename to docs/readme/logo.png
diff --git a/docs/advanced_theme.png b/docs/templates/advanced_theme.png
similarity index 100%
rename from docs/advanced_theme.png
rename to docs/templates/advanced_theme.png
diff --git a/docs/csv_template.png b/docs/templates/csv_template.png
similarity index 100%
rename from docs/csv_template.png
rename to docs/templates/csv_template.png
diff --git a/docs/default_theme.png b/docs/templates/default_theme.png
similarity index 100%
rename from docs/default_theme.png
rename to docs/templates/default_theme.png
diff --git a/docs/fastdash_template.png b/docs/templates/fastdash_template.png
similarity index 100%
rename from docs/fastdash_template.png
rename to docs/templates/fastdash_template.png
diff --git a/docs/iris_theme.png b/docs/templates/iris_theme.png
similarity index 100%
rename from docs/iris_theme.png
rename to docs/templates/iris_theme.png
diff --git a/docs/templates/leaflet.png b/docs/templates/leaflet.png
new file mode 100644
index 0000000..34585ef
Binary files /dev/null and b/docs/templates/leaflet.png differ
diff --git a/docs/mantine_theme.png b/docs/templates/mantine_theme.png
similarity index 100%
rename from docs/mantine_theme.png
rename to docs/templates/mantine_theme.png
diff --git a/docs/multipage_new_theme.png b/docs/templates/multipage_new_theme.png
similarity index 100%
rename from docs/multipage_new_theme.png
rename to docs/templates/multipage_new_theme.png
diff --git a/docs/sidebar_theme.png b/docs/templates/sidebar_theme.png
similarity index 100%
rename from docs/sidebar_theme.png
rename to docs/templates/sidebar_theme.png
diff --git a/docs/tabs_theme.png b/docs/templates/tabs_theme.png
similarity index 100%
rename from docs/tabs_theme.png
rename to docs/templates/tabs_theme.png
diff --git a/requirements.txt b/requirements.txt
index 0ba8bfd..3e0a618 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,12 +4,8 @@ dash-bootstrap-components
 Flask
 gunicorn
 packaging==21.0
-python-dotenv
 setuptools==47.1.0
 pipreqs
 pre-commit
 pytest
-pandas
-dash-mantine-components
-fast-dash
-dash-iconify
\ No newline at end of file
+pandas
\ No newline at end of file


Test patch:
diff --git a/tests/test_configUtils.py b/tests/test_configUtils.py
new file mode 100644
index 0000000..a1a6f3c
--- /dev/null
+++ b/tests/test_configUtils.py
@@ -0,0 +1,25 @@
+'''
+ # @ Author: Andrew Hossack
+ # @ Create Time: 2022-04-28 23:09:06
+'''
+import configparser
+import unittest
+from dashtools.data import configUtils
+
+
+class ConfigUtilsTest(unittest.TestCase):
+
+    def test_get_config_smoketest(self):
+        config = configUtils._get_config()
+        self.assertIsInstance(config, configparser.ConfigParser)
+
+    def test_get_config_value(self):
+        try:
+            # python_shell_cmd should is set with the pypi package
+            configUtils.get_config_value('python_shell_cmd')
+        except KeyError:
+            self.fail('KeyError')
+
+    def test_set_config_value(self):
+        # TODO
+        assert True
diff --git a/tests/test_randomWords.py b/tests/test_randomWords.py
new file mode 100644
index 0000000..eea8764
--- /dev/null
+++ b/tests/test_randomWords.py
@@ -0,0 +1,19 @@
+'''
+ # @ Author: Andrew Hossack
+ # @ Create Time: 2022-04-28 23:09:06
+'''
+import unittest
+from dashtools.data import randomWords
+
+
+class RandomWordsTest(unittest.TestCase):
+    """
+    Tests RandomWords for generating heroku name
+    """
+
+    def test_random_words(self):
+        # Test words are random
+        words = randomWords.get_words(10)
+        self.assertEqual(len(words), 10)
+        new_words = randomWords.get_words(10)
+        self.assertNotEqual(words, new_words)
diff --git a/tests/test_runtimeUtils.py b/tests/test_runtimeUtils.py
new file mode 100644
index 0000000..a21b7c5
--- /dev/null
+++ b/tests/test_runtimeUtils.py
@@ -0,0 +1,17 @@
+'''
+ # @ Author: Andrew Hossack
+ # @ Create Time: 2022-04-28 23:09:06
+'''
+import unittest
+from dashtools.runtime import runtimeUtils
+
+
+class RuntimeUtilsTest(unittest.TestCase):
+
+    def test_set_python_shell_cmd(self):
+        # TODO
+        assert True
+
+    def test_run_app(self):
+        # TODO
+        assert True