Writing Your First App (Part 1)

In this chapter, we will create a simple Tropofy App in a step by step fashion. After we’re finished creating the App, we’ll add some more advanced functionality in Writing Your First App (Part 2).

It assumes you already have Tropofy installed. If you do not, head over to the Installing Tropofy on a Unix System or Installing Tropofy on a Windows System sections.

The simple App created in this chapter will solve the following problem,

See also

Problem: A Department Store wants to display the locations of all of its Stores using an App. The App needs to do the following,

  • Input the latitude and longitude for each Store in a grid.
  • Output a map dispalying the Store location using the latitude and longitude.

Fortunately, the owner of the Department Stores decided to write a Tropofy App and has started with the code below. This code creates a Tropofy App which displays a grid allowing the Store locations to be entered.

from sqlalchemy.types import Text, Float
from sqlalchemy.schema import Column
from tropofy.database.tropofy_orm import DataSetMixin
from tropofy.app import AppWithDataSets, Step, StepGroup
from tropofy.widgets import SimpleGrid


class Store(DataSetMixin):
    name = Column(Text)
    latitude = Column(Float)
    longitude = Column(Float)


class MyFirstApp(AppWithDataSets):
    def get_name(self):
        return "My First App"

    def get_gui(self):
        return [
            StepGroup(
                name='Stores',
                steps=[
                    Step(
                        name='Stores',
                        widgets=[SimpleGrid(Store)]
                    )
                ]
            )
        ]

To see what this looks like so far, we need to quickstart the owner’s App. (Conveniently, this is one of the Tropofy example Apps called tropofy_starter).

From within your virtual environment run:

# Create a new App directory called tropofy_starter, containing the Tropofy example starter App
# If you do not have keys for the app, request one from info@tropofy.com
tropofy quickstart tropofy_starter

# Change into the tropofy_starter directory
cd tropofy_starter

# open the run file and add the app keys
notepad config.py

# Configure app and install dependencies
python setup.py develop

# Launch the app.
tropofy app -c config.py

Now visit http://localhost:8080 in your favourite browser to see your App so far.

To get a better understanding of this App, lets examine it piece-by-piece.

Data Model

The Tropofy Platform preserves data via a mechanism called a Data Set. A Data Set represents a collection of information relevant to an App and owned by an App User. Each User could have many Data Sets, all of them independent. Under the covers, Tropofy stores each Data Set in a database using a toolkit named SQLAlchemy.

SQLAlchemy has several distinct areas of functionality, however the Tropofy Platform predominantly uses the Object Relational Mapper (ORM). The ORM associates user-defined Python classes with database tables, and instances of those classes (objects) with rows in their corresponding tables.

With this understanding, we will now discuss the tropofy.app.DataSetMixin class. Consider the following example,

class Store(DataSetMixin):
    name = Column(Text)
    latitude = Column(Float)
    longitude = Column(Float)

The Store class extends the tropofy.database.DataSetMixin class. This is Tropofy’s way of including Store objects in a Data Set. In this way, any class that extends tropofy.database.DataSetMixin will be included in a Data Set.

In addition, SQLAlchemy uses the Column construct to define the underlying database table.

See also

Python constructor:

If you are familiar with Python you may be wondering why there is no def __init__(self):. SQLAlchemy provides us with a default constructor, and therefore we do not need to write one. Later, we will cover creating your own constructor.

Application

One of the core features of the Tropofy Platform is it’s ability to quickly generate an App. As you can see in our example, it is possible to define an App in very little code. The Tropofy Platform provides an interface for writing Apps. This interface is implemented by extending the tropofy.app.AppWithDataSets class.

class MyFirstApp(AppWithDataSets):
    def get_name(self):
        return "My First App"

    def get_gui(self):
        return [
            StepGroup(
                name='Stores',
                steps=[
                    Step(
                        name='Stores',
                        widgets=[SimpleGrid(Store)]
                    )
                ]
            )
        ]

In the example above, MyFirstApp extends the tropofy.app.AppWithDataSets class, and implements the tropofy.app.AppWithDataSets.get_gui() and the tropofy.app.AppWithDataSets.get_name() functions. This provides a mechanism for customising the App.

In this example, tropofy.app.AppWithDataSets.get_name() defines the name of the App. While, tropofy.app.AppWithDataSets.get_gui() defines the Graphical User Interface (GUI). The GUI is defined in a simple way using tropofy.app.Step() objects, and tropofy.app.StepGroup() objects. These relate to the steps followed by the user in the App’s workflow.

In addition to these two functions, we can also implement the get_example_data() function. This function provides a User with an example Data Set, and is discussed later.

Widgets

Widgets facilitate interaction with your Apps data model. They are the core component which make up the GUI. In our example, the tropofy.widgets.SimpleGrid widget is used to display our Store objects. This is an editable grid from which Stores can be added, edited and deleted.

Inserting a Widget in a Tropofy App

