Migrating Apps from Tethys 3 to 4

Last Updated: December 2022

This guide describes how to migrate Tethys 3 apps to work in Tethys 4. There are several "breaking" changes that were introduced in Tethys 4 that may cause apps developed in Tethys 3 to not load or function properly. Use the tips below to help you make the changes necessary for the app to function properly in Tethys 4.

Note

To migrate an app developed for Tethys 2 to Tethys 4, you will need to first complete the steps found in the Migrating Apps from Tethys 2 to 3 guide.

Index Controller

The index property of the app class is used to tell Tethys which controller should be used for the home page of the app. In Tethys 4, the app namespace portion (the part before the :) should be dropped.

For example, a Tethys app with an index property looking like this:

class MyFirstApp(TethysAppBase):
    index = "my_first_app:home"

should be changed to this:

class MyFirstApp(TethysAppBase):
    index = "home"

Controller Decorators

The url_maps() method is being deprecated in favor of the simpler controller decorator method introduced in Tethys 4. The url_maps method is temporarily available in Tethys 4 to allow for easier app migration, but support for the ``url_maps`` method will be dropped in Tethys 4.1.0.

It is strongly recommended to migrate apps to use the new controller decorator approach and remove the url_maps() method from the app.py. If you still wish to declare UrlMaps in the app.py, use the new register_url_maps() method and then remove the url_maps() method (see: Register URL Maps Method). The console will display warnings for apps still using the url_maps method in Tethys 4 to encourage migration to one of the new methods described above as soon as possible. Don't wait for Tethys 4.1 to migrate.

Use the following tips to help you migrate:

  1. Review the Routing API documentation to become familiar with the controller decorator.

  2. If your app has a lot of controllers, use the url_maps() in app.py to make a list of them. There should be one controller function or class for each UrlMap listed.

  3. Add the controller decorator to each controller function or class in your app.

  4. If the default URL or name generated by the controller decorator don't match what is set in the UrlMap, override it by setting the url and name arguments of the controller decorator.

  5. If your controller uses any other decorators, remove them and use the appropriate arguments in the controller decorator instead.

  6. Remove the url_maps() method from the app.py.

Note

Tethys 4 also introduces the consumer and handler decorators that function equivalently for consumers and handler functions. See the Routing API documentation for more details.

Search Path

Tethys will only search for the controller, consumer, and handler decorators in modules named controllers.py or consumers.py or any module located in packages named controllers and consumers. If your app has controllers located in modules with different names than these defaults, the recommended migration is to move the modules into a package named either controllers or consumers.

However, you may also use the controller_modules property of the app class to define addtiional search locations. For example:

class MyFirstApp(TethysAppBase):
    ...
    controller_modules = [
        'custom_controllers',  # For a module named custom_controller.py in the same directory as app.py
        'rest',  # For a package named "rest" in the same directory as app.py containing modules with controllers
    ]

Workspaces

It is recommended that you use the app_workspace and user_workspace arguments of the controller decorator to acquire workspaces in Tethys 4.

For example, the following controllers:

from tethys_sdk.workspaces import app_workspace
from .app import MyFirstApp as app


def controller_a(request):
    """Gets user workspace from old app class method."""
    user_workspace = app.get_user_workspace(request.user)
    uw_path = user_workspace.path
    ...


@app_workspace
def controller_b(request, app_workspace):
    """Gets app workspace from the app_workspace decorator."""
    aw_path = app_workspace.path
    ...

should be refactored to use the controller decorator as follows:

from tethys_sdk.routing import controller


@controller(user_workspace=True)
def controller_a(request, user_workspace):
    """Gets user workspace from the controller decorator."""
    uw_path = user_workspace.path
    ...


@controller(app_workspace=True)
def controller_b(request, app_workspace):
    """Gets app workspace from the controller decorator."""
    aw_path = app_workspace.path
    ...

Note

In rare cases when the controller decorator cannot be used to acquire workspaces, you may use the get_app_workspace() and get_user_workspace() methods of the app class. These methods are no longer deprecated. However, they will raise an exception if the quotas feature is enabled and the user or app workspace is out of storage space. The controller decorator automatically handles these exceptions, but when using the get_app_workspace() and get_user_workspace() directly, you will need to handle those exceptions.

Templates

In Tethys 4, the staticfiles template library needs to be changed to static:

For example, the following load statement:

