Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
LynnS1
Advisor
Advisor
Many of us may know Python as a programming language with data science libraries such as Tensorflow for building Deep Neural Networks, Pandas for data preparation and agile analytics, or Jupyter for putting it all together in a digital notebook. Some of us may have already coded several microservices for SAP Data Hub using Python operators within a pipeline. But did you know that Python can serve Fiori Applications?

Python Flask is a lightweight web application framework which supports RESTful API and MVC (Model, View, Controller) paradigms required by many projects where scalable integration is required. With a few adjustments to existing Fiori templates, you can build applications locally and deploy globally. Although the Flask web server is not production quality, when it is placed within an Apache framework then it can provide a robust platform as seen in use today with massive digital first companies like Netflix, Lyft, and Reddit. In addition it is also used in SAP Extended Services Advanced (XSA) and SAP Cloud Platform (SCP).

Why would you be interested in this series?



  • You are struggling to locally develop Fiori apps

  • You don’t have connections to ABAP or HANA

  • You want to try something new and unopinionated.

  • You really like SAP Data Hub and want to see what else Python can do


What you should know to complete this series?



  • Basic Python, Javascript, HTML/XML

  • Familiarity with Fiori resources, specifically hana.ondemand.com.

  • Access to Github to access sample code.


In this first of a multi-part series, we will focus on the basic project structure and changes to Fiori application files required to serve a basic template application. By the last part in the series, we will show how SAP HANA, OrientDB, and innovative use cases are integrated into a fully productive web application.

The sample application contains a simple launchpage with tile:



...and Master-Detail app with dummy date:



DISCLAIMER: This is not a guide on how to code Fiori applications nor is it an end-to-end tutorial on setting up web applications with Python Flask. There are plenty of resources that cover this far better than I can and I am happy to point any interested parties to them. To run the application and code, installing Python and Flask is required. This is rather an integration topic with pinpoint solutions to issues that I have not found elsewhere.

What is Flask?


Flask is a BSD licensed Python minimalistic web application framework based on Werkzeug and Jinja 2. A Flask project can consist of a single python file that initializes the app server and contains routes, models, and any supporting logic as shown in the Flask open source documentation. However, this is not scalable for production and therefore we break these functions into 3 different files.
#basic_template/run.py
from flaskr import app

if __name__ == "__main__":
app.run(debug=True)

At the application parent directory is run.py which imports the app class from the Flask resources directory (flaskr) and runs default on port 5000. We set the debug configuration to true so that the server resets when changes are made to the code base. Other variables to serve on HTTP or HTTPS are available but won’t be covered at this stage.
#basic_template/flaskr/__init__.py
from flask import Flask

app = Flask(__name__)

import flaskr.routes

Within flaskr we have __init__.py which initializes the application and additionally makes the flaskr directory into a Python module which can be imported into other files. Also at this level is routes.py which provides the interface between the Fiori application and any back end resources we may have in the future such as SAP HANA or OrientDB.
#basic_template/flaskr/routes.py
from flask import render_template, jsonify
import time
from datetime import datetime
from . import app

@app.route('/')
def index():

return render_template("index.html")

@app.route('/DashboardAnalytics', methods=["GET"])
def DashboardAnalytics():
"""
Return a simple odata container with date time information
:return:
"""
odata = {
'd': {
'results': []
}
}
i = 0
names = 'abcdefghijklmnopqrxtu'
while i < 20:
odata['d']['results'].append({
"id": i,
"name": names[i],
"datetime": datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S'),

})
i+=1
return jsonify(odata)

That’s it for the python required to run this demo and in less than 50 lines which includes a small function to generate dummy data in JSON format thanks to Flask's internal "jsonify" function. To start the application, navigate to the parent directory containing run.py and execute it with Python as seen below. If you have all dependencies installed you should see the server running on the local host.
> python run.py

In summary we have the following structure with 3 files distributed in 2 folders.

  • FioriFlask\run.py (Execution to start serving the application on port 5000)

  • FioriFlask\flaskr\ (resources for Flask)

    • __init__.py (initialize the app and directory as a python module)

    • routes.py (interface between python back end services and javascript based front end)




