diff --git a/README.md b/README.md
index 6a4dfb3..6ee2617 100644
--- a/README.md
+++ b/README.md
@@ -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 = `item`, ticked = `item`
+* SECRET_KEY=somerandomstring
+* SESSION_COOKIE_NAME=name for cookies
+
## Running
diff --git a/code/revprox.py b/code/revprox.py
index 9649e61..3914560 100644
--- a/code/revprox.py
+++ b/code/revprox.py
@@ -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)
diff --git a/code/shop.py b/code/shop.py
index 58b000b..42a18fa 100644
--- a/code/shop.py
+++ b/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"]*)>([^<]+)")
+CHECKBOX_DOKUWIKI_UNTICKED = re.compile(r"([^<]+)")
+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'*\1*', s)
s = CODEFINDER.sub(r'`\1`', 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'\1', s)
+def checkbox_add(item):
+ if MARKDOWN_STYLE == "markdown":
+ return f"[ ] {item}" + "\n"
+ if MARKDOWN_STYLE == "dokuwiki":
+ return f"{item}" + "\n"
+
+
+def untick(item):
+ if MARKDOWN_STYLE == "markdown":
+ return item.replace("[x]", "[ ]")
+ if MARKDOWN_STYLE == "dokuwiki":
+ return CHECKBOX_DOKUWIKI_TICKED.sub(r"\2", 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"\1".format(user), item)
+ return item
+
+
+def is_ticked(item):
+ return "[x]" in item or "" 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"))