Electronic stock management. -- not updated anymore
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

stockazng.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. from pprint import pprint as pp
  2. from flask import Flask, render_template, g, send_from_directory, request, redirect, url_for
  3. from flask.ext.security import Security, login_required, current_user
  4. from flask.ext.script import Manager
  5. from flask.ext.migrate import Migrate, MigrateCommand
  6. from flask.ext.mail import Mail
  7. from flask_bootstrap import Bootstrap
  8. from flask_debugtoolbar import DebugToolbarExtension
  9. from flask.ext.uploads import UploadSet, DOCUMENTS, IMAGES, TEXT, DATA, configure_uploads
  10. from models import db, user_datastore, Part, PartAttachment, ManufacturerLogo, BARCODE_TYPES, Tag
  11. from controllers.main import bp_main
  12. from controllers.ajax_parts import bp_ajax_parts
  13. from controllers.views import bp_views
  14. from controllers.parts import bp_parts
  15. from controllers.edits import bp_edits
  16. from controllers.users import bp_users
  17. from controllers.ajax_edits import bp_ajax_edits
  18. from controllers.api import bp_api
  19. from controllers.autocompletes import bp_autocompletes
  20. from controllers.projects import bp_projects
  21. import texttable
  22. import magic
  23. from dbseed import make_db_seed
  24. from forms import ExtendedRegisterForm, ManufacturerLogoForm
  25. from crons import c_cron_cache, c_cron_thumbs
  26. from posixpath import basename
  27. from datetime import datetime
  28. from hashlib import sha1
  29. import os
  30. import subprocess
  31. import logging
  32. from logging.handlers import RotatingFileHandler
  33. __VERSION__ = "0.0.1"
  34. # App Configuration
  35. app = Flask(__name__)
  36. Bootstrap(app)
  37. app.config.from_pyfile("config.py")
  38. # Logging
  39. if not app.debug:
  40. formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')
  41. file_handler = RotatingFileHandler("%s/errors_app.log" % os.getcwd(), 'a', 1000000, 1)
  42. file_handler.setLevel(logging.DEBUG)
  43. file_handler.setFormatter(formatter)
  44. app.logger.addHandler(file_handler)
  45. toolbar = DebugToolbarExtension(app)
  46. mail = Mail(app)
  47. db.init_app(app)
  48. migrate = Migrate(app, db)
  49. manager = Manager(app)
  50. # Setup Flask-Security
  51. security = Security(app, user_datastore,
  52. register_form=ExtendedRegisterForm)
  53. allowed_extensions = DOCUMENTS + IMAGES + DATA + TEXT + tuple('pdf'.split(" "))
  54. parts_attachments = UploadSet('attachments', extensions=allowed_extensions)
  55. manufacturers_logos = UploadSet('attachments', extensions=IMAGES)
  56. configure_uploads(app, parts_attachments)
  57. configure_uploads(app, manufacturers_logos)
  58. git_version = ""
  59. gitpath = os.path.join(os.getcwd(), ".git")
  60. if os.path.isdir(gitpath):
  61. git_version = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'])
  62. if git_version:
  63. git_version = git_version.strip()
  64. @app.before_request
  65. def before_request():
  66. g.cfg = {
  67. 'PAG_MANUFACTURERS': app.config['PAG_MANUFACTURERS'],
  68. 'PAG_DISTRIBUTORS': app.config['PAG_DISTRIBUTORS'],
  69. 'PAG_FOOTPRINTS': app.config['PAG_FOOTPRINTS'],
  70. 'PAG_PARTS_UNITS': app.config['PAG_PARTS_UNITS'],
  71. 'PAG_PROJECTS': app.config['PAG_PROJECTS'],
  72. 'PAG_STORAGE': app.config['PAG_STORAGE'],
  73. 'PAG_PARTS_PARAMETERS': app.config['PAG_PARTS_PARAMETERS'],
  74. 'PAG_PARTS_DISTRIB': app.config['PAG_PARTS_DISTRIB'],
  75. 'PAG_PARTS_MANUF': app.config['PAG_PARTS_MANUF'],
  76. 'PAG_PARTS': app.config['PAG_PARTS'],
  77. 'PAG_UNITS': app.config['PAG_UNITS'],
  78. 'STOCKAZ_VERSION': "{0} ({1})".format(__VERSION__, git_version),
  79. 'STOCKAZ_PUBLIC': app.config['STOCKAZ_PUBLIC']
  80. }
  81. g.current_user = current_user
  82. g.tags = db.session.query(Tag).filter(Tag.parts.any())
  83. app.register_blueprint(bp_main)
  84. app.register_blueprint(bp_views)
  85. app.register_blueprint(bp_parts)
  86. app.register_blueprint(bp_edits)
  87. app.register_blueprint(bp_users)
  88. app.register_blueprint(bp_ajax_edits)
  89. app.register_blueprint(bp_ajax_parts)
  90. app.register_blueprint(bp_api)
  91. app.register_blueprint(bp_autocompletes)
  92. app.register_blueprint(bp_projects)
  93. # I'm unable to split into blueprint of bp_main, so here it is...
  94. @app.route('/parts/<int:pid>/attachment/new', methods=['POST'])
  95. @login_required
  96. def parts_attachment_new(pid):
  97. part = Part.query.get_or_404(pid)
  98. if part.attachments.filter(PartAttachment.simple_type == 'IMAGE').count() <= 0:
  99. _first = True
  100. else:
  101. _first = False
  102. if 'webcam' in request.files:
  103. _file = request.files['webcam']
  104. _description = ''
  105. elif 'attachment' in request.files:
  106. _file = request.files['attachment']
  107. _description = request.form['att_description']
  108. else:
  109. _file = None
  110. _description = request.form['att_description']
  111. if _file and _file.filename != '':
  112. # Calculate destination file hash
  113. _str = "{0} {1} {2}".format(part.name,
  114. _file.filename,
  115. str(datetime.now()))
  116. _hash = sha1()
  117. _hash.update(_str)
  118. _hashi = _hash.hexdigest()
  119. filename = parts_attachments.save(storage=_file,
  120. folder=str(Part.BARCODE_TYPE),
  121. name="{0}.".format(_hashi))
  122. fname = os.path.join(app.config['UPLOADS_DEFAULT_DEST'], 'attachments', filename)
  123. filesize = os.stat(fname).st_size
  124. _magic = magic.from_file(fname, mime=True).decode('unicode_escape')
  125. if _magic == "application/pdf":
  126. st = "PDF"
  127. elif _magic.startswith("image/"):
  128. st = "IMAGE"
  129. elif _magic.startswith("text/"):
  130. st = "TEXT"
  131. else:
  132. st = "UNKNOWN"
  133. pa = PartAttachment(filename=basename(filename),
  134. orig_filename=_file.filename,
  135. mimetype=_magic,
  136. simple_type=st,
  137. hash=_hashi,
  138. part=part,
  139. filesize=filesize,
  140. description=_description,
  141. default=_first)
  142. db.session.add(pa)
  143. db.session.commit()
  144. return redirect(url_for("bp_parts.parts_edit", pid=pid, _anchor="AddPartTabAttachments"))
  145. else:
  146. # We are threating remote URI
  147. pa = PartAttachment(part=part,
  148. description=_description,
  149. is_remote=True,
  150. remote_uri=request.form['remote_uri'],
  151. remote_cached=False,
  152. remote_processed=False)
  153. db.session.add(pa)
  154. db.session.commit()
  155. return redirect(url_for("bp_parts.parts_edit", pid=pid, _anchor="AddPartTabAttachments"))
  156. @app.route('/edit/manufacturers/logos', methods=['GET', 'POST'])
  157. @login_required
  158. def new_manufacturers_logos():
  159. logos = ManufacturerLogo.query.all()
  160. form = ManufacturerLogoForm()
  161. if form.validate_on_submit():
  162. if 'logo' in request.files:
  163. _file = request.files['logo']
  164. if _file and _file.filename != '':
  165. # Calculate destination file hash
  166. _str = "{0} {1}".format(_file.filename,
  167. str(datetime.now()))
  168. _hash = sha1()
  169. _hash.update(_str)
  170. _hashi = _hash.hexdigest()
  171. filename = manufacturers_logos.save(storage=_file,
  172. folder=str(ManufacturerLogo.BARCODE_TYPE),
  173. name="{0}.".format(_hashi))
  174. fname = os.path.join(app.config['UPLOADS_DEFAULT_DEST'], 'attachments', filename)
  175. filesize = os.stat(fname).st_size
  176. a = ManufacturerLogo(manufacturer_id=form.manufacturer.data.id,
  177. filename=basename(filename),
  178. orig_filename=_file.filename,
  179. hash=_hashi,
  180. filesize=filesize)
  181. db.session.add(a)
  182. db.session.commit()
  183. return redirect(url_for('new_manufacturers_logos'))
  184. return render_template('edit/manufacturers_logos.jinja2', logos=logos, form=form)
  185. # Used in development
  186. @app.route('/uploads/<path:stuff>', methods=['GET'])
  187. def get_uploads_stuff(stuff):
  188. print("Get {0} from {1}".format(stuff, app.config['UPLOADS_DEFAULT_DEST']))
  189. return send_from_directory(app.config['UPLOADS_DEFAULT_DEST'], stuff, as_attachment=False)
  190. @app.errorhandler(404)
  191. def page_not_found(e):
  192. pcfg = {"title": "Whoops, something failed.",
  193. "error": 404, "message": "Page not found", "e": e}
  194. return render_template('error_page.jinja2', pcfg=pcfg), 404
  195. @app.errorhandler(403)
  196. def err_forbidden(e):
  197. pcfg = {"title": "Whoops, something failed.",
  198. "error": 403, "message": "Access forbidden", "e": e}
  199. return render_template('error_page.jinja2', pcfg=pcfg), 403
  200. @app.errorhandler(410)
  201. def err_gone(e):
  202. pcfg = {"title": "Whoops, something failed.",
  203. "error": 410, "message": "Gone", "e": e}
  204. return render_template('error_page.jinja2', pcfg=pcfg), 410
  205. if not app.debug:
  206. @app.errorhandler(500)
  207. def err_failed(e):
  208. pcfg = {"title": "Whoops, something failed.", "error": 500, "message": "Something is broken", "e": e}
  209. return render_template('error_page.jinja2', pcfg=pcfg), 500
  210. # Other commands
  211. @manager.command
  212. def dump_routes():
  213. """Dump all routes of defined app"""
  214. table = texttable.Texttable()
  215. table.set_deco(texttable.Texttable().HEADER)
  216. table.set_cols_dtype(['t', 't', 't'])
  217. table.set_cols_align(["l", "l", "l"])
  218. table.set_cols_width([60, 30, 90])
  219. table.add_rows([["Prefix", "Verb", "URI Pattern"]])
  220. for rule in sorted(app.url_map.iter_rules(), key=lambda x: x.match_compare_key()):
  221. methods = ','.join(rule.methods)
  222. table.add_row([rule.endpoint, methods, rule])
  223. print(table.draw())
  224. @manager.command
  225. def config():
  226. """Dump config"""
  227. pp(app.config)
  228. @manager.command
  229. def db_seed():
  230. """Seed database with default content"""
  231. make_db_seed(db)
  232. @manager.command
  233. def cron_thumbs():
  234. """ Create thumbnails """
  235. c_cron_thumbs(app.config, db)
  236. @manager.command
  237. def cron_cache():
  238. """ Create locale caches for remote_uri's"""
  239. c_cron_cache(app.config, db)
  240. @manager.command
  241. def mkdirs():
  242. """ Create needed dirs if doesn't exists """
  243. def mkdir(path):
  244. if not os.path.isdir(path):
  245. print("Creating: %s" % path)
  246. os.makedirs(path)
  247. mkdir(app.config['UPLOADS_DEFAULT_DEST'])
  248. mkdir(os.path.join(app.config['UPLOADS_DEFAULT_DEST'], 'attachments'))
  249. for i in BARCODE_TYPES:
  250. for ii in ['attachments', 'barcodes', 'thumbs', 'cache']:
  251. mkdir(os.path.join(app.config['UPLOADS_DEFAULT_DEST'], ii, str(i['id'])))
  252. @manager.command
  253. def tags_untag():
  254. """ Set tags_slug as 'untagged' if no associated tags """
  255. for i in Part.query.all():
  256. if len(i.tags) <= 0:
  257. i.tags_slug = "untagged"
  258. db.session.commit()
  259. manager.add_command('db', MigrateCommand)
  260. if __name__ == '__main__':
  261. manager.run()