A Fiori project on the other hand requires several folders and hierarchies to manage user interactions. We won’t be exhausting the functionality at each level but instead focus on what needs to be changed from the traditional Fiori project structure for serving with Flask. In this case it is a single step that requires understanding 2 additional folders in our Flask project structure, templates and static.

  • FioriFlask\flaskr\templates\index.html

  • FioriFlask\flaskr\static\(all other Fiori Files)


The application will serve HTML files from the routes.py file and Flask’s  render_template function which points at the templates folder by default. Therefore, we need to move the index.html file into the templates folder. This is the typical case for single-page apps not only common in Fiori’s framework but also seen in other popular examples like React and Angular. In any index file case we modify it with pointers to the rest of the application content including controllers, views, fragments, css, and other customizing files. For Fiori this means changing only one line using Jinja.

Flask uses a templating engine called Jinja which allows Python logic to be implemented directly in static web files like HTML and is what we will be using to create the pointers within the index file. A Fiori index file is the container for the application content and is initialized with javascript that sets the content source, theme, resource roots and other options such as dynamic loading. The key line to change here is the value for data-sap-ui-resourceroots.
<!--basic_template/flaskr/templates/index.html-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Basic Template</title>

<script id="sap-ui-bootstrap"
src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_belize"
data-sap-ui-resourceroots='{
"sap.ui.demo.basicTemplate": "{{ url_for('static', filename='.')}}"}'
}'
data-sap-ui-oninit="module:sap/ui/core/ComponentSupport"
data-sap-ui-compatVersion="edge"
data-sap-ui-async="true"
data-sap-ui-frameOptions="trusted">
</script>
</head>

<body class="sapUiBody">
<div data-sap-ui-component data-name="sap.ui.demo.basicTemplate" data-id="container" data-settings='{"id" : "basicTemplate"}'></div>
</body>
</html>

This line can contain a json format to identify several roots. However, for this example we want to ensure that there is a value identifying the namespace of the application and that it is set to the application’s static folder using Jinja syntax, specifically url_for and setting it to the static directory. You will notice the Jinja specific syntax through the double curly braces {{ }}. In our case this will be:
"sap.ui.demo.basicTemplate": "{{ url_for('static', filename='.')}}"}'

This will ensure that when the application server runs, it will set up its static directory so that all Fiori resources are accessible and integrated. That’s it for the initial setup and the only line of code necessary to ensure the application can run. The remaining files can be dropped directly as is into the static folder.

The next step is to ensure the application can make calls to the back end. For this we will use the press function of the tile within the App.controller.js. The press function takes the name of the tile pressed and uses that as the url to call to the back end. In the routes.py file we have defined an endpoint to match the name of that tile, DashboardAnalytics and it returns a simple JSON with some values to show it populated the application.
//basic_template/flaskr/static/controllers/App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"../model/formatter",
"sap/ui/core/routing/History"
], function(Controller, formatter, History) {
"use strict";

return Controller.extend("sap.ui.demo.basicTemplate.controller.App", {

formatter: formatter,

onInit: function () {

self = this;

},
press: function(tile) {

var selectedData = {};
sap.ui.core.BusyIndicator.show(0);
this.getData(tile).done(function(result) {
var oModel = new sap.ui.model.json.JSONModel(result.d);
sap.ui.getCore().setModel(oModel, "DashboardAnalyticsModel");
self.routeToApp(tile);

}).fail(function(result) {
console.log(result);
});

},
onNavBack: function() {

var sPreviousHash = History.getInstance().getPreviousHash();

if (sPreviousHash !== undefined) {
history.go(-1);
} else {
this.getRouter().navTo("home", {}, true);
}

},

getData: function(url){
return jQuery.ajax({
url: url,
type: "GET"
});
},

getRouter : function () {
return sap.ui.core.UIComponent.getRouterFor(this);
},

routeToApp: function(tile) {
self.getRouter().navTo(tile, {});

},
});
});

