# -*- coding: utf-8 -*- # all the imports import sqlite3, time, datetime, hashlib, os from shutil import copyfile from flask import Flask, request, session, g, redirect, url_for, \ abort, render_template, flash # configuration DATABASE = 'shop.db' DATADIR = 'data' DEBUG = False SECRET_KEY = 'development key' USERNAME = 'admin' PASSWORD = 'default' # create our little application :) app = Flask(__name__) app.config.from_object(__name__) def connect_db(): if not os.path.exists(app.config['DATABASE']): db=sqlite3.connect(app.config['DATABASE']) for command in open('schema.sql','rt').read().split(';'): db.execute(command) db.commit() return sqlite3.connect(app.config['DATABASE']) def password_hash(s): return hashlib.sha224(s).hexdigest() def get_username(id): cur = g.db.execute('select * from users') for row in cur.fetchall(): if id==row[0]: return row[1] return None def get_userid(name): cur = g.db.execute('select * from users') for row in cur.fetchall(): if name==row[1]: return row[0] return None def get_shares(id): cur = g.db.execute('select * from shares') shares=[] for row in cur.fetchall(): if id==row[1]: shares.append(row[0]) return shares def get_shop_date(id): date="" cur = g.db.execute('select * from shops') for row in cur.fetchall(): if id==row[0]: 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') return date def scan_for_new_documents(id): user=get_username(id) data_dir=os.path.join(DATADIR, user) if not os.path.exists(data_dir): return cur = g.db.execute('select * from shops') existing=[] non_existing=[] for row in cur.fetchall(): if row[2]!=id: continue existing.append(row[1]) for row in os.listdir(data_dir): if row.endswith(".md"): if row[:-3] not in existing: non_existing.append(row[:-3]) for shop in non_existing: g.db.execute('insert into shops (shop,owner) values (?, ?)', [shop, id]) g.db.commit() @app.before_request def before_request(): g.db = connect_db() @app.teardown_request def teardown_request(exception): db = getattr(g, 'db', None) if db is not None: db.close() @app.route('/shop/') def show_shop(shopid): if not session.get('logged_in'): return redirect(url_for('login',error=None)) try: shopid=int(shopid) except ValueError: return redirect(url_for('login',error=None)) has_access=False cur = g.db.execute('select * from shops') shared=get_shares(session.get('user')) for row in cur.fetchall(): if row[0]==shopid: if row[2]==session.get('user') or row[0] in shared: has_access=True shopname=row[1] break if not has_access: return redirect(url_for('list_shops')) data_dir=os.path.join(DATADIR, get_username(row[2])) data_file=os.path.join(data_dir, row[1]+".md") entries=[] content=open(data_file, 'rt').read() for i,row in enumerate(open( data_file, 'rt').read().split("\n")): # any parsing magick would be here if row=="": continue icon=" " extra_class="noitem" if "[ ]" in row or "[x]" in row: icon=u"\u2714" extra_class="" entries.append( dict(row=i, text=row, icon=icon, extra_class=extra_class) ) shared_to=[] cur = g.db.execute('select * from shares') for row in cur.fetchall(): if row[0]==shopid: shared_to.append(get_username(row[1])) return render_template('show_shop.html', entries=entries, shop=shopname, shopid=shopid, content=content,shares=shared_to) @app.route('/') def list_shops(): if not session.get('logged_in'): return redirect(url_for('login',error=None)) scan_for_new_documents(session.get('user')) cur = g.db.execute('select * from shops order by shop') entries=[] 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) ) 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) ) return render_template('list_shops.html', entries=entries) @app.route('/add', methods=['POST']) 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] ownername=get_username(ownerid) data_dir=os.path.join(DATADIR, ownername) data_file=os.path.join(data_dir, shopname+".md") count=0 contents_file=open(data_file,'at') for row in request.form['add_md'].split("\n"): if row.strip()=="": continue count+=1 contents_file.write("[ ] %s\n"%row.strip()) contents_file.close() flash('Added %d items'%(count)) return redirect(url_for('show_shop',shopid=shopid)) @app.route('/edit', methods=['POST']) 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] ownername=get_username(ownerid) data_dir=os.path.join(DATADIR, ownername) data_file=os.path.join(data_dir, shopname+".md") backup=data_file+".bkp" copyfile(data_file, backup) contents_file=open(data_file,'wt') for row in request.form['edit_md'].split("\n"): contents_file.write("%s\n"%row) contents_file.close() flash('Saved new file.') return redirect(url_for('show_shop',shopid=shopid)) @app.route('/toggle', methods=['POST']) 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] ownername=get_username(ownerid) req_row=None for key in request.form: if key.startswith('item'): req_row=int(key[4:]) if req_row==None: return redirect(url_for('show_shop',shopid=shopid)) data_dir=os.path.join(DATADIR, ownername) data_file=os.path.join(data_dir, shopname+".md") contents_file=open(data_file,'rt') contents=contents_file.read().split("\n") contents_file.close() changed=False for i,row in enumerate(contents): if i==req_row: if '[ ]' in row: contents[i]=row.replace('[ ]','[x]') if '[x]' in row: contents[i]=row.replace('[x]','[ ]') if row!=contents[i]: changed=True break if changed: contents_file=open(data_file,'wt') contents_file.write("\n".join(contents)) contents_file.close() #~ flash('successfully posted %s (%d)'%(row,req_row)) return redirect(url_for('show_shop',shopid=shopid)) @app.route('/remove_toggled', methods=['POST']) 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] ownername=get_username(ownerid) data_dir=os.path.join(DATADIR, ownername) data_file=os.path.join(data_dir, shopname+".md") contents_file=open(data_file,'rt') contents=[] changed=False for i,row in enumerate(contents_file.read().split("\n")): if '[x]' not in row: contents.append(row) changed=True contents_file.close() if changed: contents_file=open(data_file,'wt') contents_file.write("\n".join(contents)) contents_file.close() #~ flash('successfully posted %s (%d)'%(row,req_row)) return redirect(url_for('show_shop',shopid=shopid)) @app.route('/add_shop', methods=['POST']) def add_shop(): if not session.get('logged_in'): abort(401) import re, string pattern = re.compile('[\W_]+') shopname=pattern.sub('', request.form['shop']) if shopname=="": flash('Shop name empty!') return redirect(url_for('list_shops')) cur = g.db.execute('select * from shops order by shop') for row in cur.fetchall(): 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.commit() new_dir=os.path.join(DATADIR, get_username(session['user'])) new_file=os.path.join(new_dir, shopname+".md") if not os.path.exists(new_dir): os.mkdir(new_dir) open( new_file, 'at') flash('successfully created new shop: '+shopname) return redirect(url_for('list_shops')) @app.route('/add_share', methods=['POST']) def add_share(): if not session.get('logged_in'): abort(401) import re, string pattern = re.compile('[\W_]+') username=pattern.sub('', request.form['share']) shopid=pattern.sub('', request.form['shopid']) if username=="": flash('User name empty!') return redirect(url_for('show_shop',shopid=shopid)) userid=get_userid(username) 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] if session.get('user')!=ownerid: flash('Not your shop!') return redirect(url_for('show_shop',shopid=shopid)) g.db.execute('insert into shares (shopid,userid) values (?, ?)', [shopid, userid]) g.db.commit() flash('Shared to %s'%(username)) return redirect(url_for('show_shop',shopid=shopid)) @app.route('/remove_share', methods=['POST']) def remove_share(): if not session.get('logged_in'): abort(401) import re, string pattern = re.compile('[\W_]+') username=pattern.sub('', request.form['user']) shopid=pattern.sub('', request.form['shopid']) if username=="": flash('User name empty!') return redirect(url_for('show_shop',shopid=shopid)) userid=get_userid(username) 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] if session.get('user')!=ownerid: flash('Not your shop!') return redirect(url_for('show_shop',shopid=shopid)) g.db.execute('delete from shares where shopid=? and userid=?', [shopid, userid]) g.db.commit() return redirect(url_for('show_shop',shopid=shopid)) @app.route('/remove_shop', methods=['POST']) 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] ownername=get_username(ownerid) data_dir=os.path.join(DATADIR, ownername) data_file=os.path.join(data_dir, shopname+".md") if session.get('user')!=ownerid: flash('Not your shop!') return redirect(url_for('show_shop',shopid=shopid)) # remove shop DB g.db.execute('delete from shops where id=?', [shopid]) g.db.commit() # backup data, and remove backup=data_file+".bkp" copyfile(data_file, backup) os.remove(data_file) # remove shares g.db.execute('delete from shares where shopid=?', [shopid]) g.db.commit() flash('successfully deleted shop %s'%(shopname)) return redirect(url_for('list_shops')) @app.route('/login', methods=['GET', 'POST']) def login(): error = None if request.method == 'POST': cur = g.db.execute('select * from users') for row in cur.fetchall(): if request.form['username'] == row[1]: if password_hash(request.form['password']) == row[2]: session['logged_in'] = True session['user'] = row[0] # scan_for_new_documents(row[0]) return redirect(url_for('list_shops')) error='Invalid user/pass' return render_template('login.html', error=error) @app.route('/register', methods=['GET', 'POST']) def register(): error = None if request.method == 'POST': import re, string pattern = re.compile('[\W_]+') username=pattern.sub('', request.form['username']) password=password_hash(request.form['password']) if len(username)==0: error="No username given" return render_template('register.html', error=error) if len(request.form['password'])<5: error="Password too short" return render_template('register.html', error=error) cur = g.db.execute('select * from users') for row in cur.fetchall(): 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.commit() flash('successfully registered user "%s". Now login.'%username) return redirect(url_for('login')) return render_template('register.html', error=error) @app.route('/logout') def logout(): session.pop('logged_in', None) session.pop('user', None) flash('You were logged out') return render_template('login.html', error=None) if __name__ == '__main__': app.run()