dokuwiki support
This commit is contained in:
@@ -13,6 +13,12 @@ A simple shopping list app:
|
||||
Note! Register page is enabled by default, but there is no link to it anywhere.
|
||||
|
||||
* Disable registering with env variable: `ENABLE_REGISTER=false`
|
||||
* MARKDOWN_STYLE=dokuwiki or markdown
|
||||
* markdown syntax, unticked = `[ ]`, ticked = `[x]`
|
||||
* dokuwiki syntax (todo plugin), unticked = `<todo>item</todo>`, ticked = `<todo #user>item</todo>`
|
||||
* SECRET_KEY=somerandomstring
|
||||
* SESSION_COOKIE_NAME=name for cookies
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class ReverseProxied(object):
|
||||
'''Wrap the application in this middleware and configure the
|
||||
front-end server to add these headers, to let you quietly bind
|
||||
this to a URL other than / and to an HTTP scheme that is
|
||||
"""Wrap the application in this middleware and configure the
|
||||
front-end server to add these headers, to let you quietly bind
|
||||
this to a URL other than / and to an HTTP scheme that is
|
||||
different than what is used locally.
|
||||
|
||||
In nginx:
|
||||
@@ -14,19 +14,20 @@ class ReverseProxied(object):
|
||||
}
|
||||
|
||||
:param app: the WSGI application
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
|
||||
script_name = environ.get("HTTP_X_SCRIPT_NAME", "")
|
||||
if script_name:
|
||||
environ['SCRIPT_NAME'] = script_name
|
||||
path_info = environ['PATH_INFO']
|
||||
environ["SCRIPT_NAME"] = script_name
|
||||
path_info = environ["PATH_INFO"]
|
||||
if path_info.startswith(script_name):
|
||||
environ['PATH_INFO'] = path_info[len(script_name):]
|
||||
environ["PATH_INFO"] = path_info[len(script_name) :]
|
||||
|
||||
scheme = environ.get('HTTP_X_SCHEME', '')
|
||||
scheme = environ.get("HTTP_X_SCHEME", "")
|
||||
if scheme:
|
||||
environ['wsgi.url_scheme'] = scheme
|
||||
environ["wsgi.url_scheme"] = scheme
|
||||
return self.app(environ, start_response)
|
||||
|
||||
194
code/shop.py
194
code/shop.py
@@ -1,17 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# all the imports
|
||||
import sqlite3, time, datetime, hashlib, os, re
|
||||
import datetime
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import time
|
||||
from shutil import copyfile, move
|
||||
|
||||
from flask import (
|
||||
Flask,
|
||||
request,
|
||||
session,
|
||||
abort,
|
||||
flash,
|
||||
g,
|
||||
redirect,
|
||||
url_for,
|
||||
abort,
|
||||
render_template,
|
||||
flash,
|
||||
request,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from revprox import ReverseProxied
|
||||
|
||||
@@ -22,17 +28,20 @@ DEBUG = False
|
||||
SECRET_KEY = os.getenv("SECRET_KEY", "development key")
|
||||
USERNAME = os.getenv("ADMIN_USER", "admin")
|
||||
PASSWORD = os.getenv("ADMIN_PASSWD", "default")
|
||||
MARKDOWN_STYLE = os.getenv("MARKDOWN_STYLE", "markdown")
|
||||
URLFINDER = re.compile("((news|telnet|nttp|file|http|ftp|https)://[^ ]+)")
|
||||
URLPARSER = re.compile(r"(\[)([^\]]+)(\])\(([^\)]+)\)")
|
||||
BOLDFINDER = re.compile(r"\*([^\*]+)\*")
|
||||
CODEFINDER = re.compile(r"\`([^\`]+)\`")
|
||||
CHECKBOX_DOKUWIKI_TICKED = re.compile(r"<todo #([^>]*)>([^<]+)</todo>")
|
||||
CHECKBOX_DOKUWIKI_UNTICKED = re.compile(r"<todo>([^<]+)</todo>")
|
||||
SHOPNAME_INVALIDCHARS = re.compile("[^A-Za-z0-9_-]+")
|
||||
|
||||
# create our little application :)
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(__name__)
|
||||
app.config["SESSION_COOKIE_NAME"] = os.getenv("SESSION_COOKIE_NAME", "mdshop")
|
||||
app.config["register"] = os.getenv("ENABLE_REGISTER", "true") != "false"
|
||||
print(app.config)
|
||||
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||
|
||||
|
||||
@@ -82,9 +91,7 @@ def get_shop_date(id):
|
||||
data_dir = os.path.join(DATADIR, get_username(row[2]))
|
||||
data_file = os.path.join(data_dir, row[1] + ".md")
|
||||
if os.path.exists(data_file):
|
||||
date = datetime.datetime.fromtimestamp(
|
||||
os.path.getmtime(data_file)
|
||||
).strftime("%m/%d %H:%M")
|
||||
date = datetime.datetime.fromtimestamp(os.path.getmtime(data_file)).strftime("%m/%d %H:%M")
|
||||
return date
|
||||
|
||||
|
||||
@@ -96,9 +103,7 @@ def get_shop_backup_date(id):
|
||||
data_dir = os.path.join(DATADIR, get_username(row[2]))
|
||||
data_file = os.path.join(data_dir, row[1] + ".md.bkp")
|
||||
if os.path.exists(data_file):
|
||||
date = datetime.datetime.fromtimestamp(
|
||||
os.path.getmtime(data_file)
|
||||
).strftime("%m/%d %H:%M")
|
||||
date = datetime.datetime.fromtimestamp(os.path.getmtime(data_file)).strftime("%m/%d %H:%M")
|
||||
return date
|
||||
|
||||
|
||||
@@ -127,6 +132,8 @@ def markdown_parse(s):
|
||||
s = s.decode("utf8")
|
||||
s = BOLDFINDER.sub(r'*<span class="md_bold">\1</span>*', s)
|
||||
s = CODEFINDER.sub(r'`<span class="md_code">\1</span>`', s)
|
||||
s = CHECKBOX_DOKUWIKI_TICKED.sub(r"\2 (\1)", s)
|
||||
s = CHECKBOX_DOKUWIKI_UNTICKED.sub(r"\1", s)
|
||||
return s
|
||||
|
||||
|
||||
@@ -136,6 +143,37 @@ def urlify(s):
|
||||
return URLFINDER.sub(r'<a href="\1" target="_blank">\1</a>', s)
|
||||
|
||||
|
||||
def checkbox_add(item):
|
||||
if MARKDOWN_STYLE == "markdown":
|
||||
return f"[ ] {item}" + "\n"
|
||||
if MARKDOWN_STYLE == "dokuwiki":
|
||||
return f"<todo>{item}</todo>" + "\n"
|
||||
|
||||
|
||||
def untick(item):
|
||||
if MARKDOWN_STYLE == "markdown":
|
||||
return item.replace("[x]", "[ ]")
|
||||
if MARKDOWN_STYLE == "dokuwiki":
|
||||
return CHECKBOX_DOKUWIKI_TICKED.sub(r"<todo>\2</todo>", item)
|
||||
return item
|
||||
|
||||
|
||||
def tick(item, user=""):
|
||||
if MARKDOWN_STYLE == "markdown":
|
||||
return item.replace("[ ]", "[x]")
|
||||
if MARKDOWN_STYLE == "dokuwiki":
|
||||
return CHECKBOX_DOKUWIKI_UNTICKED.sub(r"<todo #{}>\1</todo>".format(user), item)
|
||||
return item
|
||||
|
||||
|
||||
def is_ticked(item):
|
||||
return "[x]" in item or "<todo #" in item
|
||||
|
||||
|
||||
def is_unticked(item):
|
||||
return "[ ]" in item or "<todo>" in item
|
||||
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
g.db = connect_db()
|
||||
@@ -180,11 +218,11 @@ def show_shop(shopid):
|
||||
continue
|
||||
icon = " "
|
||||
extra_class = "noitem"
|
||||
if "[ ]" in row:
|
||||
icon = u" "
|
||||
if is_unticked(row):
|
||||
icon = " "
|
||||
extra_class = ""
|
||||
if "[x]" in row:
|
||||
icon = u"\u2714"
|
||||
if is_ticked(row):
|
||||
icon = "\u2714"
|
||||
extra_class = ""
|
||||
row = urlify(row).encode("ascii", "xmlcharrefreplace")
|
||||
row = markdown_parse(row)
|
||||
@@ -227,17 +265,13 @@ def list_shops():
|
||||
for row in cur.fetchall():
|
||||
if session.get("user") == row[2]: # owner
|
||||
date = get_shop_date(row[0])
|
||||
entries.append(
|
||||
dict(shop=row[1], shopid=row[0], owner=get_username(row[2]), date=date)
|
||||
)
|
||||
entries.append(dict(shop=row[1], shopid=row[0], owner=get_username(row[2]), date=date))
|
||||
cur = g.db.execute("select * from shops order by shop")
|
||||
shares = get_shares(session.get("user"))
|
||||
for row in cur.fetchall():
|
||||
if row[0] in shares: # Has been shared to
|
||||
date = get_shop_date(row[0])
|
||||
entries.append(
|
||||
dict(shop=row[1], shopid=row[0], owner=get_username(row[2]), date=date)
|
||||
)
|
||||
entries.append(dict(shop=row[1], shopid=row[0], owner=get_username(row[2]), date=date))
|
||||
|
||||
return render_template("list_shops.html", entries=entries)
|
||||
|
||||
@@ -247,12 +281,8 @@ def add_items():
|
||||
if not session.get("logged_in"):
|
||||
abort(401)
|
||||
shopid = int(request.form["shopid"])
|
||||
ownerid = g.db.execute(
|
||||
"select owner from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
shopname = g.db.execute(
|
||||
"select shop from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
ownername = get_username(ownerid)
|
||||
data_dir = os.path.join(DATADIR, ownername)
|
||||
data_file = os.path.join(data_dir, shopname + ".md")
|
||||
@@ -262,7 +292,7 @@ def add_items():
|
||||
if row.strip() == "":
|
||||
continue
|
||||
count += 1
|
||||
contents_file.write("[ ] %s\n" % row.strip())
|
||||
contents_file.write(checkbox_add(row.strip()))
|
||||
contents_file.close()
|
||||
flash("Added %d items" % (count))
|
||||
return redirect(url_for("show_shop", shopid=shopid))
|
||||
@@ -273,12 +303,8 @@ def edit_md():
|
||||
if not session.get("logged_in"):
|
||||
abort(401)
|
||||
shopid = int(request.form["shopid"])
|
||||
ownerid = g.db.execute(
|
||||
"select owner from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
shopname = g.db.execute(
|
||||
"select shop from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
ownername = get_username(ownerid)
|
||||
data_dir = os.path.join(DATADIR, ownername)
|
||||
data_file = os.path.join(data_dir, shopname + ".md")
|
||||
@@ -300,12 +326,8 @@ def restore_md():
|
||||
if not session.get("logged_in"):
|
||||
abort(401)
|
||||
shopid = int(request.form["shopid"])
|
||||
ownerid = g.db.execute(
|
||||
"select owner from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
shopname = g.db.execute(
|
||||
"select shop from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
ownername = get_username(ownerid)
|
||||
data_dir = os.path.join(DATADIR, ownername)
|
||||
data_file = os.path.join(data_dir, shopname + ".md")
|
||||
@@ -326,13 +348,10 @@ def toggle_item():
|
||||
if not session.get("logged_in"):
|
||||
abort(401)
|
||||
shopid = int(request.form["shopid"])
|
||||
ownerid = g.db.execute(
|
||||
"select owner from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
shopname = g.db.execute(
|
||||
"select shop from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
ownername = get_username(ownerid)
|
||||
user = get_username(session.get("user"))
|
||||
req_row = None
|
||||
for key in request.form:
|
||||
if key.startswith("item"):
|
||||
@@ -355,10 +374,10 @@ def toggle_item():
|
||||
for i, row in enumerate(contents):
|
||||
if i == req_row or req_row < 0:
|
||||
if req_row != -2: # no ticking if unticking all
|
||||
if "[ ]" in row:
|
||||
contents[i] = row.replace("[ ]", "[x]")
|
||||
if "[x]" in row:
|
||||
contents[i] = row.replace("[x]", "[ ]")
|
||||
if is_unticked(row):
|
||||
contents[i] = tick(row, user)
|
||||
if is_ticked(row):
|
||||
contents[i] = untick(row)
|
||||
if row != contents[i]:
|
||||
changed = True
|
||||
if changed:
|
||||
@@ -375,12 +394,8 @@ def remove_toggled():
|
||||
if not session.get("logged_in"):
|
||||
abort(401)
|
||||
shopid = int(request.form["shopid"])
|
||||
ownerid = g.db.execute(
|
||||
"select owner from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
shopname = g.db.execute(
|
||||
"select shop from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
ownername = get_username(ownerid)
|
||||
data_dir = os.path.join(DATADIR, ownername)
|
||||
data_file = os.path.join(data_dir, shopname + ".md")
|
||||
@@ -389,7 +404,7 @@ def remove_toggled():
|
||||
contents = []
|
||||
changed = False
|
||||
for i, row in enumerate(contents_file.read().split("\n")):
|
||||
if "[x]" not in row:
|
||||
if not is_ticked(row):
|
||||
contents.append(row)
|
||||
else:
|
||||
changed = True
|
||||
@@ -407,10 +422,11 @@ def remove_toggled():
|
||||
def add_shop():
|
||||
if not session.get("logged_in"):
|
||||
abort(401)
|
||||
import re, string
|
||||
import re
|
||||
import string
|
||||
|
||||
shopname = SHOPNAME_INVALIDCHARS.sub("", request.form["shop"])
|
||||
|
||||
pattern = re.compile("[\W]+")
|
||||
shopname = pattern.sub("", request.form["shop"])
|
||||
if shopname == "":
|
||||
flash("Shop name empty!")
|
||||
return redirect(url_for("list_shops"))
|
||||
@@ -419,9 +435,7 @@ def add_shop():
|
||||
if shopname == row[1]:
|
||||
flash("Shop already exists! " + shopname)
|
||||
return redirect(url_for("list_shops"))
|
||||
g.db.execute(
|
||||
"insert into shops (shop,owner) values (?, ?)", [shopname, session["user"]]
|
||||
)
|
||||
g.db.execute("insert into shops (shop,owner) values (?, ?)", [shopname, session["user"]])
|
||||
g.db.commit()
|
||||
new_dir = os.path.join(DATADIR, get_username(session["user"]))
|
||||
new_file = os.path.join(new_dir, shopname + ".md")
|
||||
@@ -436,11 +450,11 @@ def add_shop():
|
||||
def add_share():
|
||||
if not session.get("logged_in"):
|
||||
abort(401)
|
||||
import re, string
|
||||
import re
|
||||
import string
|
||||
|
||||
pattern = re.compile("[\W]+")
|
||||
username = pattern.sub("", request.form["share"])
|
||||
shopid = pattern.sub("", request.form["shopid"])
|
||||
username = SHOPNAME_INVALIDCHARS.sub("", request.form["share"])
|
||||
shopid = SHOPNAME_INVALIDCHARS.sub("", request.form["shopid"])
|
||||
if username == "":
|
||||
flash("User name empty!")
|
||||
return redirect(url_for("show_shop", shopid=shopid))
|
||||
@@ -448,9 +462,7 @@ def add_share():
|
||||
if userid == None:
|
||||
flash("No such user!")
|
||||
return redirect(url_for("show_shop", shopid=shopid))
|
||||
ownerid = g.db.execute(
|
||||
"select owner from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
if session.get("user") != ownerid:
|
||||
flash("Not your shop!")
|
||||
return redirect(url_for("show_shop", shopid=shopid))
|
||||
@@ -464,11 +476,11 @@ def add_share():
|
||||
def remove_share():
|
||||
if not session.get("logged_in"):
|
||||
abort(401)
|
||||
import re, string
|
||||
import re
|
||||
import string
|
||||
|
||||
pattern = re.compile("[\W]+")
|
||||
username = pattern.sub("", request.form["user"])
|
||||
shopid = pattern.sub("", request.form["shopid"])
|
||||
username = SHOPNAME_INVALIDCHARS.sub("", request.form["user"])
|
||||
shopid = SHOPNAME_INVALIDCHARS.sub("", request.form["shopid"])
|
||||
if username == "":
|
||||
flash("User name empty!")
|
||||
return redirect(url_for("show_shop", shopid=shopid))
|
||||
@@ -476,9 +488,7 @@ def remove_share():
|
||||
if userid == None:
|
||||
flash("No such user!")
|
||||
return redirect(url_for("show_shop", shopid=shopid))
|
||||
ownerid = g.db.execute(
|
||||
"select owner from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
if session.get("user") != ownerid:
|
||||
flash("Not your shop!")
|
||||
return redirect(url_for("show_shop", shopid=shopid))
|
||||
@@ -493,12 +503,8 @@ def remove_shop():
|
||||
if not session.get("logged_in"):
|
||||
abort(401)
|
||||
shopid = int(request.form["shopid"])
|
||||
ownerid = g.db.execute(
|
||||
"select owner from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
shopname = g.db.execute(
|
||||
"select shop from shops where id=?", (request.form["shopid"],)
|
||||
).fetchall()[0][0]
|
||||
ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
|
||||
ownername = get_username(ownerid)
|
||||
data_dir = os.path.join(DATADIR, ownername)
|
||||
data_file = os.path.join(data_dir, shopname + ".md")
|
||||
@@ -553,10 +559,10 @@ def register():
|
||||
if not app.config["register"]:
|
||||
return ""
|
||||
if request.method == "POST":
|
||||
import re, string
|
||||
import re
|
||||
import string
|
||||
|
||||
pattern = re.compile("[\W]+")
|
||||
username = pattern.sub("", request.form["username"])
|
||||
username = SHOPNAME_INVALIDCHARS.sub("", request.form["username"])
|
||||
password = password_hash(request.form["password"])
|
||||
if len(username) == 0:
|
||||
error = "No username given"
|
||||
@@ -569,9 +575,7 @@ def register():
|
||||
if username == row[1]:
|
||||
error = "Username already exists"
|
||||
return render_template("register.html", error=error)
|
||||
g.db.execute(
|
||||
"insert into users (user,pass) values (?, ?)", [username, password]
|
||||
)
|
||||
g.db.execute("insert into users (user,pass) values (?, ?)", [username, password])
|
||||
g.db.commit()
|
||||
flash('successfully registered user "%s". Now login.' % username)
|
||||
return redirect(url_for("login"))
|
||||
@@ -585,16 +589,14 @@ def profile():
|
||||
error = None
|
||||
user = get_username(session.get("user"))
|
||||
if request.method == "POST":
|
||||
import re, string
|
||||
import re
|
||||
import string
|
||||
|
||||
pattern = re.compile("[\W]+")
|
||||
password = password_hash(request.form["password"])
|
||||
if len(request.form["password"]) < 5:
|
||||
error = "Password too short"
|
||||
return render_template("profile.html", error=error, user=user)
|
||||
g.db.execute(
|
||||
"update users set pass=? where id=?", [password, session.get("user")]
|
||||
)
|
||||
g.db.execute("update users set pass=? where id=?", [password, session.get("user")])
|
||||
g.db.commit()
|
||||
flash("successfully updated profile.")
|
||||
return redirect(url_for("profile"))
|
||||
|
||||
Reference in New Issue
Block a user