Instead of returning the index file as before with render_template, we just want to update the application without an entire re-load. Fiori has JQuery and Ajax built in to ensure we can do this, and Flask provides the pre-packaged function called, jsonify mentioned earlier that transforms Python dictionaries into a JSON that are then updated into the Fiori models. Therefore, once the button is pressed, it makes a GET call to the python routes and receives a JSON to populate the UI elements.
<!--basic_template/flaskr/static/view/App.view.xml-->
<mvc:View
displayBlock="true"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Shell>
<App id="app" class="appBackground">
<pages>
<!--<Page title="{i18n>title}">-->
<!-- <content></content>-->
<!--</Page>-->
</pages>
</App>
</Shell>
</mvc:View>

In Fiori and MVC in general, you will have separate controllers for each view. Above is the simple App view which is a container for all pages including the launchpad view below.
<!--basic_template/flaskr/static/view/Home.view.xml-->
<core:View controllerName="sap.ui.demo.basicTemplate.controller.App"
xmlns:core="sap.ui.core"
xmlns="sap.uxap"
xmlns:m="sap.m"
height="100%">
<m:Page class="firstPage" title="Launchpad">
<m:customHeader class="NavToolbar">
<m:Bar class="audienceSensorHeader">

<m:contentLeft>

</m:contentLeft>

<m:contentMiddle>
<m:Text id="titleText" class="titleTextHeader" text="Launchpad"/>
</m:contentMiddle>

<m:contentRight>
</m:contentRight>

</m:Bar>
</m:customHeader>

<m:content>

<m:HBox class="mainPage" renderType="Bare">
<ObjectPageLayout id="ObjectPageLayout">
<sections>
<ObjectPageSection id="section1" title="Section 1">
<subSections>
<ObjectPageSubSection id="OpsSection1" title="Operations">
<!--<blocks>-->
<core:Fragment fragmentName="sap.ui.demo.basicTemplate.view.Fragments.Operations" type="XML"/>
<!--</blocks>-->
</ObjectPageSubSection>
</subSections>
</ObjectPageSection>
</sections>
</ObjectPageLayout>
</m:HBox>

</m:content>
</m:Page>
</core:View>

Here we see an example with the FlexibleColumnLayout which provides 3 main frames for navigation, header, and objective detail and its controller below. Together they form the basis for the Master-Detail Fiori pattern for apps.
<!--basic_template/flaskr/static/view/FlexibleColumnLayout.view.xml-->
<mvc:View id="Main" controllerName="sap.ui.demo.basicTemplate.controller.FlexibleColumnLayout"
xmlns:mvc="sap.ui.core.mvc"
displayBlock="true"
xmlns="sap.f"
xmlns:core="sap.ui.core"
xmlns:m="sap.m">
<m:Page class="secondPage" title="{i18n>title}">
<m:customHeader class="NavToolbar">
<core:Fragment fragmentName="sap.ui.demo.basicTemplate.view.Fragments.Header" type="XML"/>
</m:customHeader>
<m:content>
<FlexibleColumnLayout id="fcl" initialMidColumnPage="start" layout="TwoColumnsMidExpanded">
<beginColumnPages>
<m:Page showHeader="false">
<m:content>
<core:Fragment fragmentName="sap.ui.demo.basicTemplate.view.Fragments.Master" type="XML"/>
</m:content>
</m:Page>
</beginColumnPages>
<midColumnPages>
<m:Page id="start" showHeader="false">
<m:content>
<core:Fragment fragmentName="sap.ui.demo.basicTemplate.view.Fragments.Details" type="XML"/>
</m:content>
</m:Page>
<m:Page id="charts" showHeader="false">
<m:content>
<m:Text text="Two" />
</m:content>
</m:Page>
</midColumnPages>
</FlexibleColumnLayout>
</m:content>
</m:Page>
</mvc:View>

