Commit 28fca716 authored by Nelso Jost's avatar Nelso Jost

FIX: Refactoring and improving documentation

parent ca6179bc
venv/
.venv/
*.swp
*.autosave
*.sqlite
*.pyc
datalog.csv
......
......@@ -3,6 +3,7 @@ BOARD_ID := 2
PORT := 8000
SERVER := nginx-gunicorn
HOST := localhost
VENV := .venv
all: help
......@@ -46,52 +47,52 @@ setup: venv initdb
venv: clean-venv
@ echo "-------------------------------------------------------"
virtualenv -v --python='${PY}' venv
@ echo "Virtualenv with interpreter '${PY}' was created at venv/"
virtualenv -v --python='${PY}' ${VENV}
@ echo "Virtualenv with interpreter '${PY}' was created at ${VENV}/"
@ echo "-------------------------------------------------------"
venv/bin/pip3 install --upgrade pip
${VENV}/bin/pip3 install --upgrade pip
@ echo "-------------------------------------------------------"
venv/bin/pip3 install -r requirements.pip
${VENV}/bin/pip3 install -r requirements.pip
@ echo "-------------------------------------------------------"
@ echo "Virtualenv is ready at venv/!"
@ echo "Virtualenv is ready at ${VENV}/!"
@ echo " "
@ echo "TOTAL SIZE: "
@ du -sh venv
@ du -sh ${VENV}
clean-venv:
rm -rf venv
rm -rf ${VENV}
initdb:
@ echo "-------------------------------------------------------"
venv/bin/python manage.py initdb
${VENV}/bin/python manage.py initdb
run:
@ echo "-------------------------------------------------------"
venv/bin/python manage.py runserver --host ${HOST} --port 5000
${VENV}/bin/python manage.py runserver --host ${HOST} --port 5000
shell:
venv/bin/ipython manage.py shell
${VENV}/bin/ipython manage.py shell
fakelog:
venv/bin/python tests/fakemeteorolog.py ${BOARD_ID}
${VENV}/bin/python tests/fakemeteorolog.py ${BOARD_ID}
tree:
tree . -I "venv|__pycache__|*.pyc"
tree . -I "${VENV}|__pycache__|*.pyc"
deploy:
@ echo "-------------------------------------------------------"
mkdir -p production/nginx-gunicorn/logs
sudo venv/bin/python production/nginx-gunicorn/deploy.py ${PORT}
sudo ${VENV}/bin/python production/nginx-gunicorn/deploy.py ${PORT}
undeploy:
@ echo "-------------------------------------------------------"
sudo venv/bin/python production/nginx-gunicorn/undeploy.py
sudo ${VENV}/bin/python production/nginx-gunicorn/undeploy.py
clean-logs-nginx:
rm -rf production/nginx-gunicorn/logs
mkdir -p production/nginx-gunicorn/logs
clean-cache:
clean-all: clean-venv
rm -rf app/db.sqlite
py3clean app
......
......@@ -40,10 +40,10 @@ Hit CTRL + C to cancel the execution.
$ make deploy # register site on nginx and launch a gunicorn process via supervisor
```
Check under http://localhost or the registered domain. Default port: 8000.
* Alternativaly, you can specify another port with:
```
$ make deploy PORT=9090
```
* Alternativaly, you can specify another port:
```
$ make deploy PORT=9090
```
* Undo the production deployment and quit related processeses with:
```
......@@ -53,6 +53,7 @@ $ make undeploy
### 3. Additional commands
* Explore the database (see [SQLAlchemy](http://www.sqlalchemy.org/)) with:
```
$ make shell
venv/bin/ipython manage.py shell
......
#-------------------------------------------------------------------------------
# Author: Nelso G. Jost (nelsojost@gmail.com)
#
# License: BEERWARE (http://en.wikipedia.org/wiki/Beerware)
#
# Purpose: Provides all basic global objects, such 'db' (the database), and
# the essential application factory, create_app().
#
# This file makes the directory app/ to behave like a Python package.
# Author: Nelso G. Jost <nelsojost@gmail.com> Copyright(C) 2015
# License: AGPL
# Purpose: Provides the application factory and basic initializations.
#-------------------------------------------------------------------------------
'''
Instead of initializing the application by creating the Flask object globally, the goal here is to do it on-the-fly via the factory function create_app().
This approach has several advantages:
* Avoid Python's circular import pitfalls, since package modules are
loaded only on the scope of create_app();
* Encorages use of Flask's blueprints -- actually, those are needed here;
* Allows creation of multiple instances -- better for unit testing.
'''
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
......@@ -17,6 +24,9 @@ db = SQLAlchemy()
bootstrap = Bootstrap()
def create_app():
''' Creates the Flask application object and register configurations,
blueprints and related global objects for it.
'''
app = Flask(__name__)
from . import config
......
#-------------------------------------------------------------------------------
# Author: Nelso G. Jost (nelsojost@gmail.com)
#
# License: BEERWARE (http://en.wikipedia.org/wiki/Beerware)
#
# Purpose: Provide a bunch of global settings for the project and application.
# Those settings will be applied to the app instance and otherwise
# used along all the project.
# Author: Nelso G. Jost <nelsojost@gmail.com> Copyright(C) 2015
# License: AGPL
# Purpose: Holds application-wide settings.
#-------------------------------------------------------------------------------
import os
basedir = os.path.abspath(os.path.dirname(__file__))
......
This diff is collapsed.
......@@ -2,6 +2,7 @@
{% block content %}
{{ super() }}
<!--
<div class="container">
<div class="page-header">
<form method='POST'>
......@@ -9,5 +10,5 @@
{{ form.board_choices(onchange="this.form.submit()") }}
</form>
</div>
</div>
</div> -->
{% endblock %}
#-------------------------------------------------------------------------------
# Author: Nelso G. Jost (nelsojost@gmail.com)
#
# License: BEERWARE (http://en.wikipedia.org/wiki/Beerware)
#
# Purpose: All application routes are defined in here. Following the MVC design,
# routes defines actions by calling specific functions (controllers)
# which return specific rendered HTML (views).
#
# CONVENTION: Here we call a view every function decorated by routes
# and that returns some kind of data (HTML or JSON).
#
# This project make use of Flask blueprints technology. All routes
# are defined on the main Blueprint object, which is not a Flask app.
# The blueprint will then be applied to an app instance by the factory
# function app.create_app(), on which the routes will have a real,
# solid meaning.
#
# Among other things, this technique together with the application
# factory function, avoids Python circular imports -- since this file
# will actually import stuff from app/__init__.py and that ile needs
# to import the blueprint from here in order to build the app.
# Author: Nelso G. Jost <nelsojost@gmail.com> Copyright(C) 2015
# License: AGPL
# Purpose: Provide all the routes for the web site.
#-------------------------------------------------------------------------------
'''
All application routes are defined in here. Following the MVC design,
routes defines actions by calling specific functions (controllers)
which return specific rendered HTML (views).
CONVENTION: Here we call a view every function decorated by routes
and that returns some kind of data (HTML or JSON).
This project make use of Flask blueprints technology. All routes
are defined on the main Blueprint object, which is not a Flask app.
The blueprint will then be applied to an app instance by the factory
function app.create_app(), on which the routes will have a real,
solid meaning.
Among other things, this technique together with the application
factory function, avoids Python circular imports -- since this file
will actually import stuff from app/__init__.py and that ile needs
to import the blueprint from here in order to build the app.
'''
from . import db
from .models import Board, Sensor, RawSensorData
......@@ -113,11 +114,8 @@ def check_need_refresh():
@main.route('/', methods=['GET', 'POST'])
def index():
''' GET: provide HTML for selection of registered boards
POST: redirect to view_board_data on combobox changes
As for now this page is only meant to provide quick access to all
registered boards.
''' GET --> base.html (allows board selection)
POST --> redirect to view_board() passing selected board id
'''
if request.method == 'POST':
return redirect(url_for('.view_board',
......@@ -127,7 +125,7 @@ def index():
form.board_choices.choices = [(-1, '')] + get_board_choices()
update_need_refresh()
return render_template('base.html', config=config, form=form)
return render_template('index.html', config=config, form=form)
@main.route('/board/<int:id>', methods=['GET', 'POST'])
def view_board(id):
......
......@@ -3,15 +3,72 @@
# License: AGPL
# Purpose: Launch the application or execute extra management operations.
#-------------------------------------------------------------------------------
'''
This script provides a series of commands to launch and manage the web app.
Usage: $ python manage.py [command]
------
Where [command] can be:
runserver : launches Flask's werkzeug development server (DEBUG=True).
See on http://localhost:5000
$ python manage.py runserver --help # for extra options
initdb : (re)create the database (models and initial population)
shell : launch an interactive session useful to explore the database.
The session will have all the names on SHELL_GLOBALS made
available -- which includes all database tables.
Consult SQL Alchemy and app/models.py for reference on
database operations.
Example of session (assuming initdb() results):
-----------------------------------------------
>>> boards.ls()
[<Board id=1 nickname="Pezzi" sensor_count=4>,
<Board id=2 nickname="CAp" sensor_count=4>]
>>> b = boards.get(1)
>>> b
<Board id=1 nickname="Pezzi" sensor_count=4>
>>> b.sensors.all()
[<Sensor id=1 board_id=1 nickname='DHT22_TEMP' data_count=>7903,
<Sensor id=2 board_id=1 nickname='DHT22_AH' data_count=7903>,
<Sensor id=3 board_id=1 nickname='BMP085_PRESSURE' data_count=7903>,
<Sensor id=4 board_id=1 nickname='LDR' data_count=7903>]
>>> ldr = b.get_sensor('LDR')
>>> ldr
<Sensor id=4 board_id=1 nickname='LDR' data_count=7903>
>>> ldr.board # backref
<Board id=1 nickname="Pezzi" sensor_count=4>
>>> b = boards.add(nickname='test')
>>> b
<Board id=None nickname="test" sensor_count=0>
>>> b # accesing the record again will flush()
<Board id=3 nickname="test" sensor_count=0>
>>> commit() # write changes to database
>>> b.nickname = 'new_test'
>>> b # the change is apparent (still NOT on db)
<Board id=3 nickname="new_test" sensor_count=4>
# if session ends now, commit() will be called automatically
# call rollback() before end of the session to avoid writing
# changes permanently on the database
'''
from flask.ext.script import Manager, Shell
from app import create_app, db
from flask.ext.script import Manager, Shell
from app.config import DEFAULT_SENSORS
from app.models import *
from app.models import Board, Sensor, RawSensorData
# nice plural and lowercased names for better semantics on db operations
for m in list_models:
globals()[m.__tablename__] = m
boards, sensors, rawsensordata = Board, Sensor, RawSensorData
SHELL_GLOBALS = ['app', 'db', 'boards', 'sensors', 'rawsensordata', 'p',
'Board', 'Sensor', 'RawSensorData']
# short nicknames for commom db.session operations
for n in ('commit', 'rollback', 'flush'):
globals()[n] = getattr(db.session, n)
import csv
import os
......@@ -22,42 +79,29 @@ manager = Manager(app)
@manager.command
def initdb():
from app.config import DEFAULT_SENSORS
''' (re)creates all the database from models and populate it.
Inicial population includes two boards with app.config.DEFAULT_SENSORS:
* nickname='Pezzi' : imports 'data/26-Fev-2013_-_04-Mar-2013.log'
* nickname='CAp' : empty raw sensor data
'''
db.drop_all()
db.create_all()
db.session.add(Board(nickname='Pezzi'))
db.session.add(Board(nickname='CAP'))
db.session.commit()
for board_name in ('Pezzi', 'CAP'):
board = boards.get(board_name)
for sensor in DEFAULT_SENSORS:
Sensor.add_from_json(json_dict=sensor, board_id=board.id)
db.session.commit()
for board_nickname in ('Pezzi', 'CAP'):
Board.add(nickname=board_nickname, sensors=DEFAULT_SENSORS)
CSV_FILENAME = os.path.join('data', '26-Fev-2013_-_04-Mar-2013.log')
boards.get('Pezzi').import_csv_sensor_data(
filename=os.path.join('data', '26-Fev-2013_-_04-Mar-2013.log'),
columns=['datetime', 'DHT22_TEMP', 'DHT22_AH', '#',
'BMP085_PRESSURE', 'LDR'],
delimiter='\t')
with open(CSV_FILENAME) as f:
reader = csv.reader(f, delimiter='\t')
temp_sensor = boards.get_sensor('Pezzi', 'DHT22_TEMP')
ah_sensor = boards.get_sensor('Pezzi', 'DHT22_AH')
press_sensor = boards.get_sensor('Pezzi', 'BMP085_PRESSURE')
lum_sensor = boards.get_sensor('Pezzi', 'LDR')
for i, row in enumerate(reader):
if i == 0: continue
temp_sensor.add_data(datetime = row[0], value = row[1])
ah_sensor.add_data(datetime = row[0], value = row[2])
press_sensor.add_data(datetime = row[0],value = row[4])
lum_sensor.add_data(datetime = row[0], value = row[5])
db.session.commit()
db.session.commit()
manager.add_command("shell", Shell(make_context=
lambda: {k: v for k, v in globals().items() if k in SHELL_GLOBALS}))
lambda: {k: v for k, v in globals().items()}))
if __name__ == '__main__':
manager.run()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment