{"id":854,"date":"2018-03-10T22:58:17","date_gmt":"2018-03-10T22:58:17","guid":{"rendered":"http:\/\/www.codeastar.com\/?p=854"},"modified":"2019-02-10T15:32:28","modified_gmt":"2019-02-10T15:32:28","slug":"flask-easy-web-app-python","status":"publish","type":"post","link":"https:\/\/www.codeastar.com\/flask-easy-web-app-python\/","title":{"rendered":"Easy Weather Forecast Flask Web App with Python"},"content":{"rendered":"
We have tried to code an Easy Weather Forecast Tool<\/a> with Python. Yes, it is fast and easy, but it is just not that convenient to run anything on a command prompt. It is so…. 80s. It would be better if we can make a single-page Flask web app. Then we can run the weather forecast tool on a browser in anywhere. Isn’t it cool? Yeah! Okay, requirement is defined, let’s build our project!<\/p>\n <\/p>\n Other than the “single-page web app” requirement, we have another hidden requirement as well, it is “fast and easy” (who doesn’t like fast and easy?). Luckily we are in the Python world, so “fast and easy” is never a miracle to us. Before we go further in our development, let’s use pip<\/a> to install a Python module, the “pipenv<\/a>” for our development environment.<\/p>\n Pipenv is not a functional module in our project, but an officially recommended Python packaging tool from\u00a0Python.org<\/a>. We use it before we start our project. The pipenv is a combination of virtual environment and pip. We can create a virtual environment from it, then install \/ uninstall modules within the environment and *automatically*<\/strong>\u00a0maintain the list of the modules.<\/p>\n After the installation of pipenv, we can set up a directory for our project. Let’s call it “ezw” then (stands for EZ Weather forecast, okay, I admit it, I am a huge wrestling mask, EZW! EZW! EZW!).<\/p>\n And we initialize a Python 3 environment.<\/p>\n (Note: for Mac users with Anaconda Python, when you face “dyld: Library not loaded” error, you may need to run Anaconda Distribution of virtualenv first)<\/p>\n [reference: Anaconda’s community group<\/a>]<\/p>\n When the virtual environment is set,\u00a0we can install following modules with pipenv<\/em>:<\/p>\n Then we will find Pipfile<\/em> and Pipfile.lock<\/em>\u00a0files generated in our working directory. The Pipfile<\/em>\u00a0is the list of our dependencies. Thus we can bring the Pipfile<\/em> to a new location and install the\u00a0dependencies, by typing:<\/p>\n That’s it! And the Pipfile.lock<\/em> is the file for storing version and environment details.<\/p>\n When we are ready, we can run:<\/p>\n to start our virtual environment with required libraries.<\/p>\n Okay, we are now going to make our web app under the Model-View-Controller (MVC<\/a>) pattern in the Flask framework. First, we create a model file (let’s name it “ezw_model.py<\/em>“) under our working directory. A model file is a place where we define an data object for our web application. In our case, we define the weather report object here.<\/p>\n Our weather report model is made of date, maximum and minimum temperatures, chance of raining, weather icon and the summary.<\/p>\n We then move to create our controller files, let name the first file as “ezw_controller.py<\/em>“. The source code is basically the one we did on our Easy Weather Forecast Tool<\/a> project. This time, we just put the outputs in an array of weather report models, instead of printing them directly.<\/p>\n After that, we create an application controller, “ezw_app.py<\/em>“. It is the backbone of our ezw web app in the flask framework. Other than the flask<\/em> module, you may notice we have imported a flask_cors<\/em> module there. Since we are making a web app with API, there are a lot of HTTP requests sending in and out of our app.\u00a0It would be a pain in the <you know what><\/em>, if those requests cannot be made between different domains. Thus we enable CORS<\/a> (Cross-Origin Resource Sharing) in flask to handle this issue.<\/p>\n We create 2 routes in our app controller, one is the index route which displays the weather query template of our ezw web app. Another route is the POST request handler for calling up our ezw_controller.<\/p>\n Now we have the Model and the Controllers, it is time to work on the View. From the ezw_app.py<\/em>, you may spot 2 templates there, index.html<\/em> and reports.html<\/em>. The index<\/em> template is the view of our weather query form, while the reports<\/em> template is the view of our ezw report.<\/p>\n Please note that Flask<\/em> is using Jinja2<\/em> template engine, you can visit this link<\/a> for more Jinja2 details. And our “views” are stored under “templates” sub-directory.<\/p>\n Thus our web app file structure should look like:<\/p>\n In our index.html<\/em> query form view, we are using following JavaScript and CSS libraries:<\/p>\n So we import those links in the\u00a0index.html<\/em> view:<\/p>\n And create our query form with Datepicker:<\/p>\n Add JavaScript here to interact with our Flask controller using AJAX.<\/p>\n The UI should look like:<\/p>\n <\/p>\n The view of our query form is ready, just don’t click the “Get the weather” button at this moment. Because, we just haven’t made the view of weather reports yet.<\/p>\n Our weather report will contain: a date, max and min temperatures, a summary and a weather icon. We can get all the information from our controller, “ezw_controller.py<\/em>” file. What we need to do next, is putting all those information in our view, the “reports.html<\/em>“.<\/p>\n The\u00a0“reports.html<\/em>” view is a loop of division element showing weather information in\u00a0Jinja2<\/em> syntax.<\/p>\n The code above is pretty straight forward which displays information from our ezw model. But, you may discover one thing different from other programming languages there.<\/p>\n After building the weather report view, we have finished all the MVC components of our Flask web app.<\/p>\n The time is now, we can run our Flask web app here. But first, we need to set the Dark Sky API key as an environment variable.<\/p>\n Then, we can type “python ezw_appy.py” to start our application<\/p>\n Open a web browser and go to “http:\/\/127.0.0.1:5000\/”. Our Easy Weather Forecast Tool is now online (in local network :]] ).<\/p>\n <\/p>\n Try filling up the query form, then press “Get the weather” to receive the reports in our single-page web app.<\/p>\n <\/p>\n Hope you all can enjoy this EZW web app. You can build extra features on it, like an auto-fill function on location or add more fields on the report view model. Feel free to try and explore the code, and make your own customized weather forecast app.<\/p>\nPlanning Stage<\/h3>\n
>pip install pipenv\n<\/pre>\n
Start our project with pipenv<\/h3>\n
>mkdir ezw\n>cd ezw\n<\/pre>\n
>pipenv --three\n<\/pre>\n
>conda install virtualenv\n<\/pre>\n
\n
>pipenv install flask\n>pipenv install flask-cors\n>pipenv install requests\n>pipenv install geopy<\/pre>\n
>pipenv install\n<\/pre>\n
>pipenv shell\n<\/pre>\n
The Model, the View and the Controller in the Flask<\/h3>\n
class WeatherReport():\n def __init__(self, date, max_temperature, \n min_temperature, summary, raining_chance, \n icon):\n self.date = date\n self.max_temperature = max_temperature\n self.min_temperature = min_temperature\n self.summary = summary \n self.raining_chance = raining_chance\n self.icon = icon \n<\/pre>\n
from ezw_model import WeatherReport\nfrom geopy.geocoders import Nominatim\nfrom datetime import datetime,timedelta\nimport requests, os\n\nDARK_SKY_API_KEY = os.environ['DARK_SKY_KEY']\noption_list = \"exclude=currently,minutely,hourly,alerts&units=si\"\n\nclass EZWController:\n \n def getLocation(self, input_location):\n location = Nominatim().geocode(input_location, language='en_US') \n return location\n \n def getWeatherReports(self, data, location):\n date_from = data['date_from']\n date_to = data['date_to']\n\n d_from_date = datetime.strptime(date_from , '%Y-%m-%d')\n d_to_date = datetime.strptime(date_to , '%Y-%m-%d')\n delta = d_to_date - d_from_date\n\n latitude = str(location.latitude)\n longitude = str(location.longitude)\n\n weather_reports = []\n\n for i in range(delta.days+1):\n new_date = (d_from_date + timedelta(days=i)).strftime('%Y-%m-%d')\n search_date = new_date+\"T00:00:00\"\n response = requests.get(\"https:\/\/api.darksky.net\/forecast\/\"+DARK_SKY_API_KEY+\"\/\"+latitude+\",\"+longitude+\",\"+search_date+\"?\"+option_list)\n json_res = response.json()\n report_date = (d_from_date + timedelta(days=i)).strftime('%Y-%m-%d %A')\n unit_type = '\u00b0F' if json_res['flags']['units'] == 'us' else '\u00b0C'\n min_temperature = str(json_res['daily']['data'][0]['apparentTemperatureMin'])+unit_type\n max_temperature = str(json_res['daily']['data'][0]['apparentTemperatureMax'])+unit_type\n summary = json_res['daily']['data'][0]['summary']\n icon = json_res['daily']['data'][0]['icon']\n precip_type = None\n precip_prob = None\n raining_chance = None\n if'precipProbability' in json_res['daily']['data'][0] and 'precipType' in json_res['daily']['data'][0]:\n precip_type = json_res['daily']['data'][0]['precipType']\n precip_prob = json_res['daily']['data'][0]['precipProbability']\n if (precip_type == 'rain' and precip_prob != None):\n precip_prob *= 100\n raining_chance = \"Chance of rain: %.2f%%\" % (precip_prob)\n\n ezw_wr = WeatherReport(report_date, max_temperature,min_temperature, \n summary, raining_chance, icon) \n\n weather_reports.append(ezw_wr)\n\n return weather_reports\n<\/pre>\n
from flask import Flask, render_template, request\nfrom flask_cors import CORS\nfrom ezw_controller import EZWController\n\napp = Flask(__name__)\nCORS(app)\n<\/pre>\n
@app.route(\"\/\")\ndef index():\n return render_template('index.html')\n\n@app.route('\/ezw', methods=['POST', 'GET'])\ndef ezw():\n if request.method == 'POST': \n data = request.json\n input_location = data['location']\n\n ezw = EZWController()\n\n geo_location = ezw.getLocation(input_location)\n if geo_location == None:\n ezw_address = \"Unknown location\"\n report_template = render_template('reports.html', weather_address=ezw_address)\n return report_template \n \n ezw_address = geo_location.address \n ezw_reports = ezw.getWeatherReports(data, geo_location)\n\n report_template = render_template('reports.html', weather_address=ezw_address, weather_reports=ezw_reports)\n\n return report_template \n\nif __name__ == '__main__':\n app.run(debug=False,host='0.0.0.0') \n<\/pre>\n
View the View<\/h3>\n
\\ezw\n | ezw_app.py\n | ezw_model.py\n | ezw_controller.py\n \\templates\n | index.html\n | reports.html\n<\/pre>\n
\n
<head>\n <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jquery\/3.3.1\/jquery.min.js\"><\/script>\n <link id=\"bs-css\" href=\"https:\/\/netdna.bootstrapcdn.com\/bootstrap\/3.0.3\/css\/bootstrap.min.css\" rel=\"stylesheet\"> \n <link id=\"bsdp-css\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/bootstrap-datepicker\/1.7.1\/css\/bootstrap-datepicker3.min.css\" rel=\"stylesheet\"> \n <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/bootstrap-datepicker\/1.7.1\/js\/bootstrap-datepicker.min.js\"><\/script> \n <link id=\"weather-css\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/weather-icons\/2.0.9\/css\/weather-icons.min.css\" rel=\"stylesheet\">\n<\/head>\n<\/pre>\n
<div class=\"row\"> \n <div class=\"col-md-6\"> \n <label style=\"width: 100%;\">Date range <div id=\"ezw-dateinput\"> \n <div class=\"input-daterange input-group\" id=\"datepicker\"> \n <input type=\"text\" class=\"input-sm form-control\" name=\"date_from\" id=\"date_from\" \/> \n <span class=\"input-group-addon\">to<\/span> \n <input type=\"text\" class=\"input-sm form-control\" name=\"date_to\" id=\"date_to\" \/> \n <\/div><\/div> \n <\/label> \n <\/div> \n<\/div> \n<div class=\"row\"> \n <div class=\"col-md-6\"> \n <label style=\"width: 100%;\">Location <input type=\"text\" class=\"input-sm form-control\" id=\"location\" name=\"location\" required> <\/label> \n <\/div> \n<\/div> \n<div class=\"row\"> \n <div class=\"col-md-6\"> \n <button type=\"button\" class=\"btn btn-primary\" id=\"gtw\">Get the weather<\/button> \n <\/div> \n<\/div>\n<div class=\"row\"> \n <div class=\"col-md-12\" id=\"result\"><\/div> \n<\/div>\n<\/pre>\n
<script> \n $(document).ready(function () { \n $('#ezw-dateinput .input-daterange').datepicker({ \n format: \"yyyy-mm-dd\", \n todayHighlight: true }); \n \n $('#gtw').click( function() { \n var data ={}; data['location'] = $('#location').val(); \n data['date_from'] = $('#date_from').val(); \n data['date_to'] = $('#date_to').val(); \n \n $('#result').empty(); \n \n $.ajax({ \n type : \"POST\", \n url : \"{{ url_for('ezw') }}\", \n data: JSON.stringify(data), \n contentType: 'application\/json;charset=UTF-8', \n success: function(result) { \n $('#result').html(result); \n } \n }); \n }); \n }); \n<\/script> \n<\/pre>\n
Build the weather report<\/h3>\n
<div class=\"row\"> \n <div class=\"col-md-6\"> \n <h4>Location: {{ weather_address }}<\/h4> \n <\/div> \n<\/div> \n{% for report in weather_reports %} \n<div class=\"row\"> \n <div class=\"col-md-6\"> <div class=\"well\"> <div class=\"row\"> \n <div class=\"col-xs-9\"> \n <b>Date:<\/b> {{ report.date }}<br\/> \n <b>Max Tempertaure:<\/b> {{ report.max_temperature }}<br\/> \n <b>Min Tempertaure:<\/b> {{ report.min_temperature }}<br\/> \n <b>Summary:<\/b> {{ report.summary }}<br\/> \n {% if report.raining_chance %} \n <b>Chance of rain:<\/b> {{ report.raining_chance }}<br\/> \n {% endif %} \n <\/div> \n <div class=\"col-xs-3\"> \n <h1> {% if report.icon == \"clear-day\" %} <i class=\"wi wi-day-sunny\"><\/i> \n {% elif report.icon == \"clear-night\" %} <i class=\"wi wi-night-clear\"><\/i> \n {% elif report.icon == \"rain\" %} <i class=\"wi wi-rain\"><\/i> \n {% elif report.icon == \"snow\" %} <i class=\"wi wi-snow\"><\/i> \n {% elif report.icon == \"sleet\" %} <i class=\"wi wi-sleet\"><\/i> \n {% elif report.icon == \"wind\" %} <i class=\"wi wi-windy\"><\/i> \n {% elif report.icon == \"fog\" %} <i class=\"wi wi-fog\"><\/i> \n {% elif report.icon == \"cloudy\" %} <i class=\"wi wi-cloudy\"><\/i> \n {% elif report.icon == \"partly-cloudy-day\" %} <i class=\"wi wi-day-cloudy\"><\/i> \n {% elif report.icon == \"partly-cloudy-night\" %} <i class=\"wi wi-night-partly-cloudy\"><\/i> \n {% endif %} <\/h1> \n <\/div> \n <\/div> <\/div> <\/div> \n<\/div> {% endfor %}\n<\/pre>\n
\nIt is because we can simply use if\/else statements to perform\u00a0the switch-case logic. No kidding, it is the official answer from\u00a0Guido van Rossum<\/a>, the father of Python.<\/p>\nGentlemen, start your (Flask) engines<\/h3>\n
>export DARK_SKY_KEY=[your Dark Sky API key]\nor \n(for Windows OS)\n>set DARK_SKY_KEY=[your Dark Sky API key]\n<\/pre>\n
>python ezw_app.py\n * Running on http:\/\/127.0.0.1:5000\/\n<\/pre>\n
“Ta-Da!”<\/h3>\n
What have we learnt in this post?<\/h3>\n