Skip to content

Commit

Permalink
Adds flask admin as a feature. Bumps flask admin to 1.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
oafernandes committed Jul 6, 2022
1 parent a9e8702 commit 98c2b7a
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 6 deletions.
4 changes: 4 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ FLASK_DEBUG=1
ALGOLIA_APP_ID=search_id
ALGOLIA_API_KEY=search_key
INDEX_NAME=resources_api
SECRET_KEY=sammy
SECURITY_PASSWORD_SALT=saltedpop
ADMIN_EMAIL=[email protected]
ADMIN_PASSWORD=1234
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ RUN apt-get update \
&& pip install poetry \
&& poetry config virtualenvs.create false

RUN poetry lock

RUN poetry install --no-dev --no-interaction --no-ansi

COPY . /src
Expand Down
1 change: 0 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from algoliasearch.search_client import SearchClient
from configs import Config

from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
Expand Down
35 changes: 35 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from sqlalchemy import DateTime
from sqlalchemy.sql import func
from sqlalchemy_utils import URLType
from flask_security import UserMixin, RoleMixin

language_identifier = db.Table('language_identifier',
db.Column(
Expand Down Expand Up @@ -206,3 +207,37 @@ class VoteInformation(db.Model):
current_direction = db.Column(db.String, nullable=True)
resource = db.relationship('Resource', back_populates='voters')
voter = db.relationship('Key', back_populates='voted_resources')


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 Role(db.Model, RoleMixin):
'''Role has three fields, ID, name and description'''
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))

def __str__(self):
return self.name

# __hash__ method avoids the exception, returns attribute that does not change
# TypeError:unhashable type:'Role' when saving a User
def __hash__(self):
return self.name


class User(db.Model, UserMixin):
id = db.Column(db.Integer(), primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship(
'Role',
secondary=roles_users,
backref=db.backref('users', lazy='dynamic')
)
17 changes: 17 additions & 0 deletions configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ def get_sys_exec_root_or_drive():
if not all([algolia_app_id, algolia_api_key]):
print("Application requires 'ALGOLIA_APP_ID' and 'ALGOLIA_API_KEY' for search")

secret_key = os.environ.get('SECRET_KEY', None)
security_password_hash = 'pbkdf2_sha512'
security_password_salt = os.environ.get('SECURITY_PASSWORD_SALT', None)

if not all([secret_key, security_password_salt]):
print('Application requires "SECRET_KEY" and "SECURITY_HASH"')

index_name = os.environ.get("INDEX_NAME")


Expand All @@ -49,6 +56,16 @@ class Config:
ALGOLIA_API_KEY = algolia_api_key
INDEX_NAME = index_name

SECRET_KEY = secret_key
SECURITY_URL_PREFIX = "/admin"
SECURITY_PASSWORD_HASH = security_password_hash
SECURITY_PASSWORD_SALT = security_password_salt
SECURITY_LOGIN_URL = "/login/"
SECURITY_LOGOUT_URL = "/logout/"
SECURITY_POST_LOGIN_VIEW = "/admin/"
SECURITY_POST_LOGOUT_VIEW = "/admin/"
SECURITY_REGISTERABLE = False
SECURITY_SEND_REGISTER_EMAIL = False
# Can pass in changes to defaults, such as PaginatorConfig(per_page=40)
RESOURCE_PAGINATOR = PaginatorConfig()
LANGUAGE_PAGINATOR = PaginatorConfig()
Expand Down
10 changes: 7 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ python = "^3.7"
algoliasearch = ">=2.0,<3.0"
alembic = "1.5.8"
bandit = "1.5.1"
click = "7.1.2"
click = "8.1.3"
flake8 = "3.9.0"
flask = "1.1.2"
flask = "2.1.2"
Flask-Cors = "3.0.10"
Flask-Migrate = "2.7.0"
prometheus_client = "0.9.0"
Expand All @@ -27,9 +27,13 @@ requests = "2.25.1"
sqlalchemy = "1.3.22"
SQLAlchemy-Utils = "0.36.8"
uWSGI = "2.0.19.1"
Werkzeug = "1.0.1"
Werkzeug = "2.1.2"
pyjwt = "^2.0.1"
cryptography = "^3.4"
flask-admin = "^1.6.0"
Flask-Login = "^0.6.1"
Flask-Security = "^3.0.0"
email-validator = "^1.2.1"

[tool.poetry.dev-dependencies]

Expand Down
57 changes: 55 additions & 2 deletions run.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
from app import app, cli
from app.models import Category, Language, Resource, db
from app.admin import run_flask_admin
from app.models import Category, Language, Resource, db, Role, User
import os
from flask_security import Security, SQLAlchemyUserDatastore, utils
from flask import url_for
from flask_admin import helpers as admin_helpers
from werkzeug.middleware.dispatcher import DispatcherMiddleware
from prometheus_client import make_wsgi_app
from sqlalchemy import event

admin = run_flask_admin(app)

user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)

if __name__ == "__main__":
app.run()
Expand All @@ -15,6 +25,49 @@
})


# @event.listens_for(User.password, 'set', retval=True)
# def hash_user_password(target, value, oldvalue, initiator):
# """Encrypts password when new admin created in User View"""
# if value != oldvalue:
# return utils.encrypt_password(value)
# return value


@security.context_processor
def security_context_processor():
return dict(
admin_base_template=admin.base_template,
admin_view=admin.index_view,
h=admin_helpers,
get_url=url_for
)


@app.shell_context_processor
def make_shell_context():
return {'db': db, 'Resource': Resource, 'Category': Category, 'Language': Language}
return {'db': db, 'Resource': Resource, 'Category': Category, 'Language': Language,
'User': User, 'Role': Role}


@app.before_first_request
def before_first_request():
""" Adds admin/user roles and default admin account and password if none exists"""
db.create_all()
user_datastore.find_or_create_role(name='admin', description='Administrator')
user_datastore.find_or_create_role(name='user', description='End User')

admin_email = os.environ.get('ADMIN_EMAIL', "[email protected]")
admin_password = os.environ.get('ADMIN_PASSWORD', 'password')

encrypted_password = utils.encrypt_password(admin_password)

if not user_datastore.get_user(admin_email):
user_datastore.create_user(email=admin_email, password=encrypted_password)
db.session.commit()

user_datastore.add_role_to_user(admin_email, 'admin')
db.session.commit()


if __name__ == "__main__":
app.run()

0 comments on commit 98c2b7a

Please sign in to comment.