diff --git a/.gitignore b/.gitignore index 179dd1d..28ebf13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,181 +1,3 @@ -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask instance folder -instance/ - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject - -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask instance folder -instance/ - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject - -# Created by .ignore support plugin (hsz.mobi) +.idea/ +**.DS_Store +**.swp diff --git a/Dockerfile b/Dockerfile index 08db488..96c10bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,5 +7,6 @@ RUN pip3 install -r /requirements.txt; \ ADD app /app VOLUME ["/data"] +VOLUME ["/app/config] EXPOSE 5000 ENTRYPOINT python3 /app/main.py diff --git a/README.md b/README.md index 5054800..c0aff72 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,14 @@ ## build `docker build --tag=$(basename $PWD) .` +## general configuration + +Look at *app/config/email.py.example** for the configuration of the +parameters required for sending emails. Copy the file as *email.py* to +a folder that will serve as configuration directory and fill in the +information. The directory will be used as volume during container +operation. + ## run in development Include the development version of the code as volume, so the app gets @@ -10,7 +18,7 @@ reloaded automatically. The sqlite file will be stored in *tmp* so it can be inspected with tools like *sqlite3*. The switch *DEBUG* enables debugging during development. -`docker run --name rollerverbrauch -ti -v `pwd`/app:/app -v /tmp/pitstops/:/data -e DEBUG=True -p 5000:5000 rollerverbrauch` +`docker run --rm --name rollerverbrauch -ti -v `pwd`/app:/app -v `pwd`/../rollerverbrauch_config:/app/config -v /tmp/pitstops/:/data -e DEBUG=True -p 5000:5000 rollerverbrauch` ## run in production -`docker run --name pitstops -d -v /data/pitstops/:/data -p 80:5000 rollerverbrauch` \ No newline at end of file +`docker run --name pitstops -d -v /data/pitstops/:/data -v /configs/pitstops/:/app/config -p 80:5000 rollerverbrauch` diff --git a/app/config/config.py.example b/app/config/config.py.example new file mode 100644 index 0000000..0b968c8 --- /dev/null +++ b/app/config/config.py.example @@ -0,0 +1,6 @@ +MAIL_SERVER = 'smtp.gmail.com' +MAIL_PORT = 465 +MAIL_USE_TLS = False +MAIL_USE_SSL = True +MAIL_USERNAME = 'your-gmail-username' +MAIL_PASSWORD = 'your-gmail-password' \ No newline at end of file diff --git a/app/db/__pycache__/__init__.cpython-34.pyc b/app/db/__pycache__/__init__.cpython-34.pyc deleted file mode 100644 index fcd275f..0000000 Binary files a/app/db/__pycache__/__init__.cpython-34.pyc and /dev/null differ diff --git a/app/main.py b/app/main.py index 5fc40ac..b7de3dd 100644 --- a/app/main.py +++ b/app/main.py @@ -1,16 +1,18 @@ from datetime import date -from datetime import datetime from flask import Flask from flask import render_template, make_response from flask import request, redirect, g from flask import url_for from flask_sqlalchemy import SQLAlchemy from flask.ext.security import Security, SQLAlchemyUserDatastore, \ - UserMixin, RoleMixin, login_required, utils + UserMixin, RoleMixin, login_required, utils, roles_required from flask.ext.security.core import current_user import uuid import hashlib from functools import wraps +from flask.ext.security import user_registered +from flask.ext.mail import Mail, Message +from flask_security.core import current_user from flask_wtf import Form from wtforms import DateField, IntegerField, DecimalField, SelectField from wtforms.validators import DataRequired, ValidationError @@ -21,11 +23,14 @@ DATABASE = '/data/rollerverbrauch.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+DATABASE db = SQLAlchemy(app) -app.config['SECRET_KEY'] = 'development key' app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512' -app.config['SECURITY_PASSWORD_SALT'] = 'xxxxxxxxxxxxxxxxxxxxxx' +app.config['SECURITY_REGISTERABLE'] = True +app.config['SECURITY_CHANGEABLE'] = True +app.config['SECURITY_RECOVERABLE'] = True +app.config.from_envvar('config') app.config.from_object(__name__) +mail = Mail(app) roles_users = db.Table('roles_users', db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), @@ -97,26 +102,21 @@ class Pitstop(db.Model): user_datastore = SQLAlchemyUserDatastore(db, User, Role) security = Security(app, user_datastore) + +@user_registered.connect_via(app) +def user_registered_sighandler(app, user, confirm_token): + """ + Called after a user was created + """ + role = user_datastore.find_role('user') + user_datastore.add_role_to_user(user, role) + + @app.before_first_request def before_first_request(): db.create_all() - - user_datastore.find_or_create_role(name='admin', description='Administrator') - user_datastore.find_or_create_role(name='end-user', description='End user') - - encrypted_password = utils.encrypt_password('password') - if not user_datastore.get_user('someone@example.com'): - user_datastore.create_user(email='someone@example.com', password=encrypted_password) - if not user_datastore.get_user('admin@example.com'): - user_datastore.create_user(email='admin@example.com', password=encrypted_password) - - # Commit any database changes; the User and Roles must exist before we can add a Role to the User - db.session.commit() - - # Give one User has the "end-user" role, while the other has the "admin" role. (This will have no effect if the - # Users already have these Roles.) Again, commit any database changes. - user_datastore.add_role_to_user('someone@example.com', 'end-user') - user_datastore.add_role_to_user('admin@example.com', 'admin') + user_datastore.find_or_create_role(name='admin', description='Role for administrators') + user_datastore.find_or_create_role(name='user', description='Role for all users.') db.session.commit() user = User.query.filter(User.email=='someone@example.com').first() @@ -217,6 +217,20 @@ def get_manual(): return render_template('manual.html', data=g.data) +@app.route('/admin', methods=['GET']) +@roles_required('admin') +def get_admin_page(): + g.data['users'] = User.query.all() + return render_template('admin.html', data=g.data) + + +@app.route('/account', methods=['GET']) +@login_required +def get_account_page(): + print(current_user) + return render_template('account.html', data=g.data) + + @app.route('/statistics', methods=['GET']) @login_required def get_statistics(): diff --git a/app/templates/account.html b/app/templates/account.html new file mode 100644 index 0000000..de46b10 --- /dev/null +++ b/app/templates/account.html @@ -0,0 +1,7 @@ +{% extends "layout.html" %} + +{% block body %} +