Add mdi, fake sessions, WIP db models (no migrations yet)
This commit is contained in:
parent
d529552609
commit
4a519bbb85
|
@ -8,3 +8,4 @@ __pycache__
|
|||
.ycm_extra_conf.py
|
||||
postgres/
|
||||
.envrc
|
||||
.mypy_cache/
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "material-design-icons"]
|
||||
path = material-design-icons
|
||||
url = https://github.com/google/material-design-icons
|
5
Makefile
5
Makefile
|
@ -1,4 +1,7 @@
|
|||
.PHONY: check install
|
||||
.PHONY: setup check install
|
||||
|
||||
setup:
|
||||
! [ -d wikilain/static/mdi ] && ln -s ../../material-design-icons/iconfont/ wikilain/static/mdi || true
|
||||
|
||||
check:
|
||||
@mypy . || true
|
||||
|
|
|
@ -12,6 +12,7 @@ mistune
|
|||
mistune_contrib
|
||||
flask-sqlalchemy
|
||||
flask-migrate
|
||||
pygments
|
||||
```
|
||||
|
||||
```
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 224895a86501195e7a7ff3dde18e39f00b8e3d5a
|
3
setup.py
3
setup.py
|
@ -15,7 +15,8 @@ setup(name='wikilain',
|
|||
"mistune",
|
||||
"mistune_contrib",
|
||||
"flask-sqlalchemy",
|
||||
"flask-migrate"
|
||||
"flask-migrate",
|
||||
"pygments"
|
||||
],
|
||||
include_package_data=True,
|
||||
entry_points={
|
||||
|
|
|
@ -1,15 +1,29 @@
|
|||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
from flask import Flask, render_template, g
|
||||
|
||||
from .models import *
|
||||
from .markdown import *
|
||||
from .adapters.session_simple import session_provider
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:////tmp/test.db'
|
||||
app.config["SECRET_KEY"] = "changeme"
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = 'postgresql://@/wikilain_dev'
|
||||
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
||||
|
||||
from .models import db, migrate, User
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
|
||||
app.register_blueprint(session_provider, url_prefix="/session")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def main_page():
|
||||
return "Hello and also world"
|
||||
print(g.wl_user)
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@app.route("/test")
|
||||
def test_article():
|
||||
return render_template(
|
||||
'article.html', article_title="Sample Text",
|
||||
article_content=md_render("# sample\n\nmeme and also\n\n\n---\nmeme $2+2$"))
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
from flask import Blueprint, session, request, render_template, redirect, g, flash
|
||||
import secrets
|
||||
|
||||
from ..models import User, db
|
||||
|
||||
__all__ = ["session_provider"]
|
||||
|
||||
session_provider = Blueprint("session_simple", __name__, template_folder='templates')
|
||||
|
||||
@session_provider.before_app_request
|
||||
def get_session_user():
|
||||
if session.get("username", None) is not None:
|
||||
g.wl_user = User.query.filter_by(username=session["username"]).first()
|
||||
else:
|
||||
g.wl_user = None
|
||||
|
||||
|
||||
@session_provider.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
old_csrf = session.get("_csrf", None)
|
||||
csrf = secrets.token_urlsafe(32)
|
||||
session["_csrf"] = csrf
|
||||
|
||||
if g.wl_user is not None:
|
||||
return redirect("/", code=302)
|
||||
|
||||
if request.method == 'GET':
|
||||
return render_template("login_form.html", csrf=csrf)
|
||||
else:
|
||||
if not request.form["username"]:
|
||||
flash("Must provide username", "error")
|
||||
return render_template("login_form.html", csrf=csrf)
|
||||
if old_csrf != request.form["_csrf"]:
|
||||
flash("Invalid request", "error")
|
||||
return render_template("login_form.html", csrf=csrf)
|
||||
user = User.query.filter_by(username=request.form["username"]).first()
|
||||
if not user:
|
||||
db.session.add(User(username=request.form["username"], email=secrets.token_hex(8) + "@example.com"))
|
||||
db.session.commit()
|
||||
session["username"] = request.form["username"]
|
||||
del session["_csrf"]
|
||||
return redirect("/", code=302)
|
||||
|
||||
|
||||
@session_provider.route("/logout", methods=["POST"])
|
||||
def logout():
|
||||
del session["username"]
|
||||
del session["_csrf"]
|
||||
return redirect("/", code=302)
|
|
@ -0,0 +1,14 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}
|
||||
Log In
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Log In</h2>
|
||||
<form action="{{ url_for(request.endpoint) }}" method="POST">
|
||||
<input type="hidden" style="display:none" name="_csrf" value="{{ csrf }}" />
|
||||
<input type="text" name="username" placeholder="Username" />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,32 @@
|
|||
from mistune import Markdown, Renderer, InlineLexer, BlockLexer, BlockGrammar
|
||||
from mistune_contrib.highlight import HighlightMixin
|
||||
from mistune_contrib.math import MathBlockMixin, MathInlineMixin, MathRendererMixin
|
||||
from flask import Markup
|
||||
|
||||
|
||||
class WlInlineLexer(InlineLexer, MathInlineMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WlInlineLexer, self).__init__(*args, **kwargs)
|
||||
self.enable_math()
|
||||
|
||||
|
||||
class WlBlockLexer(BlockLexer, MathBlockMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WlBlockLexer, self).__init__(*args, **kwargs)
|
||||
self.enable_math()
|
||||
|
||||
|
||||
class WlRenderer(Renderer, MathRendererMixin, HighlightMixin):
|
||||
def __init__(self):
|
||||
super(WlRenderer, self).__init__(escape=True, use_xhtml=True)
|
||||
|
||||
|
||||
renderer = WlRenderer()
|
||||
inline = WlInlineLexer(renderer)
|
||||
block = WlBlockLexer(BlockGrammar())
|
||||
markdown = Markdown(renderer=renderer, inline=inline, block=block)
|
||||
|
||||
__all__ = ["md_render"]
|
||||
|
||||
def md_render(*args, **kwargs):
|
||||
return Markup(markdown(*args, **kwargs))
|
|
@ -5,9 +5,44 @@ db = SQLAlchemy()
|
|||
migrate = Migrate(None, db)
|
||||
|
||||
class User(db.Model):
|
||||
__tablename__ = "users"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||
username = db.Column(db.String, unique=True, nullable=False)
|
||||
email = db.Column(db.String, unique=True, nullable=False)
|
||||
avatar = db.Column(db.String, nullable=True)
|
||||
|
||||
def __repr__(self):
|
||||
return '<User %r>' % self.username
|
||||
return f"<User {self.username}>"
|
||||
|
||||
class Section(db.Model):
|
||||
__tablename__ = "sections"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String, unique=True, nullable=False)
|
||||
articles = db.relationship("Article", backref="section", lazy=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Section \"{self.title}\">"
|
||||
|
||||
DEFAULT_SECTION = 1
|
||||
|
||||
class Article(db.Model):
|
||||
__tablename__ = "articles"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String, unique=True, nullable=False)
|
||||
section_id = db.Column(db.Integer, db.ForeignKey("sections.id"), nullable=False)
|
||||
revisions = db.relationship("ArticleRevision", backref="article", lazy=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Article \"{self.title}\">"
|
||||
|
||||
class ArticleRevision(db.Model):
|
||||
__tablename__ = "articlerevisions"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
article_id = db.Column(db.Integer, db.ForeignKey("articles.id"), nullable=False)
|
||||
date = db.Column(db.DateTime, nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
|
||||
user = db.relationship("User", lazy=True)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ArticleRevision at {self.date} by {self.user}>"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../material-design-icons/iconfont/
|
|
@ -0,0 +1,5 @@
|
|||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}
|
||||
{{ article_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{{ article_title }}</h2>
|
||||
{{ article_content }}
|
||||
{% endblock %}
|
|
@ -0,0 +1,35 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>{% block title %}{% endblock %} - WikiLain</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='mdi/material-icons.css') }}" />
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<h1><a href="/">WikiLain</a></h1>
|
||||
<ul>
|
||||
<li>
|
||||
<form action="/search" action="POST">
|
||||
<input type="text" name="q" placeholder="Search" />
|
||||
</form>
|
||||
</li>
|
||||
<li><a href="/sections">Sections</a></li>
|
||||
<li><a href="/user/login">Log In</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<section class="flashed-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash flash-{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}
|
||||
Main Page
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Welcome to WikiLain</h2>
|
||||
<p>Sample and also text</p>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue