Compare commits
No commits in common. "daf343535eca54261efd627e272f80adda75632f" and "bdfcebf439452044f7e9dff6cdb3114c3b3fb79e" have entirely different histories.
daf343535e
...
bdfcebf439
|
@ -1,45 +0,0 @@
|
||||||
from flask_admin import Admin, helpers as admin_helpers
|
|
||||||
from flask_admin.contrib.sqla import ModelView
|
|
||||||
from flask_security import current_user, utils
|
|
||||||
from wtforms.fields import PasswordField
|
|
||||||
|
|
||||||
from libertywiki.db import session
|
|
||||||
from libertywiki.models import User, Role
|
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(ModelView):
|
|
||||||
"""A custom view for user administration"""
|
|
||||||
column_exclude_list = ('password',)
|
|
||||||
form_excluded_columns = ('password',)
|
|
||||||
column_auto_select_related = True
|
|
||||||
|
|
||||||
def is_accessible(self):
|
|
||||||
"""Prevent administration of users from users without the "admin" role"""
|
|
||||||
return current_user.has_role('admin')
|
|
||||||
|
|
||||||
def scaffold_form(self):
|
|
||||||
"""Remove the password field so that we can bypass it and set the password manually"""
|
|
||||||
form_class = super().scaffold_form()
|
|
||||||
form_class.password2 = PasswordField('New Password')
|
|
||||||
return form_class
|
|
||||||
|
|
||||||
def on_model_change(self, form, model, is_created):
|
|
||||||
"""Update the password when the user is saved"""
|
|
||||||
if len(model.password2):
|
|
||||||
model.password = utils.encrypt_password(model.password2)
|
|
||||||
|
|
||||||
|
|
||||||
class RoleAdmin(ModelView):
|
|
||||||
"""A custom view for role administration"""
|
|
||||||
|
|
||||||
def is_accessible(self):
|
|
||||||
"""Prevent administration of roles from users without the "admin" role"""
|
|
||||||
return current_user.has_role('admin')
|
|
||||||
|
|
||||||
|
|
||||||
def setup_admin(app):
|
|
||||||
"""Set up the admin interface"""
|
|
||||||
admin = Admin(app, 'LibertyWiki Admin', base_template='admin/my_master.html', template_mode='bootstrap4')
|
|
||||||
admin.add_view(UserAdmin(User, session))
|
|
||||||
admin.add_view(RoleAdmin(Role, session))
|
|
||||||
return admin, admin_helpers
|
|
|
@ -1,92 +1,26 @@
|
||||||
import json
|
from flask import Flask
|
||||||
import os
|
|
||||||
import secrets
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
from flask import Flask, url_for
|
|
||||||
from flask_security import Security, SQLAlchemyUserDatastore
|
|
||||||
from flask_security.utils import hash_password
|
|
||||||
from rst2html import rst2html
|
from rst2html import rst2html
|
||||||
|
|
||||||
from libertywiki.admin import setup_admin
|
from libertywiki.db import db
|
||||||
from libertywiki.db import db, session
|
|
||||||
from libertywiki.models import User, Role
|
|
||||||
from libertywiki.views import wiki
|
from libertywiki.views import wiki
|
||||||
|
|
||||||
DEFAULT_CONFIG = {
|
|
||||||
'DEBUG': False,
|
|
||||||
'SECRET_KEY': secrets.token_urlsafe(128),
|
|
||||||
'SECURITY_PASSWORD_SALT': secrets.token_urlsafe(128),
|
|
||||||
'SECURITY_USERNAME_ENABLE': False,
|
|
||||||
'SECURITY_REGISTERABLE': True,
|
|
||||||
'SECURITY_SEND_REGISTER_EMAIL': False,
|
|
||||||
'SQLALCHEMY_DATABASE_URI': 'sqlite://',
|
|
||||||
'SQLALCHEMY_ENGINE_OPTIONS': {'pool_pre_ping': True},
|
|
||||||
'SQLALCHEMY_TRACK_MODIFICATIONS': False
|
|
||||||
}
|
|
||||||
CONFIG_VARS = [
|
|
||||||
'LIBERTYWIKI_CONFIG',
|
|
||||||
'LW_CONFIG',
|
|
||||||
'WIKI_CONFIG'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_app():
|
def get_app():
|
||||||
"""Create and configure the application object"""
|
"""Create and configure the application object"""
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
# Set app defaults
|
|
||||||
app.config.update(DEFAULT_CONFIG)
|
|
||||||
|
|
||||||
# Load config from file
|
|
||||||
for config_var in CONFIG_VARS:
|
|
||||||
if os.environ.get(config_var):
|
|
||||||
config_fname = Path(os.environ[config_var])
|
|
||||||
if config_fname.suffix == 'json':
|
|
||||||
loader = json.load
|
|
||||||
elif config_fname.suffix in ['yaml', 'yml']:
|
|
||||||
loader = yaml.safe_load
|
|
||||||
with Path(os.environ[config_var]).open() as conf_file:
|
|
||||||
app.config.update(**loader(conf_file))
|
|
||||||
break
|
|
||||||
|
|
||||||
# Load from environment variables
|
|
||||||
app.config.update(**{key: os.environ[key] for key in DEFAULT_CONFIG.keys() if os.environ.get(key)})
|
|
||||||
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
|
||||||
security = Security(app, user_datastore)
|
|
||||||
admin, admin_helpers = setup_admin(app)
|
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
# Create all the tables in the database
|
|
||||||
db.create_all()
|
db.create_all()
|
||||||
# Create the basic roles
|
|
||||||
admin_role = user_datastore.find_or_create_role(name='admin', description='Administrator')
|
|
||||||
user_datastore.find_or_create_role(name='end-user', description='End user')
|
|
||||||
session.commit()
|
|
||||||
# Create an administrator
|
|
||||||
if not User.query.filter(User.roles.contains(admin_role)).first():
|
|
||||||
user = user_datastore.create_user(name='Administrator', email='admin@example.com',
|
|
||||||
password=hash_password('password1234'))
|
|
||||||
user.roles.append(admin_role)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
@app.template_filter('rst2html')
|
@app.template_filter('rst2html')
|
||||||
def rst2html_filter(text):
|
def rst2html_filter(text):
|
||||||
html, warning = rst2html(text)
|
html, warning = rst2html(text)
|
||||||
|
print(html)
|
||||||
|
print(warning)
|
||||||
return html
|
return html
|
||||||
|
|
||||||
@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.register_blueprint(wiki)
|
app.register_blueprint(wiki)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -12,7 +12,6 @@ LargeBinary = db.LargeBinary
|
||||||
Table = db.Table
|
Table = db.Table
|
||||||
String = db.String
|
String = db.String
|
||||||
Text = db.Text
|
Text = db.Text
|
||||||
backref = db.backref
|
|
||||||
relationship = db.relationship
|
relationship = db.relationship
|
||||||
inspect = db.inspect
|
inspect = db.inspect
|
||||||
session = db.session
|
session = db.session
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from flask_security import UserMixin, RoleMixin
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
|
|
||||||
from libertywiki.db import Model, Table, Column, ForeignKey, Boolean, DateTime, Integer, String, Text, backref, \
|
from libertywiki.db import Model, Column, ForeignKey, DateTime, Integer, String, Text
|
||||||
relationship
|
from libertywiki.utils import bcrypt
|
||||||
|
|
||||||
|
|
||||||
roles_users = Table(
|
|
||||||
'roles_users',
|
|
||||||
Column('role_id', Integer, ForeignKey('roles.id')),
|
|
||||||
Column('user_id', Integer, ForeignKey('users.id'))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Page(Model):
|
class Page(Model):
|
||||||
|
@ -28,24 +21,10 @@ class Page(Model):
|
||||||
modified = Column(DateTime, default=datetime.now())
|
modified = Column(DateTime, default=datetime.now())
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '<Page {}>'.format(self.title)
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
class Role(Model, RoleMixin):
|
class User(Model):
|
||||||
"""
|
|
||||||
Role model
|
|
||||||
"""
|
|
||||||
__tablename__ = 'roles'
|
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
|
||||||
name = Column(String(255), unique=True, index=True, nullable=False)
|
|
||||||
description = Column(Text)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '<Role {}>'.format(self.name)
|
|
||||||
|
|
||||||
|
|
||||||
class User(Model, UserMixin):
|
|
||||||
"""
|
"""
|
||||||
User model
|
User model
|
||||||
"""
|
"""
|
||||||
|
@ -54,13 +33,13 @@ class User(Model, UserMixin):
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
name = Column(String(255))
|
name = Column(String(255))
|
||||||
email = Column(String(255), nullable=False, index=True, unique=True)
|
email = Column(String(255), nullable=False, index=True, unique=True)
|
||||||
password = Column(String(255), nullable=False)
|
_password = Column('password', String(255), nullable=False)
|
||||||
activation_code = Column(String(255), index=True)
|
activation_code = Column(String(255))
|
||||||
active = Column('is_active', Boolean, index=True)
|
|
||||||
confirmed_at = Column(DateTime)
|
|
||||||
fs_uniquifier = Column(String(255), unique=True, nullable=False, index=True)
|
|
||||||
|
|
||||||
roles = relationship('Role', secondary=roles_users, backref=backref('users', lazy='dynamic'))
|
@hybrid_property
|
||||||
|
def password(self):
|
||||||
|
return self.password
|
||||||
|
|
||||||
def __str__(self):
|
@password.setter
|
||||||
return '<User {}>'.format(self.email)
|
def password(self, value):
|
||||||
|
self._password = bcrypt.generate_password_hash(value)
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
{% extends 'admin/master.html' %}
|
|
||||||
{% block body %}
|
|
||||||
{{ super() }}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-10 col-sm-offset-1">
|
|
||||||
<h1>Flask-Admin example</h1>
|
|
||||||
<p class="lead">
|
|
||||||
Authentication
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This example shows how you can use <a href="https://pythonhosted.org/Flask-Security/index.html" target="_blank">Flask-Security</a> for authentication.
|
|
||||||
</p>
|
|
||||||
{% if not current_user.is_authenticated %}
|
|
||||||
<p>You can register as a regular user, or log in as a superuser with the following credentials:
|
|
||||||
<ul>
|
|
||||||
<li>email: <b>admin</b></li>
|
|
||||||
<li>password: <b>admin</b></li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
<a class="btn btn-primary" href="{{ url_for('security.login') }}">login</a> <a class="btn btn-default" href="{{ url_for('security.register') }}">register</a>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<p>
|
|
||||||
<a class="btn btn-primary" href="/"><i class="glyphicon glyphicon-chevron-left"></i> Back</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock body %}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{% extends 'admin/base.html' %}
|
|
||||||
|
|
||||||
{% block access_control %}
|
|
||||||
{% if current_user.is_authenticated %}
|
|
||||||
<div class="navbar-text btn-group pull-right">
|
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
|
||||||
<i class="glyphicon glyphicon-user"></i>
|
|
||||||
{% if current_user.first_name -%}
|
|
||||||
{{ current_user.first_name }}
|
|
||||||
{% else -%}
|
|
||||||
{{ current_user.email }}
|
|
||||||
{%- endif %}<span class="caret"></span></a>
|
|
||||||
<ul class="dropdown-menu" role="menu">
|
|
||||||
<li><a href="{{ url_for('security.logout') }}">Log out</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
|
@ -1,27 +0,0 @@
|
||||||
{% macro render_field_with_errors(field) %}
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
{{ field.label }} {{ field(class_='form-control', **kwargs)|safe }}
|
|
||||||
{% if field.errors %}
|
|
||||||
<ul>
|
|
||||||
{% for error in field.errors %}
|
|
||||||
<li>{{ error }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro render_field(field) %}
|
|
||||||
<p>{{ field(class_='form-control', **kwargs)|safe }}</p>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro render_checkbox_field(field) -%}
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="checkbox">
|
|
||||||
<label>
|
|
||||||
{{ field(type='checkbox', **kwargs) }} {{ field.label }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{%- endmacro %}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{% if security.registerable or security.recoverable or security.confirmable %}
|
|
||||||
<h2>Menu</h2>
|
|
||||||
<ul>
|
|
||||||
<li><a href="{{ url_for_security('login') }}{% if 'next' in request.args %}?next={{ request.args.next|urlencode }}{% endif %}">Login</a></li>
|
|
||||||
{% if security.registerable %}
|
|
||||||
<li><a href="{{ url_for_security('register') }}{% if 'next' in request.args %}?next={{ request.args.next|urlencode }}{% endif %}">Register</a><br/></li>
|
|
||||||
{% endif %}
|
|
||||||
{% if security.recoverable %}
|
|
||||||
<li><a href="{{ url_for_security('forgot_password') }}">Forgot password</a><br/></li>
|
|
||||||
{% endif %}
|
|
||||||
{% if security.confirmable %}
|
|
||||||
<li><a href="{{ url_for_security('send_confirmation') }}">Confirm account</a></li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{%- with messages = get_flashed_messages(with_categories=true) -%}
|
|
||||||
{% if messages %}
|
|
||||||
<ul class="flashes">
|
|
||||||
{% for category, message in messages %}
|
|
||||||
<li class="{{ category }}">{{ message }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
{%- endwith %}
|
|
|
@ -1,22 +0,0 @@
|
||||||
{% extends 'admin/master.html' %}
|
|
||||||
{% from "security/_macros.html" import render_field, render_field_with_errors, render_checkbox_field %}
|
|
||||||
{% include "security/_messages.html" %}
|
|
||||||
{% block body %}
|
|
||||||
{{ super() }}
|
|
||||||
<div class="row-fluid">
|
|
||||||
<div class="col-sm-8 col-sm-offset-2">
|
|
||||||
<h1>Login</h1>
|
|
||||||
<div class="well">
|
|
||||||
<form action="{{ url_for_security('login') }}" method="POST" name="login_user_form">
|
|
||||||
{{ login_user_form.hidden_tag() }}
|
|
||||||
{{ render_field_with_errors(login_user_form.email) }}
|
|
||||||
{{ render_field_with_errors(login_user_form.password) }}
|
|
||||||
{{ render_checkbox_field(login_user_form.remember) }}
|
|
||||||
{{ render_field(login_user_form.next) }}
|
|
||||||
{{ render_field(login_user_form.submit, class="btn btn-primary") }}
|
|
||||||
</form>
|
|
||||||
<p>Not yet signed up? Please <a href="{{ url_for('security.register') }}">register for an account</a>.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock body %}
|
|
|
@ -1,23 +0,0 @@
|
||||||
{% extends 'admin/master.html' %}
|
|
||||||
{% from "security/_macros.html" import render_field_with_errors, render_field %}
|
|
||||||
{% include "security/_messages.html" %}
|
|
||||||
{% block body %}
|
|
||||||
{{ super() }}
|
|
||||||
<div class="row-fluid">
|
|
||||||
<div class="col-sm-8 col-sm-offset-2">
|
|
||||||
<h1>Register</h1>
|
|
||||||
<div class="well">
|
|
||||||
<form action="{{ url_for_security('register') }}" method="POST" name="register_user_form">
|
|
||||||
{{ register_user_form.hidden_tag() }}
|
|
||||||
{{ render_field_with_errors(register_user_form.email) }}
|
|
||||||
{{ render_field_with_errors(register_user_form.password) }}
|
|
||||||
{% if register_user_form.password_confirm %}
|
|
||||||
{{ render_field_with_errors(register_user_form.password_confirm) }}
|
|
||||||
{% endif %}
|
|
||||||
{{ render_field(register_user_form.submit, class="btn btn-primary") }}
|
|
||||||
</form>
|
|
||||||
<p>Already signed up? Please <a href="{{ url_for('security.login') }}">log in</a>.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock body %}
|
|
22
setup.cfg
22
setup.cfg
|
@ -1,32 +1,14 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = libertywiki
|
name = libertywiki
|
||||||
description = LibertyWiki is a simple, Flask-based open source wiki written in Python
|
version = 0.0.1
|
||||||
keywords = wiki, python, flask, restructuredtext
|
|
||||||
license = MIT
|
|
||||||
license_files = LICENSE
|
|
||||||
url = https://git.libertytechforce.com/libertytechforce/libertywiki
|
|
||||||
classifiers =
|
|
||||||
Development Status :: 3 - Alpha
|
|
||||||
Environment :: Web Environment
|
|
||||||
Framework :: Flask
|
|
||||||
License :: OSI Approved :: MIT License
|
|
||||||
Operating System :: OS Independent
|
|
||||||
Programming Language :: Python
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Topic :: Documentation
|
|
||||||
Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Wiki
|
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
packages = libertywiki
|
packages = libertywiki
|
||||||
install_requires =
|
install_requires =
|
||||||
Flask
|
Flask
|
||||||
Flask-Admin
|
|
||||||
Flask-SQLAlchemy
|
Flask-SQLAlchemy
|
||||||
Flask-Bcrypt
|
Flask-Bcrypt
|
||||||
Flask-Security-Too
|
Flask-Security
|
||||||
bcrypt
|
|
||||||
email_validator
|
|
||||||
pyyaml
|
|
||||||
rst2html
|
rst2html
|
||||||
setup_requires =
|
setup_requires =
|
||||||
setuptools_scm
|
setuptools_scm
|
||||||
|
|
Loading…
Reference in New Issue