//basic_template/flaskr/static/controller/FlexibleColumnLayout.controller.js
sap.ui.define([
"sap/ui/demo/basicTemplate/controller/App.controller",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator",
"sap/ui/model/json/JSONModel",
"sap/m/MessageToast"
], function(AppController, Filter, FilterOperator, JSONModel, MessageToast) {
"use strict";

var self;
return AppController.extend("sap.ui.demo.basicTemplate.controller.FlexibleColumnLayout", {

onInit: function() {

this.oModelSettings = new JSONModel({
maxIterations: 200,
maxTime: 500,
initialTemperature: 200,
coolDownStep: 1
});
this.getView().setModel(this.oModelSettings, "settings");
this.getView().setModel(sap.ui.getCore().getModel("DashboardAnalyticsModel"), "DashboardAnalyticsModel");
sap.ui.core.BusyIndicator.hide(0);
}
});
});

Fiori apps are highly configurable and scalable due their implementation through small files that are called into the mobile stack as needed. An example of this is the concept of fragments which are individual pieces of a page that can be re-used across the application. Below are the fragments required to render the basic objects in the Master-Detail app pattern.
<!--basic_template/flaskr/static/view/Fragments/Details.fragment.xml-->
<core:FragmentDefinition
xmlns="sap.m"
xmlns:f="sap.f"
xmlns:core="sap.ui.core"
xmlns:u="sap.ui.unified"
xmlns:t="sap.ui.table"
xmlns:layout="sap.ui.layout">
<f:DynamicPage id="dynamicPageId" preserveHeaderStateOnScroll="true" headerExpanded="{/headerExpanded}">
<!-- DynamicPage Title -->
<f:title>
<f:DynamicPageTitle>
<f:heading>
<Title text="Entity name: {SELECTED_ATTACHMENT>/pname}"/>
</f:heading>
<f:expandedContent>
<Label text="Age: {SELECTED_ATTACHMENT>/age}"/>
</f:expandedContent>

<f:actions>
<ToolbarSpacer/>
</f:actions>
</f:DynamicPageTitle>
</f:title>
<!-- DynamicPage Header -->
<f:header>
<f:DynamicPageHeader pinnable="true">
<f:content>
<HBox alignItems="Start" justifyContent="SpaceBetween">
<!--<layout:HorizontalLayout allowWrapping="true" >-->
<layout:VerticalLayout class="sapUiMediumMarginEnd">
<ObjectAttribute title="Name" text="{DashboardAnalyticsModel>/results/0/name}"/>
<ObjectAttribute title="Date" text="{DashboardAnalyticsModel>/results/0/datetime}"/>
</layout:VerticalLayout>
</HBox>
<!--</layout:HorizontalLayout>-->
</f:content>
</f:DynamicPageHeader>
</f:header>

<f:content>
<VBox>
<!--
<core:Fragment fragmentName="sap.ui.demo.basicTemplate.view.Fragments.TabbedControl" type="XML" />
!-->
</VBox>
</f:content>
</f:DynamicPage>
</core:FragmentDefinition>

<!--basic_template/flaskr/static/view/Fragments/Master.fragment.xml-->
<core:FragmentDefinition
xmlns="sap.m"
xmlns:f="sap.f"
xmlns:core="sap.ui.core">
<f:DynamicPage toggleHeaderOnTitleClick="false" id="masterListAttachments">
<!-- DynamicPage Title -->
<f:title>
<f:DynamicPageTitle>
<f:heading>
<Title text="User profiles ({= ${DashboardAnalyticsModel>/results}.length })"/>
</f:heading>
</f:DynamicPageTitle>
</f:title>
<!-- DynamicPage Content -->
<f:content>
<VBox>
<SearchField liveChange="onSearchChange" class="searchBar sapUiTinyMarginTop sapUiSmallMarginBottom" width="90%" search="onSearch" />
<HBox alignItems="Start" justifyContent="End">
<Button press="onSort" icon="sap-icon://sort"/>
</HBox>
<List
id="attachmentsList"
itemPress="masterListItemSelected"
items="{DashboardAnalyticsModel>/results}"
growing="true"
growingThreshold="50"
growingScrollToLoad="true">

<CustomListItem type="Active">
<HBox alignItems="Start" justifyContent="SpaceBetween">
<HBox>
<VBox class="sapUiTinyMarginTop sapUiTinyMarginBottom">

<Label text="Name:" />
<Text class="titleText" text="{DashboardAnalyticsModel>name}" />

