Layouts API

Last Updated: April 2022

A Layout is a full featured and customizable view for Tethys Apps. They are different from Template Gizmos, which are only small piecies of that can be composed to create a full feature view, though many Layouts used Gizmos behind the scenes. For example, the Map Layout includes a fullscreen map-view with layer tree and includes advanced features feature selection, properties popups, and click and plot (Figure 1). In addition, Layouts provide rich JavaScript APIs that allow you to access the internal data and customize the dynamic behavoir of the view.

../_images/map_layout.png

Figure 1: Example of a Map Layout.

Supported Layouts

Here is a list of Layouts provided by Tethys Platform.

Layout Showcase App

The Layout Showcase App is a Tethys App can be installed in any Tethys Portal and includes live demonstrations and code samples of each Layout. See Layout Showcase App for instructions on installing the Layout Showcase App.

Tethys Layouts

Layouts are implemented using Django class-based views / contollers in a novel way that allows all the logic of complex views to be encapsulated in a single class. This makes them easy to implement, customize, and maintain. All Layouts inherit much of their core functionality from the TethysLayout class.

Class Based Views

TethysLayout is a Django class-based view, which is a controller implemented as a class instead of as a function. Class-based views have methods that coorespond with the different HTTP methods (e.g. get() -> GET and post() -> POST). For example, when a GET request is sent to a class-based view, it is routed to the get() method of the class. The get() method is structured in the same way as a function controller, accepting a request argument and returning a response.

as_controller()

Class-based views have an as_view() method that is used to return the entry point to the class (e.g.: entrypoint = TethysLayout.as_view()). The entrypoint is what is used to map the class-based view to a URL. Since Tethys uses the terminology "controller" instead of "view", TethysLayout classes have a as_controller() method that does the same thing as as_view().

Configure via Properties

Most of the configuration for a Layout is done by setting/overriding class properties in a subclass. This makes it easy to customize the behavoir of the Layout view. Here is an example of some of the configuration options for a Map Layout:

from tethys_sdk.layouts import MapLayout


class MyMapLayout(MapLayout):
   map_title = 'My Map Layout'
   map_subtitle = 'Subtitle'
   basemaps = ['OpenStreetMap', 'ESRI', 'Stamen']
   default_map_extent = [-65.69, 23.81, -129.17, 49.38]  # CONUS bbox
   max_zoom = 16
   min_zoom = 2

Customize Behavoir via Methods

Some customization of Layouts is done by overriding methods of the class in a subclass. Many of the methods of a Layout exist expressly for this purpose. For example, to add layers to a Map Layout, the compose_map() method needs to be overridden in a subclass like so:

class MyMapLayout(MapLayout):

    ...

    def compose_layers(self, request, map_view, *args, **kwargs):
        """
        Add layers to the MapLayout and create associated layer group objects.
        """
        # WMS Layer
        usa_population = self.build_wms_layer(
            endpoint='http://localhost:8181/geoserver/wms',
            server_type='geoserver',
            layer_name='topp:states',
            layer_title='USA Population',
            layer_variable='population',
            visible=True,  # Set to False if the layer should be hidden initially
        )

        # Add layer to map
        map_view.layers.append(usa_population)

        # Add layer to layer group
        layer_groups = [
            self.build_layer_group(
                id='usa-layer-group',
                display_name='United States',
                layer_control='radio',  # 'radio' or 'check'
                layers=[
                    usa_population,
                ],
            ),
        ]

        return layer_groups

Easy REST Endpoints

The TethysLayout provides a mechanism for easily setting up REST endpoints without the need to define additional URLs. This is one of the features that allows complex dynamic behavior of some Layouts to be contained in a single class. You can leverage this capability in your own Layout views to create custom REST endpoints.

First, create a new method on the subclass:

class MyMapLayout(MapLayout):

    ...

    def update_data(self, request, *args, **kwargs):
        """
        Custom REST method for updating data form Map Layout view.
        """
        parms = request.POST
        ...
        return JsonResponse({'success': True})

In the JavaScript for the view, use AJAX to call the URL of the view ('.') and add a method parameter with a value that is the name of the method on the class to call. The name can be specified using either underscores or hyphens (method=updated_data and method=updated-data are equivalent). For example:

 fetch(".", {
      method: 'POST',
      headers: {
         'Content-Type': 'application/json',
      },
      body: JSON.stringify({
         method: "update-data",
         other: 1,
         another: true
      }),
}).then((response) => response.json()).then((data) => {
   // Do something magical with your data :)
});

Custom Template and JavaScript

The HTML template for any Layout can be customized by creating an HTML document that extends the template of the Layout. This is most often done to add custom CSS or JavaScript to the template as shown in this example for a Map Layout:

{% extends "tethys_layouts/map_layout/map_layout.html" %}
{% load static %}

{% block scripts %}
  {{ block.super }}
  <script src="{% static 'layout_showcase/js/map.js' %}" type="text/javascript"></script>
{% endblock %}

Tell the Layout to use the custom template using the template_name property:

class MyMapLayout(MapLayout):
    template_name = 'my_first_app/custom_map_layout.html'
    ...

API Documentation

class tethys_layouts.views.tethys_layout.TethysLayout(**kwargs)

Base controller for all Tethys Layout views. Pass kwargs to as_controller() to override TethysLayout properties.

get(request, *args, **kwargs)

Handle GET requests, either rendering the page or routing to class method.

get_context(request, context, *args, **kwargs)

Hook to add additional content to context. Avoid removing or modifying items in context already to prevent unexpected behavior.

Parameters:
  • request (HttpRequest) -- The request.

  • context (dict) -- The context dictionary.

Returns:

modified context dictionary.

Return type:

dict

get_permissions(request, permissions, *args, **kwargs)

Hook to perform has_permission checks in. Values returned here are added to the context.

Parameters:
  • request (HttpRequest) -- The request.

  • permissions (dict) -- The permissions dictionary with boolean values.

Returns:

modified permisssions dictionary.

Return type:

dict

on_get(request, *args, **kwargs)

Hook that is called at the beginning of the get request, before any other controller logic occurs.

Parameters:

request (HttpRequest) -- The request.

Returns:

If an HttpResponse is returned, render that instead.

Return type:

None or HttpResponse

post(request, *args, **kwargs)

Route POST requests to Python methods on the class.

request_to_method(request)

Derive python method on this class from "method" GET or POST parameter.

Parameters:

request (HttpRequest) -- The request.

Returns:

the method or None if not found.

Return type:

callable