Each Step within a Tropofy App contains one or more widgets. A widget is added to our App by instantiating it and adding it to a step within tropofy.app.AppWithDataSets.get_gui(). The simplest widget to use is the tropofy.widgets.SimpleGrid. When created, tropofy.widgets.SimpleGrid is passed a SQLAlchemy Python class from which a grid is constructed. This class must conform to several conditions to work with the grid. In short, all classes created without a constructor and without renaming columns will work. If this means nothing to you, just create your SQLAlchemy Python classes in the same way that this example does, and they will work with tropofy.widgets.SimpleGrid.

A widget is added to the widgets parameter of tropofy.app.Step.

    def get_gui(self):
        return [
            StepGroup(
                name='Stores',
                steps=[
                    Step(
                        name='Stores',
                        widgets=[SimpleGrid(Store)]
                    )
                ]
            )
        ]

The tropofy.widgets.SimpleGrid is a special widget, unlike all others in Tropofy. It is imported with from tropofy.widgets import SimpleGrid and then instantiated directly. For all other widgets you must create a new class which inherits from the imported widget. If you’re unsure what inheritance means, checkout an example or you can read up on Python inheritance.

Writing a customised widget

A Widget is a Python class which implements a certain set of functions. These functions are different for each widget and are specified in the API’s linked to in the List of Widgets. The Tropofy Framework will call these functions as appropriate in order to display a widget, and send data to and from the server and client. If implemented (some functions are optional, others are not), Tropofy expects each widget to have a certain return type, and accept a pre-defined list of parameters. Beyond this, what you do inside a widget function in order to create the return value is up to you. Be creative.

Most widget functions are given access to the current tropofy.app.data_set.AppDataSet. With this, you can dynamically change the widgets to react to changes in data and the effects of other widgets.

Lets add a tropofy.widgets.KMLMap to our app that display the store locations. KML is a file format used to display geographic data on a map. The tropofy.widgets.KMLMap widget uses a KML file as its data source to render overlays on a map. Furthermore, the KML file itself can be downloaded from the map, and used offline in Google Earth. To get started with tropofy.widgets.KMLMap, we need to import the widget as well as the simplekml library, which we use as a wrapper to make the KML.

from tropofy.widgets import SimpleGrid, KMLMap
from simplekml import Kml

Now define the tropofy.widgets.KMLMap widget.

class MyKMLMap(KMLMap):
    def get_kml(self, app_session):
        kml = Kml()
        for store in app_session.data_set.query(Store).all():
            kml.newpoint(name=store.name, coords=[(store.longitude, store.latitude)])
        return kml.kml()

We have defined a single function within our new widget tropofy.widgets.KMLMap.get_kml(). This function is passed the current tropofy.app.data_set.AppDataSet from which we can access the Store objects which have been entered in the grid. We use the simplekml library to append a newpoint to the kml for each Store. return KML.kml() is used to return a kml formatted string which is the expected return type. For more information on this widget, see the tropofy.widgets.KMLMap documentation.

To add this to the GUI, simply add a new tropofy.app.Step. We have chosen to add this Step within a new tropofy.app.StepGroup, however it does not need to be.

    def get_gui(self):
        return [
            StepGroup(name='Stores', steps=[Step(name='Stores', widgets=[SimpleGrid(Store)])]),
            StepGroup(name='Map', steps=[Step(name='Map', widgets=[MyKMLMap()])])
        ]

Note

I really like how much I can customise widgets, why can’t I do this with tropofy.widgets.SimpleGrid?

This is coming. In the mean time, it is possible to create a read only grid from a customised data source with the tropofy.widgets.Chart widget. In tropofy.widgets.Chart.get_chart_type(), return Chart.TABLE.

Summary

That’s the end of Writing Your First App (Part 1). If you’re comfortable with the concepts covered in this introduction, you can move on to Writing Your First App (Part 2), which expands upon with the same example.

The full example code is below:

from sqlalchemy.types import Text, Float
from sqlalchemy.schema import Column
from tropofy.database.tropofy_orm import DataSetMixin
from tropofy.app import AppWithDataSets, Step, StepGroup
from tropofy.widgets import SimpleGrid, KMLMap
from simplekml import Kml


class Store(DataSetMixin):
    name = Column(Text)
    latitude = Column(Float)
    longitude = Column(Float)


class MyKMLMap(KMLMap):
    def get_kml(self, app_session):
        kml = Kml()
        for store in app_session.data_set.query(Store).all():
            kml.newpoint(name=store.name, coords=[(store.longitude, store.latitude)])
        return kml.kml()


class MyFirstApp(AppWithDataSets):
    def get_name(self):
        return "My First App"

    def get_gui(self):
        return [
            StepGroup(name='Stores', steps=[Step(name='Stores', widgets=[SimpleGrid(Store)])]),
            StepGroup(name='Map', steps=[Step(name='Map', widgets=[MyKMLMap()])])
        ]