{% load staticfiles %}

needs to be changed to this:

{% load static %}

Theme and Styles

The frontend CSS framework, Bootstrap, was upgraded from version 3 to version 5 in Tethys Platform 4. As a result, any Boostrap components that are used in templates will need to be updated to use the Bootstrap 5 syntax so they function and look as expected. This section describes how to update the most common Bootstrap components that are used in Tethys Apps.

Note

The app base templates and Template Gizmos have all been updated to use Bootstrap 5. You should only need to upgrade Bootstrap code that is contained in your app. The Gizmos that are used by your app will be automatically upgraded.

Bootstrap Icons

The glyphicons that were included in Bootstrap 3 were moved to a separate library called Bootstrap Icons. The Bootstrap Icons use a different syntax than glyphicons, so any glyphicons that are used in your app will not show up in Tethys Platform 4.

However, Tethys Platform 4 includes the Boostrap Icons library in the base template for apps, so the only change that you should need to make is to update the icon to an equivalent Bootstrap Icon:

<i class="bi bi-home"></i>

The Bootstrap Icons library has many more icons than the Bootstrap 3 glyphicon library, however the names of many icons have changed. For example glyphicon-pencil featured a filled pencil icon, but bi-pencil is an outlined pencil icon. To use the equivalent Bootstrap Icon, you will need to use the bi-pencil-fill icon. Fortunately, the Bootstrap Icons website has an excellent search capability that makes it easy to find equivalent icons.

For example, if your app had the following glyphicons:

<span class="glyphicon glyhpicon-home" aria-hidden="true"></span>
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
<span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>

you could update them to use the following equivalent Bootstrap Icons:

<i class="bi bi-house-door-fill"></i>
<i class="bi bi-trash"></i>
<i class="bi bi-save"></i>

App Navigation

The navigation items that are used in the app_navigation_items block of your templates (usually in the base.html) need to have slight changes made to work properly. To update the navigation elements do the following:

  1. Add a nav-item class on each <li> element.

  2. Add a class nav-link on each <a> element.

  3. Move the active class (if applicable) from the <li> element to the <a> element.

For example, an old app_navigation_items block like this:

{% block app_navigation_items %}
  <li class="title">App Navigation</li>
  <li class="active"><a href="">Home</a></li>
  <li><a href="">Jobs</a></li>
  <li class="separator"></li>
  <li><a href="">Get Started</a></li>
{% endblock %}

should be changed to this:

{% block app_navigation_items %}
  <li class="nav-item title">App Navigation</li>
  <li class="nav-item"><a class="nav-link active" href="">Home</a></li>
  <li class="nav-item"><a class="nav-link" href="">Jobs</a></li>
  <li class="nav-item separator"></li>
  <li class="nav-item"><a class="nav-link" href="">Get Started</a></li>
{% endblock %}

Data Attributes

All Boostrap related data attributes on HTML elements now include a bs namespace. For example, data-target needs to be changed to data-bs-target. Use the following tips to help you migrate data attributes appropriately:

  1. Perform a project-wide search on your app source code for data- to find instances of data attributes.

  2. Review the tips below for Tooltips, Dropdowns, and Modals.

  3. Review the Bootstrap 5 documentation for details about changes to other components that your app uses that are not listed below.

Buttons

The btn-default class no longer exists in Bootstrap 5. Change it to btn-outline-secondary for a similar looking button. Alternatively, choose from several new styles of buttons that can be found here: Boostrap Buttons.

Tooltips

Bootstrap Tooltip components have the following data attributes that need to be updated:

  • data-toggle: data-bs-toggle

  • data-placement: data-bs-placement

For example, this button with an old-style tooltip:

<button type="button" class="btn btn-default" data-toggle="tooltip" data-placement="bottom" title="Tooltip on bottom">
  Tooltip on bottom
</button>

needs to be updated to this:

<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tooltip on bottom">
  Tooltip on bottom
</button>

Modals

Bootstrap Modal components have the following data attributes that need to be updated:

  • data-dismiss: data-bs-dismiss

  • data-toggle: data-bs-toggle

  • data-target: data-bs-target

In addition the class of the close button should be changed from close to btn-close and the &times; should be removed. The modal title and close button also need to be reordered (title first, button second).

For example, the following old-style modal:

<!-- Button trigger modal -->
<button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#exampleModal">
  Launch demo modal
