Browse Source

Commit inicial.

master
Guilherme Capanema 7 years ago
commit
b1b53679b3
13 changed files with 477 additions and 0 deletions
  1. +14
    -0
      .gitignore
  2. +4
    -0
      MANIFEST.in
  3. +41
    -0
      interruptor/__init__.py
  4. +90
    -0
      interruptor/auth.py
  5. +41
    -0
      interruptor/db.py
  6. +45
    -0
      interruptor/interruptor.py
  7. +18
    -0
      interruptor/schema.sql
  8. +134
    -0
      interruptor/static/style.css
  9. +15
    -0
      interruptor/templates/auth/login.html.j2
  10. +15
    -0
      interruptor/templates/auth/register.html.j2
  11. +24
    -0
      interruptor/templates/base.html.j2
  12. +23
    -0
      interruptor/templates/interruptor/index.html.j2
  13. +13
    -0
      setup.py

+ 14
- 0
.gitignore View File

@ -0,0 +1,14 @@
venv/
*.pyc
__pycache__/
instance/
.pytest_cache/
.coverage
htmlcov/
dist/
build/
*.egg-info/

+ 4
- 0
MANIFEST.in View File

@ -0,0 +1,4 @@
include interruptor/schema.sql
graft interruptor/static
graft interruptor/templates
global-exclude *.pyc

+ 41
- 0
interruptor/__init__.py View File

@ -0,0 +1,41 @@
import os
from flask import Flask
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY='dev',
DATABASE=os.path.join(app.instance_path, 'interruptor.sqlite'),
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.from_mapping(test_config)
# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
# setup GPIO
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT)
from . import db
db.init_app(app)
from . import auth
app.register_blueprint(auth.bp)
from . import interruptor
app.register_blueprint(interruptor.bp)
app.add_url_rule('/', endpoint='index')
return app

+ 90
- 0
interruptor/auth.py View File

@ -0,0 +1,90 @@
import functools
from flask import (
Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash
from interruptor.db import get_db
bp = Blueprint('auth', __name__, url_prefix='/auth')
@bp.route('/register', methods=('GET', 'POST'))
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
if not username:
error = 'Username is required.'
elif not password:
error = 'Password is required.'
elif db.execute(
'SELECT id FROM user WHERE username = ?', (username,)
).fetchone() is not None:
error = 'User {} is already registered.'.format(username)
if error is None:
db.execute(
'INSERT INTO user (username, password) VALUES (?, ?)',
(username, generate_password_hash(password))
)
db.commit()
return redirect(url_for('auth.login'))
flash(error)
return render_template('auth/register.html.j2')
@bp.route('/login', methods=('GET', 'POST'))
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
user = db.execute(
'SELECT * FROM user WHERE username = ?', (username,)
).fetchone()
if user is None:
error = 'Incorrect username.'
elif not check_password_hash(user['password'], password):
error = 'Incorrect password.'
if error is None:
session.clear()
session['user_id'] = user['id']
return redirect(url_for('index'))
flash(error)
return render_template('auth/login.html.j2')
@bp.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
@bp.before_app_request
def load_logged_in_user():
user_id = session.get('user_id')
if user_id is None:
g.user = None
else:
g.user = get_db().execute(
'SELECT * FROM user WHERE id = ?', (user_id,)
).fetchone()
def login_required(view):
@functools.wraps(view)
def wrapped_view(**kwargs):
if g.user is None:
return redirect(url_for('auth.login'))
return view(**kwargs)
return wrapped_view

+ 41
- 0
interruptor/db.py View File

@ -0,0 +1,41 @@
import sqlite3
import click
from flask import current_app, g
from flask.cli import with_appcontext
def get_db():
if 'db' not in g:
g.db = sqlite3.connect(
current_app.config['DATABASE'],
detect_types=sqlite3.PARSE_DECLTYPES
)
g.db.row_factory = sqlite3.Row
return g.db
def close_db(e=None):
db = g.pop('db', None)
if db is not None:
db.close()
def init_db():
db = get_db()
with current_app.open_resource('schema.sql') as f:
db.executescript(f.read().decode('utf8'))
@click.command('init-db')
@with_appcontext
def init_db_command():
"""Clear the existing data and create new tables."""
init_db()
click.echo('Initialized the database.')
def init_app(app):
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)

+ 45
- 0
interruptor/interruptor.py View File