<Label class="sapUiTinyMarginTop" text="Date:" />
<Text text="{DashboardAnalyticsModel>datetime}" />

</VBox>
</HBox>
</HBox>
</CustomListItem>
</List>

</VBox>
</f:content>
</f:DynamicPage>
</core:FragmentDefinition>

<!--basic_template/flaskr/static/view/Fragments/Header.fragment.xml-->
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core"
>
<Bar>
<contentLeft>
<Button class="sapUiMediumMarginBegin sapUiMediumMarginEnd" icon="sap-icon://nav-back" press="onNavBack" />
<Select>
</Select>
</contentLeft>
<contentMiddle>
</contentMiddle>
<contentRight>
</contentRight>
</Bar>
</core:FragmentDefinition>

And even the individual tile rows.
<!--basic_template/flaskr/static/view/Fragments/Operations.fragment.xml-->
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core">
<VBox renderType="Bare">
<HBox alignItems="Start" justifyContent="SpaceBetween" wrap="Wrap">
<VBox wrap="Wrap" >
<HBox wrap="Wrap" width="100%">
<VBox class="sapUiSmallMarginEnd">
<HBox>
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="Dashboards" subheader="Analytics"
press="press('DashboardAnalytics')">
<TileContent>
<NumericContent value="1" icon="sap-icon://multiple-line-chart" />
</TileContent>
</GenericTile>
</HBox>
</VBox>
</HBox>

</VBox>

</HBox>
</VBox>
</core:FragmentDefinition>

Hopefully this has provided you with an overview of how to serve a simple Fiori application from your local client and update data models with calls to the Flask back end. In the next part of this series we will aim at productizing the base using Docker and other Flask tools so we can add more components down the road. For example, we will add OrientDB, an Open Source multi-modal database that provides a Document Store, Graph Engine and full text indexing with Lucene.

 
16 Comments
celle
Discoverer
0 Kudos
Hi Lynn,

Thanks for sharing this interesting article.
Unfortunately I do not seem to get access to your code in github.
Is there any other way to get the sample code ?
I would love to see how you get Flask working with UI5.

Thanks,

Marcel
LynnS1
Advisor
Advisor
0 Kudos
Hi Marcel,

 

Apologies the link didn't work. How about this one?
former_member596519
Participant
0 Kudos
Hello Lynn,

really interesting articles! I am looking forward to implement this on my own!

Best regards,

 

Aleks

 
celle
Discoverer
0 Kudos
Hi Lynn,

Your link works now !

I use bottle.py (similar to Flask) in combination with UI5, but I am interested to see how you did this.

I also look forward to part 2.

Many thanks for sharing this !

Kind regards,

 

Marcel
Srdjan
Product and Topic Expert
Product and Topic Expert
0 Kudos
Why this restriction:

  • You don’t have connections to ABAP or HANA ?


Flask works nice with both ABAP and HANA, using respective SAP Open Source connectors:

See https://blogs.sap.com/2013/01/29/rest-prototyping-with-python/
LynnS1
Advisor
Advisor
Hi Srdjan,

Thanks for the Links! I did not refer to a technical restriction but rather one of personal resources. Not everyone knows ABAP or HANA. I was new to SAP with no technical skill and no access to ABAP or HANA so learned Python since it's free.

But of course if you have ABAP and HANA, then nothing to stop you from still playing with Flask there too.
Snornn
Advisor
Advisor
0 Kudos
Great article, and a great starting point.  I've extended your example to use an oData back end provisioned by PySlet (and SQLLITE) which is the traditional oData v2 standard and I ran into the usual CORS issue which I have fixed using the proxy method.  Rather than a single tile I have used multiple tiles within panels calling a generic tile function.  I'm currently struggling with annotations and updates.

Regards

Justin
LynnS1
Advisor
Advisor
0 Kudos
Hi Justin,

That's awesome! If you want to share a link to it, please do. Otherwise would be great to catch up and see what can be done together!
0 Kudos
Hi Lynn,

I just copy pasted your project(download from git) into a new Python project, it ran into multiple errors.

 



 

Please let me know if I have followed correct procedure.

