diff --git a/bootstrap/webapp/templates/layout.html b/bootstrap/webapp/templates/layout.html index dbf0faf..d253c59 100644 --- a/bootstrap/webapp/templates/layout.html +++ b/bootstrap/webapp/templates/layout.html @@ -114,6 +114,8 @@ .pull-right { float: right; + right: 50px; + position: relative; } pre { diff --git a/bootstrap/webapp/templates/navigation.html b/bootstrap/webapp/templates/navigation.html index 37e28b8..0415a01 100644 --- a/bootstrap/webapp/templates/navigation.html +++ b/bootstrap/webapp/templates/navigation.html @@ -1 +1,9 @@ -Home | \ No newline at end of file +Home | +
+ {% if current_user.is_authenticated %} + {{ current_user.email }} | + Logout | + {% else %} + Login | + {% endif %} +
\ No newline at end of file diff --git a/oshipka/persistance/__init__.py b/oshipka/persistance/__init__.py index 4f44ad6..4b9e3e4 100644 --- a/oshipka/persistance/__init__.py +++ b/oshipka/persistance/__init__.py @@ -1,12 +1,94 @@ +import datetime import os +import re +from json import JSONEncoder +from uuid import uuid4 from flask_sqlalchemy import SQLAlchemy +from flask_security import Security, SQLAlchemyUserDatastore +from sqlalchemy.ext.declarative import declared_attr +from flask_security import RoleMixin, UserMixin from config import SQLALCHEMY_DATABASE_URI, MAKEDIRS, DATABASE_FILE db = SQLAlchemy() +class Ownable(object): + @declared_attr + def user_id(self): + return db.Column(db.Integer, db.ForeignKey('user.id')) + + @declared_attr + def user(self): + return db.relationship("User") + + +roles_users = db.Table('roles_users', + db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), + db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))) + + +class ModelJsonEncoder(JSONEncoder): + def default(self, o): + if isinstance(o, datetime.datetime): + return str(o) + return o.id + + +def camel_case_to_snake_case(name): + """ + Convertes a CamelCase name to snake_case + :param name: the name to be converted + :return: + """ + s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() + + +class ModelController(ModelJsonEncoder): + """ + This interface is the parent of all models in our database. + """ + + @declared_attr + def __tablename__(self): + return camel_case_to_snake_case(self.__name__) # pylint: disable=E1101 + + _sa_declared_attr_reg = {'__tablename__': True} + __mapper_args__ = {'always_refresh': True} + + id = db.Column(db.Integer, primary_key=True) + uuid = db.Column(db.Unicode, default=str(uuid4())) + + +class Role(db.Model, ModelController, RoleMixin): + name = db.Column(db.Unicode, unique=True) + description = db.Column(db.Unicode) + + +class User(db.Model, ModelController, UserMixin): + email = db.Column(db.Unicode, unique=True) + password = db.Column(db.Unicode) + + active = db.Column(db.Boolean(), default=True) + confirmed_at = db.Column(db.DateTime()) + + timezone = db.Column(db.String, default='UTC') + tz_offset_seconds = db.Column(db.Integer, default=0) + locale = db.Column(db.String(4), default='en') + + name = db.Column(db.Unicode) + profile_image_url = db.Column(db.String) + + roles = db.relationship('Role', secondary=roles_users, + backref=db.backref('users', lazy='dynamic')) + + +security = Security() +user_datastore = SQLAlchemyUserDatastore(db, User, Role) + + def init_db(app): app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False @@ -16,9 +98,11 @@ def init_db(app): app.register_blueprint(oshipka_bp) db.init_app(app) + security.init_app(app, user_datastore) + for dir in MAKEDIRS: os.makedirs(dir, exist_ok=True) if not os.path.exists(DATABASE_FILE): with app.app_context(): db.create_all() - return True + return True \ No newline at end of file diff --git a/oshipka/webapp/__init__.py b/oshipka/webapp/__init__.py index 1c3de1c..fbc5366 100644 --- a/oshipka/webapp/__init__.py +++ b/oshipka/webapp/__init__.py @@ -2,6 +2,7 @@ from flask import Flask, Blueprint app = Flask(__name__) app.config["SECRET_KEY"] = "UNSECRET_KEY....478932fjkdsl" +app.config["SECURITY_PASSWORD_SALT"] = "UNSECRETfdsafsda_KEY....478932fjkdsl" test_bp = Blueprint('test_bp', __name__, url_prefix="/test", diff --git a/requirements.txt b/requirements.txt index d72d744..dc69db6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,33 @@ Babel==2.8.0 +bcrypt==3.1.7 +blinker==1.4 +cffi==1.14.0 click==7.1.1 filelock==3.0.12 Flask==1.1.1 Flask-Babel==1.0.0 +Flask-BabelEx==0.9.4 +Flask-Login==0.5.0 +Flask-Mail==0.9.1 +Flask-Principal==0.4.0 +Flask-Security==3.0.0 Flask-SQLAlchemy==2.4.1 Flask-Table==0.5.0 +Flask-WTF==0.14.3 importlib-metadata==1.5.2 inflect==4.1.0 itsdangerous==1.1.0 Jinja2==2.11.1 MarkupSafe==1.1.1 +passlib==1.7.2 pathtools==0.1.2 +pycparser==2.20 pytz==2019.3 six==1.14.0 +speaklater==1.3 SQLAlchemy==1.3.15 SQLAlchemy-Utils==0.36.3 watchdog==0.10.2 Werkzeug==1.0.0 +WTForms==2.2.1 zipp==3.1.0