</button>

<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
      </div>
      <div class="modal-body">
        <p>Modal body text goes here.</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

needs to be updated to this:

<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
  Launch demo modal
</button>

<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <p>Modal body text goes here.</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

Hidden Class

The hidden class was changed to d-none Bootstrap 5 (short for display none). Replace all instances of hidden with d-none. Be sure to check for use in your JavaScript as this class is often used to hide elements dynamically.

References in Gizmos

Although Gizmos have been updated to use Bootstrap 5, some arguments passed to them may include Bootstrap 3 values. For example, the Button and TextInput Gizmos have arguments that accept the names of icons to be displayed on them that need to be updated to Boostrap Icons values. Other arguments to check are attributes, classes, and style that may have old Bootstrap 3 values that need to be updated to use Bootstrap 5 values. Use the following tips to help you migrate:

  1. Do a project-wide search for "glphyicon" and update any icon arguments for Gizmos to the name of equivalent Bootstrap Icons (without the bi).

  2. Check for old Bootstrap data attributes in the attributes arguments of Gizmos (see: Data Attributes).

  3. Check for old Bootstrap classes in the classes arguments of Gizmos (e.g. btn-default).

  4. Check for old Bootstrap values in the style arguments of some Gizmos (e.g.: default).

For example, to update this Button Gizmo to have the equivalent style and icon:

add_button = Button(
    display_text='Add',
    icon='glyphicon glyphicon-plus',
    style='success',
)

change the value of icon (without the bi portion) of the Bootstrap Icon:

add_button = Button(
    display_text='Add',
    icon='plus-circle-fill',
    style='success',
)

Gizmos

Static Dependencies

Most of the static dependencies of Gizmos, the CSS and JavaScript libraries they depend on, have been updated. As a result, some funtionality may have changed. If your app interacts with Gizmos using JavaScript, you may need to make some minor changes to the code to work with the new versions. This will be enirely dependent on what functionality you use.

For example, the MapView gizmo depends on the OpenLayers JavaScript library. If your app uses the getMap() JavaScript API method of the MapView Gizmo and then manipulates the map object (add layers, change map settings, etc), it is likely using the OpenLayers API to do so. If your app doesn't seem to be functioning correctly, check the developer console in your web browser for errors.

Note

This suggestion only applies to your custom JavaScript code. The Gizmos have been updated to work with the new versions of the libraries. If you use Gizmos and don't use JavaScript to manipulate them, then this likely doesn't apply to you.

MapView Gizmo

The basemap argument of the MapView Gizmo needs to be specified as a list, even if only one basemap is listed. For example a MapView Gizmo specifing only 'OpenStreetMap' basemap like this:

 map_view_options = MapView(
    height='500px',
    width='100%',
    layers=[...],
    view=view_options,
    basemap='OpenStreetMap',
 )

should be updated to this:

 map_view_options = MapView(
    height='500px',
    width='100%',
    layers=[...],
    view=view_options,
    basemap=['OpenStreetMap'],
 )

WebSockets

The URLs Tethys generates for WebSockets now begin with /apps/ to be consistent with the other URLs generated by Tethys. Update any JavaScript that uses these URLs to connect to WebSockets.

For example, the following code in a Tethys 3 app:

let websocket = new WebSocket('ws://' + window.location.host + '/dam-inventory/dams/notifications/ws/');

needs to be updated to this in Tethys 4:

let websocket = new WebSocket('ws://' + window.location.host + '/apps/dam-inventory/dams/notifications/ws/');

Schedulers

In Tethys 4, job Schedulers should be assigned to apps using Scheduler app settings. If your app uses job schedulers:

  1. Create a Scheduler app setting by defining the scheduler_settings() method on the app class. See Scheduler Settings.

  2. Define a Scheduler Service and assign it to the app setting. See Scheduler Service.

  3. Use the get_scheduler() app class method to get the Scheduler from the setting. See Using Scheduler Settings.

Other Resources

It is not possible to anticipate every migration step that will be needed. The suggestions are for the cases that will be most commonly encountered. The following resources may provide additional guidance for migrating your app(s).

Upgrade Older Apps

To upgrade apps that are several versions behind, complete the app migration process for each version in order. For example, to upgrade an app developed in Tethys 2 to Tethys 4, first complete the "2 to 3" migration instructions and then complete the "3 to 4" instructions.