@ -0,0 +1,45 @@
from flask import (
Blueprint, flash, g, redirect, render_template, request, url_for
)
from werkzeug.exceptions import abort
from interruptor.auth import login_required
from interruptor.db import get_db
import RPi.GPIO as GPIO
bp = Blueprint('interruptor', __name__)
@bp.route('/')
def index():
db = get_db()
status = db.execute(
'SELECT user_id, status, created'
' FROM status JOIN user ON status.user_id = user.id'
' ORDER BY created DESC LIMIT 1'
).fetchone()
return render_template('interruptor/index.html.j2', status=status)
@bp.route('/toggle')
@login_required
def toggle():
db = get_db()
status = db.execute(
'SELECT status'
' FROM status JOIN user ON status.user_id = user.id'
' ORDER BY created DESC LIMIT 1'
).fetchone()
if status['status']:
GPIO.output(17, GPIO.LOW)
else:
GPIO.output(17, GPIO.HIGH)
db.execute(
'INSERT INTO status (user_id, status)'
' VALUES (?, ?)',
(g.user['id'], not status['status'])
)
db.commit()
return redirect(url_for('interruptor.index'))

+ 18
- 0
interruptor/schema.sql View File

@ -0,0 +1,18 @@
DROP TABLE IF EXISTS user;
DROP TABLE IF EXISTS status;
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
);
CREATE TABLE status (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
status BOOLEAN NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user (id)
);
INSERT INTO STATUS (user_id, status) VALUES (1, 1);

+ 134
- 0
interruptor/static/style.css View File

@ -0,0 +1,134 @@
html {
font-family: sans-serif;
background: #eee;
padding: 1rem;
}
body {
max-width: 960px;
margin: 0 auto;
background: white;
}
h1, h2, h3, h4, h5, h6 {
font-family: serif;
color: #377ba8;
margin: 1rem 0;
}
a {
color: #377ba8;
}
hr {
border: none;
border-top: 1px solid lightgray;
}
nav {
background: lightgray;
display: flex;
align-items: center;
padding: 0 0.5rem;
}
nav h1 {
flex: auto;
margin: 0;
}
nav h1 a {
text-decoration: none;
padding: 0.25rem 0.5rem;
}
nav ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
nav ul li a, nav ul li span, header .action {
display: block;
padding: 0.5rem;
}
.content {
padding: 0 1rem 1rem;
}
.content > header {
border-bottom: 1px solid lightgray;
display: flex;
align-items: flex-end;
}
.content > header h1 {
flex: auto;
margin: 1rem 0 0.25rem 0;
}
.flash {
margin: 1em 0;
padding: 1em;
background: #cae6f6;
border: 1px solid #377ba8;
}
.post > header {
display: flex;
align-items: flex-end;
font-size: 0.85em;
}
.post > header > div:first-of-type {
flex: auto;
}
.post > header h1 {
font-size: 1.5em;
margin-bottom: 0;
}
.post .about {
color: slategray;
font-style: italic;
}
.post .body {
white-space: pre-line;
}
.content:last-child {
margin-bottom: 0;
}
.content form {
margin: 1em 0;
display: flex;
flex-direction: column;
}
.content label {
font-weight: bold;
margin-bottom: 0.5em;
}
.content input, .content textarea {
margin-bottom: 1em;
}
.content textarea {
min-height: 12em;
resize: vertical;
}
input.danger {
color: #cc2f2e;
}
input[type=submit] {
align-self: start;
min-width: 10em;
}

+ 15
- 0
interruptor/templates/auth/login.html.j2 View File

@ -0,0 +1,15 @@
{% extends 'base.html.j2' %}
{% block header %}
<h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Log In">
</form>
{% endblock %}

+ 15
- 0
interruptor/templates/auth/register.html.j2 View File

@ -0,0 +1,15 @@
{% extends 'base.html.j2' %}
{% block header %}
<h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Register">
</form>
{% endblock %}

+ 24
- 0
interruptor/templates/base.html.j2 View File

@ -0,0 +1,24 @@
<!doctype html>
<title>{% block title %}{% endblock %} - Interruptor</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
<h1>Interruptor</h1>
<ul>
{% if g.user %}
<li><span>{{ g.user['username'] }}</span>
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
<li><a href="{{ url_for('auth.register') }}">Register</a>
<li><a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}
</ul>
</nav>
<section class="content">
<header>
{% block header %}{% endblock %}
</header>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
</section>

+ 23
- 0
interruptor/templates/interruptor/index.html.j2 View File

@ -0,0 +1,23 @@
{% extends 'base.html.j2' %}
{% block header %}
<h1>{% block title %}Status{% endblock %}</h1>
{% if g.user %}
<a class="action" href="{{ url_for('interruptor.toggle') }}">
{% if status.status %}
Desligar
{% else %}
Ligar
{% endif %}
</a>
{% endif %}
{% endblock %}
{% block content %}
{% if status.status %}
Ligado
{% else %}
Desligado
{% endif %}
{% endblock %}

+ 13
- 0
setup.py View File

@ -0,0 +1,13 @@
from setuptools import find_packages, setup
setup(
name='interruptor',
version='1.0.0',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=[
'flask',
'RPi.GPIO',
],
)

Loading…
Cancel
Save