import os import re import json import requests import sqlalchemy from functools import wraps from sqlalchemy.sql import select from sqlalchemy.sql import text from flask import Flask from flask import render_template, send_from_directory, request, Response, send_file, session, jsonify from PIL import Image, ImageDraw from io import BytesIO from os import listdir from os.path import isfile, join from werkzeug.utils import secure_filename import random app = Flask(__name__) db_engine = {} db_metadata = {} parts = {} octopartURL ="" baseURL = "/parts" taglines = [] with open('taglines.txt', 'r') as infile: for line in infile: taglines.append(line[:-1]) def getContainers(): query = "select id, name from containers order by UPPER(name);" # r = db_engine.execute(text(query)) # containers = [] # for row in r: # containers.append(dict(row)) # r.close() with db_engine.connect() as conn: r = conn.execute(text(query)) containers = r.mappings().all() return containers def check_auth(username, password): query = "select id, password from users where username=:usrnm;" #r = db_engine.execute(text(query), usrnm=username) #SQLalchemy 1.x with db_engine.connect() as conn: r = conn.execute(text(query)) results = r.mappings().all() if len(results)!=1: return False; if results[0]['password']==password: session['uid'] = results[0]['id'] print (session['uid']) return True else: return False def authenticate(): return Response('Could not verify access level. Please retry', 401, {'WWW-Authenticate' : 'Basic realm="Login Required"'}) def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): return authenticate() return f(*args, **kwargs) return decorated def serveImage(img): img_io = BytesIO() img.convert("RGB").save(img_io, 'PNG') img_io.seek(0) return send_file(img_io, mimetype='image/png') @app.route(baseURL, strict_slashes=True) def index(): return render_template('partsearch.html', containers=getContainers(), baseURL=baseURL, tagline=random.choice(taglines)) @app.route(baseURL+'/getlocationsInContainer/') def get_locations_in_container(containerID): s = 'select id, name from locations where container_id = :id order by name;' # r = db_engine.execute(text(s), id=containerID) # l={} # for row in r: # l[str(row[0])]= row[1]; # r.close() # return json.dumps(l) with db_engine.connect() as conn: r = conn.execute(text(s), {"id": containerID}) l = {str(row.id): row.name for row in r} return jsonify(l) @app.route(baseURL+'/getlocationURL/') def get_locationURL(locationID): s = 'select map from locations where id = :id;' # r = db_engine.execute(text(s), id=locationID) # l=[]; # for row in r: # l.append(row); # r.close() # return l[0][0] with db_engine.connect() as conn: result = conn.execute(text(s), {"id": locationID}).fetchone() if result is None: return "" return result[0] @app.route(baseURL+'/locationEditor') def locationEditor(): query = 'select c.name as container, l.name as name, l.id, c.id as container_id from locations as l inner join containers as c on l.container_id = c.id order by container, name;' # r = db_engine.execute(text(query)) # locations = [] # for row in r: # locations.append(dict(row)) # r.close() # return render_template('locationEditor.html', locations=locations, containers=getContainers(),baseURL=baseURL) with db_engine.connect() as conn: result = conn.execute(text(query)) locations = [dict(row._mapping) for row in result] return render_template( 'locationEditor.html', locations=locations, containers=getContainers(), baseURL=baseURL ) @app.route(baseURL+'/userEditor') def userEditor(): query = 'select id, username from users;' # r = db_engine.execute(text(query)) # users = [] # for row in r: # users.append(dict(row)) # r.close() with db_engine.connect() as conn: result = conn.execute(text(query)) users = [dict(row._mapping) for row in result] return render_template('userEditor.html', users=users, containers=getContainers(),baseURL=baseURL) @app.route(baseURL + '/alterLocation/', methods=['POST']) @requires_auth def alterLocation(locationID): locationID = int(locationID) if locationID < 0: # New entry query = text(""" insert into locations (name, container_id) values (:name, :container) """) with db_engine.begin() as conn: conn.execute(query, { "name": request.form['name'], "container": request.form['container'] }) return jsonify(status="ok") else: # Modify entry query = text(""" update locations set name=:name, container_id=:container where id=:locationID """) with db_engine.begin() as conn: conn.execute(query, { "name": request.form['name'], "container": request.form['container'], "locationID": locationID }) return jsonify(status="ok") @app.route(baseURL + '/alterUser/', methods=['POST']) @requires_auth def alterUser(userID): userID = int(userID) if userID < 0: # New entry query = text(""" insert into users (username, password) values (:name, :password) """) with db_engine.begin() as conn: conn.execute(query, { "name": request.form['name'], "password": request.form['password'] }) return jsonify(status="ok") else: # Modify entry query = text(""" update users set username=:name, password=:password where id=:userID """) with db_engine.begin() as conn: conn.execute(query, { "name": request.form['name'], "password": request.form['password'], "userID": userID }) return jsonify(status="ok") @app.route(baseURL+'/getpartinfo/') def get_part_info(partID): s = 'select p.id,partno,description,notes, c.name || l.name as location_descriptor, location_id, container_id, datasheet from parts as p inner join locations as l on p.location_id = l.id inner join containers as c on l.container_id = c.id where p.id = :id;' with db_engine.connect() as conn: result = conn.execute(text(s), {"id": partID}) row = result.mappings().first() return jsonify(dict(row) if row else {}) @app.route(baseURL+'/query//') # TODO: maybe change AND to OR or maybe not def query(filter_dummy, query): filter = request.args.to_dict() keywords = query.split() # Default splits with spaces for i in range(len(keywords)): keywords[i] = '%' + keywords[i] + '%' kw_dict = {} for i in range(len(keywords)): kw_dict["kw" + str(i)] = keywords[i] s = 'select p.id,partno,description, c.name || l.name as location_descriptor from parts as p inner join locations as l on p.location_id = l.id inner join containers as c on l.container_id = c.id where ' if filter['l'] == 'true': s += '(' for i in range(len(keywords)): s += 'LOWER(c.name||l.name) like LOWER(:kw'+ str(i) +') AND ' s = s[:-5] s += ') OR ' if filter['p'] == 'true': s += '(' for i in range(len(keywords)): s += 'LOWER(partno) like LOWER(:kw'+ str(i) +') AND ' s = s[:-5] s += ') OR ' if filter['d'] == 'true': s += '(' for i in range(len(keywords)): s += 'LOWER(description) like LOWER(:kw'+ str(i) +') AND ' s = s[:-5] s += ') OR ' s = s[:-4] + ';' # s = text(s) # r = db_engine.execute(s, kw_dict) # l = [] # for row in r: # l.append(dict(row)) # r.close() with db_engine.connect() as conn: l = [dict(row) for row in conn.execute(text(s), kw_dict).mappings()] return json.dumps(l) @app.route(baseURL+'/map/') @app.route(baseURL+'/map//') def getMap(containerID, annotate=None): s = 'select map, overlay from containers where id = :id;' with db_engine.connect() as conn: row = conn.execute(text(s), {"id": containerID}).mappings().first() mapFile = row['map'] overlayFile = row['overlay'] try: mapImage = Image.open('maps/' + mapFile).convert("RGBA") overlayImage = Image.open('maps/overlays/' + overlayFile).convert("RGBA") except FileNotFoundError: return serveImage(Image.open("maps/404.png")) # if request.args.get('x') is not None and request.args.get('y') is not None: # x = int(request.args.get('x')) # y = int(request.args.get('y')) # pointer = Image.open('maps/here.png') # pointerLayer = Image.new("RGBA", mapimage.size) # width, height = pointer.size # pointerLayer.paste(pointer, (x - int(width/2), y-int(height/2))) mapimage = Image.alpha_composite(mapImage, overlayImage) if annotate=='a': try: annotationName = mapFile.split('.')[0] + '-annotated.' + mapFile.split('.')[1] annotationImage = Image.open('maps/' + annotationName).convert("RGBA") mapimage = Image.alpha_composite(mapimage, annotationImage) except FileNotFoundError: pass return serveImage(mapimage) @app.route(baseURL+'/getfile/') def getfile(filename): # if(re.match('^[\w\-_]+.[p|P][d|D][f|F]$', filename) == None): if(re.match(r'^[\w-]+\.pdf$', filename, re.IGNORECASE) == None): return 'No injections pls.' return send_from_directory('/srv/datasheets/', filename) @app.route(baseURL + '/alter/', methods=['POST']) @requires_auth def alter(partID): partID = int(partID) # ------------------------- # HANDLE FILE UPLOAD # ------------------------- if len(request.files) != 0: datasheet_file = request.files['datasheet-file'] datasheet_filename = secure_filename(datasheet_file.filename) i = 1 while os.path.isfile('/srv/datasheets/' + datasheet_filename): datasheet_filename = datasheet_filename[:-4] + str(i) + '.pdf' i += 1 datasheet_file.save('/srv/datasheets/' + datasheet_filename) datasheet_filename = 'http://elab.kth.se/parts/getfile/' + datasheet_filename else: try: datasheet_filename = request.form['datasheet-url'] except: datasheet_filename = None # ------------------------- # INSERT # ------------------------- if partID < 0: query = text(""" insert into parts (partno, description, datasheet, location_id, whoadded, notes) values (:partno, :description, :datasheet, :location_id, :user_id, :notes) returning id """) with db_engine.begin() as conn: result = conn.execute(query, { "partno": request.form['partno'], "description": request.form['description'], "datasheet": datasheet_filename, "location_id": request.form['location_id'], "notes": request.form['notes'], "user_id": session['uid'] }) new_id = result.mappings().first()["id"] return jsonify(status="ok", part_id=new_id) # ------------------------- # UPDATE # ------------------------- else: query = text(""" update parts set partno=:partno, description=:description, datasheet=:datasheet, location_id=:location_id, notes=:notes where id=:id returning id """) with db_engine.begin() as conn: result = conn.execute(query, { "partno": request.form['partno'], "description": request.form['description'], "datasheet": datasheet_filename, "location_id": request.form['location_id'], "notes": request.form['notes'], "id": partID }) new_id = result.mappings().first()["id"] return jsonify(status="ok", part_id=new_id) @app.route(baseURL + '/delete/') @requires_auth def delete(partID): partID = int(partID) if partID < 0: abort(400) query = text('delete from parts where id=:id') with db_engine.begin() as conn: conn.execute(query, {"id": partID}) return jsonify(status="ok") @app.route(baseURL + '/deleteLocation/') @requires_auth def deleteLocation(locationID): locationID = int(locationID) if locationID < 0: abort(400) query = text('delete from locations where id=:id') with db_engine.begin() as conn: conn.execute(query, {"id": locationID}) return jsonify(status="ok") @app.route(baseURL + '/deleteUser/') @requires_auth def deleteUser(userID): userID = int(userID) if userID < 0: abort(400) query = text('delete from users where id=:id') with db_engine.begin() as conn: conn.execute(query, {"id": userID}) return jsonify(status="ok") @app.route(baseURL+'/fetchOctopartSnippet/') def fetchOctopartSnippet(searchTerm): if octopartURL == '': return '{"result":"octopart integration not enabled"}' args = { 'q': searchTerm, 'start': '0', 'limit': 1 } data = requests.get(octopartURL, params=args) search_response = json.loads(data.text) result = '{"result":"no results. sorry :(("}' if search_response['hits']>0: try: result = '{"result":"ok", "snippet":"' + (search_response['results'][0]['snippet']) + '"}'; except TypeError: result = '{"result":"no results?"}' return result def connect(user, password, db, host='localhost', port=5432): '''Returns a connection and a metadata object''' # We connect with the help of the PostgreSQL URL url = 'postgresql://{}:{}@{}:{}/{}' url = url.format(user, password, host, port, db) # The return value of create_engine() is our connection object con = sqlalchemy.create_engine(url, client_encoding='utf8') # We then bind the connection to MetaData() meta = sqlalchemy.MetaData() meta.reflect(con) return con, meta if __name__ == '__main__': app.secret_key = 'asuiygdiahsdo[ainsfl]asfkjnb;asklnj' app.config['SESSION_TYPE'] = 'memcached' with open('admin.json') as f: postgres_credentials = json.load(f) db_engine, db_metadata = connect(postgres_credentials['username'], postgres_credentials['password'], 'parts_v3') parts = sqlalchemy.Table('parts', db_metadata) try: with open('octopartAPIkey.json') as f: j = json.load(f); if j['key'] != '': octopartURL = j['URL'] + j['key'] print ("Octopart credentials loaded.") else: raise FileNotFoundError except FileNotFoundError: print ("NO OCTOPART KEY FOUND. ABANDONING THAT PART") # Example query '''s = select([parts]).where(parts.c.notes != '') with db_engine.connect() as conn: r = conn.execute(text(query)) ''' app.run(host='127.0.0.1', port='5000', debug=False)