Please let me know in which perspective I have to copy the python project.

Also, please help in understanding on how to deploy the python project into SAP system to run as SAPUI5/Fiori Application.

 

Thanks& Regards,

Krishna

 
0 Kudos
Hi Lynn,

I got the answer for the error.

 

I have to inform the flask - to use 'Static' folder as template in file _init_.py

app = Flask(__name__,template_folder='static')

With, this I got the output.

 

I am still looking for the information on how to deploy the python project into SAP system to run as SAPUI5/Fiori Application. Please help me on this / guide me to relevant study links.

 

Thanks and Regards,

Krishna

 
LynnS1
Advisor
Advisor
0 Kudos
Hi Krishna,

Sorry for the late reply to your post and glad that you were able to solve this on your own. Well done :).

In terms of serving this as part of a Fiori Application, we will have to set up your Flask service behind an HTTP server from which you can set up a destination in SCP Cockpit. The image below shows the desinations tab in SCP cockpit and two services which you'll see in the NeoApp json file...

As shown here...



 

And then in the actual code making a call to the service here...

somnath
Active Participant
0 Kudos
Hello Lynn Scheinman,

This is really an interesting blog post and I am new to the python/flask world so facing a bit of challenge in following this post end to end.

I am stuck simply at,
"sap.ui.demo.basicTemplate": "{{ url_for('static', filename='.')}}"}'

Let me tell you what my steps were:

  1. Cloned the application from git hub

  2. created a virtual environment and installed all the dependencies

  3. Now stuck, as no index.html file been cloned from, so copied from this blog post and put it into the static folder

  4. Obviously, I had to mention the template folder as 'static' in __init__.py file ( thanks to

    sapcon.r for sharing this trick).



  5. Next when everything seems OK executed as
    python run.py​


  6. It failed with the below error message





  • File "C:\Users\Somnath\Desktop\dev\FioriFlask\venv\Lib\site-packages\werkzeug\routing.py", line 2305, in build



    raise BuildError(endpoint, values, method, self)




werkzeug.routing.BuildError: Could not build url for endpoint ' static' with values ['filename']. Did you mean 'static' instead?

and not sure how to fix this, my understanding is, here  sap.ui.demo.basicTemplate 
will be the namespace for this ui5 app where static folder will be working like webeapp.

If you kindly suggest how to fix this issue , will be a great help for me.

Thank you. Somnath
somnath
Active Participant
0 Kudos
Hi Hello Lynn Scheinman,

 

After doing certain trial and error I did finally with a below small change:
data-sap-ui-resourceroots='{
"sap.ui.demo.basicTemplate": "../static"
}'

And it worked!! I am not seriously confident if this is the right thing that I have done or was just a fluke! Still thought to share with this community in case someone like me struggling..

  • Thanks, Som

LynnS1
Advisor
Advisor
Hi Somnath,

Thank you for checking out the blog and trying everything out! I will take a deeper look but first thing I notice in the error message is that it says "Could not build url for endpoint ' static' with values...". I could be wrong but it looks like there might be a leading space before static. If that is not the case, I will take another look and try to repeat the error. I hope this can wait until the following week though?

Best,

Lynn
somnath
Active Participant
0 Kudos

Hi  Lynn,

First of all, thanks a lot that you checked this question and took the time to answer this 🙂

Yes, just spot on! This space was the problem and now it works fine.

Just a curiosity, the approach I tried, also worked, so is that an acceptable solution from a flask/python perspective, or this is not recommended. If you kindly share your thoughts.

"sap.ui.demo.basicTemplate": "../static"

Many Thanks!

Somnath

LynnS1
Advisor
Advisor
0 Kudos
Hi Somnath,

Yes, absolutely an acceptable approach. The leading .. means it will use the parent folder as root. Just as a note, there are some more stable and scalable methods to run UI5 from your client since I wrote this blog a while back. I also use FastAPI instead of Flask as it has a lot of nice built in features and runs on an async server which allows streaming more easily than Flask. If you're interested and have access to our SAP github tools, please find it here: D063195/graph-explorer (tools.sap).

Best regards,

Lynn