M app.py => app.py +14 -0
@@ 1,7 1,21 @@
from flask import Flask
from solo.api import bp as api_bp
from solo.general import bp as general_bp
+from playhouse.flask_utils import FlaskDB
+from flask_login import LoginManager
+from solo.models import User
+from solo.config import Config
+
app = Flask(__name__)
app.register_blueprint(general_bp)
app.register_blueprint(api_bp, url_prefix='/api')
+app.config.from_object(Config)
+app.db = FlaskDB(app)
+login = LoginManager(app)
+login.login_view = 'login'
+
+@login.user_loader
+def load_user(uid):
+ return User.get(User.id == uid)
+
M solo/general/endpoints.py => solo/general/endpoints.py +31 -1
@@ 1,5 1,35 @@
-from flask import render_template
+from flask import render_template, request, flash, redirect
+from flask import render_template_string
+from flask import url_for, Response
+from flask_login import login_user, logout_user
from solo.general import bp
+
+from solo.models import * #noqa
+import solo.general.forms as forms
+
@bp.route('/')
+@bp.route('/index')
def index():
return render_template('index.html')
+
+@bp.route('/login', methods=['GET', 'POST'])
+def login():
+ form = forms.LoginForm()
+ if form.validate_on_submit(): # noqa
+ user = User.get(User.username == form.username.data)
+ if user is None or not user.check_password(form.password.data):
+ flash('Invalid username or password')
+ return redirect(url_for('login'))
+ login_user(user, remember=form.remember_me.data)
+ next_page = request.args.get('next')
+ if not next_page or url_parse(next_page).netloc != '':
+ next_page = url_for('general.index')
+ return redirect(next_page)
+ else: # noqa
+ return render_template('login.html', form=form)
+
+@bp.route('/logout')
+def logout():
+ flash("Goodbye!")
+ logout_user()
+ return redirect(url_for('general.index'))
A solo/general/forms.py => solo/general/forms.py +18 -0
@@ 0,0 1,18 @@
+from flask_wtf import FlaskForm
+from wtforms import StringField, PasswordField, BooleanField, SubmitField
+from wtforms.validators import DataRequired, URL
+from wtforms.widgets import TextArea
+
+
+class LoginForm(FlaskForm):
+ """Login Form"""
+ username = StringField('Username', validators=[DataRequired()])
+ password = PasswordField('Password', validators=[DataRequired()])
+ remember_me = BooleanField('Remember Me')
+ submit = SubmitField('Sign In')
+
+
+class SearchForm(FlaskForm):
+ """Search form"""
+ term = StringField('Search Query', validators=[DataRequired()])
+ submit = SubmitField('Go!')
M solo/models.py => solo/models.py +24 -0
@@ 1,5 1,9 @@
from peewee import *
from solo.config import Config
+from playhouse.sqlite_ext import SqliteExtDatabase
+from flask_login import UserMixin
+from werkzeug.security import generate_password_hash, check_password_hash
+
database = SqliteExtDatabase(Config.dbpath)
class UnknownField(object):
@@ 13,6 17,7 @@ class Games(BaseModel):
descr = TextField(null=True)
name = CharField(null=True)
owner_id = IntegerField()
+ last_updated = DateTimeField(null=False, default='1970-01-01')
class Meta:
table_name = 'Games'
@@ 30,7 35,26 @@ class Acts(BaseModel):
first_scene = ForeignKeyField(column_name='first_scene', field='duid', model=Scenes, null=True)
name = CharField(null=True)
parent_game = ForeignKeyField(column_name='parent_game', field='id', model=Games)
+ last_updated = DateTimeField(null=False, default='1970-01-01')
+ published = BooleanField(default=False)
class Meta:
table_name = 'Acts'
+
+class User(UserMixin, BaseModel):
+ id = IntegerField(primary_key=True)
+ username = CharField(max_length=64, index=True, unique=True)
+ email = TextField(unique=True, index=True)
+ password_hash = CharField(max_length=128)
+ admin = BooleanField(null=False, default=False)
+ date_created = DateTimeField()
+ urole = CharField(max_length=64, null=False, default='alpha-tester')
+
+ def set_password(self, password):
+ self.password_hash = generate_password_hash(password)
+
+ def check_password(self, password):
+ return check_password_hash(self.password_hash, password)
+
+
M templates/base.html => templates/base.html +5 -7
@@ 14,18 14,16 @@
<main>
<div class="sidebar">
- <a href="/index"> ⭐ Home </a> <br/>
-
-
- {% if not False %}
- <a href="/login"> 🗝️ Login </a> <br/>
+ <a href="{{ url_for('general.index') }"> ⭐ Home </a> <br/>
+ {% if current_user.is_anonymous %}
+ <a href="{{ url_for('general.login') }"> 🗝️ Login </a> <br/>
{% else %}
- <a href="/logout"> 🗝️ Logout </a><br/>
+ <a href="{{ url_for('general.logout') }"> 🗝️ Logout </a><br/>
{% endif %}
</div>
<hr>
<hr>
-<span style="color: peru;"> {% with messages = get_flashed_messages() %}
+<span style="color: purple;"> {% with messages = get_flashed_messages() %}
{% if messages %}
<marquee width="60%" direction="up" height="100px">
<ul>
M templates/index.html => templates/index.html +1 -1
@@ 1,5 1,5 @@
{% extends "base.html" %}
{% block content %}
-<div class="fancy-pants"><p> <h1> Hi World </h1> </p> </div>
+<div class="fancy-pants"><p> <h1> Hi {{ current_user.username }} </h1> </p> </div>
{% endblock %}
A templates/login.html => templates/login.html +24 -0
@@ 0,0 1,24 @@
+{% extends "base.html" %}
+
+{% block content %}
+ <h1>Welcome to SoloAdventure</h1>
+ <form action="" method="post" novalidate>
+ {{ form.hidden_tag() }}
+ <p>
+ {{ form.username.label }}<br>
+ {{ form.username(size=32) }}<br>
+ {% for error in form.username.errors %}
+ <span style="color: purple;">[{{ error }}]</span>
+ {% endfor %}
+ </p>
+ <p>
+ {{ form.password.label }}<br>
+ {{ form.password(size=32) }}<br>
+ {% for error in form.password.errors %}
+ <span style="color: purple;">[{{ error }}]</span>
+ {% endfor %}
+ </p>
+ <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
+ <p>{{ form.submit() }}</p>
+ </form>
+{% endblock %}<
\ No newline at end of file