Une base pour applications web basées sur Python/flask. TetaWebApp permet de coder rapidement n'importe quel type d'application web accédant ou non une base de données.
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.

tetawebapp.py 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8
  3. # Required modules
  4. import os
  5. import inspect
  6. import random
  7. import binascii
  8. import bcrypt
  9. from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
  10. from functools import wraps
  11. # Optionnal modules
  12. import psycopg2
  13. from flask_sqlalchemy import SQLAlchemy
  14. ########################################################################
  15. # App settings
  16. ########################################################################
  17. app = Flask(__name__)
  18. # Path to static files
  19. app.static_url_path='/static'
  20. # Set debug mode to False for production
  21. app.debug = True
  22. # Various configuration settings belong here (optionnal)
  23. app.config.from_pyfile('config.local.py')
  24. # Generate a new key: head -n 40 /dev/urandom | md5sum | cut -d' ' -f1
  25. app.secret_key = 'ce1d1c9ff0ff388a838b3a1e3207dd27'
  26. # Feel free to use SQLAlchemy (or not)
  27. db = SQLAlchemy(app)
  28. ########################################################################
  29. # Sample user database
  30. ########################################################################
  31. class Tetawebapp_users(db.Model):
  32. __tablename__ = 'tetawebapp_users'
  33. id = db.Column(db.Integer, primary_key=True)
  34. mail = db.Column(db.Text, nullable=False)
  35. password = db.Column(db.Text, nullable=False)
  36. name = db.Column(db.Text, nullable=False)
  37. ########################################################################
  38. # Menu and navigation management
  39. ########################################################################
  40. def get_menu(page):
  41. """ The main menu is a list of lists in the followin format:
  42. [unicode caption,
  43. {unicode URL endpoint: [unicode route, ...]},
  44. int 0]
  45. - The URL end point is the URL where to point to (href)
  46. - One of the routes MUST match the route called by request
  47. - The int 0 is used to determine which menu entry is actally called.
  48. The value MUST be 0."""
  49. menu = [[u'Home', {u'/': [u'/']}, 0],
  50. [u'Articles', {u'/articles': [u'/articles', u'/articles/<ID>']}, 0],
  51. [u'Basics', {u'/basics': [u'/basics']}, 0],
  52. [u'Inputs', {u'/inputs': [u'/inputs']}, 0],
  53. [u'Ajax', {u'/ajax': [u'/ajax']}, 0],
  54. [u'Database', {u'/database': [u'/database']}, 0],
  55. [u'Todo', {u'/todo': [u'/todo']}, 0],
  56. ]
  57. for item in menu:
  58. for url in item[1]:
  59. for route in item[1][url]:
  60. if route == page:
  61. item[2] = 1
  62. return menu
  63. # This should never happen
  64. return menu
  65. def get_navbar(page, selected):
  66. """ The horizontal navbar is a list of lists in the followin format:
  67. [unicode caption, {unicode URL endpoint: [unicode route, ...]}, int 0]
  68. - The URL end point is the URL where to point to (href)
  69. - One of the routes MUST match the route called by request
  70. - The int 0 is used to de """
  71. navbars = [[u'First article', {u'/articles/1': [u'/articles', u'/articles/<ID>']}, 0, 0],
  72. [u'Second article', {u'/articles/2': [u'/articles', u'/articles/<ID>']}, 0, 0],
  73. [u'Third article', {u'/articles/3': [u'/articles', u'/articles/<ID>']}, 0, 0]
  74. ]
  75. navbar = []
  76. for item in navbars:
  77. for url in item[1]:
  78. if url == selected:
  79. item[2] = 1
  80. for route in item[1][url]:
  81. if route == page:
  82. navbar.append(item)
  83. navbar[len(navbar) - 1][3] = 1
  84. return navbar
  85. ########################################################################
  86. # Session management
  87. ########################################################################
  88. def sync_session(request, session):
  89. """ Synchronize cookies with session """
  90. for key in request.cookies:
  91. session[key] = request.cookies[key].encode('utf8')
  92. def sync_cookies(response, session):
  93. """ Synchronize session with cookies """
  94. for key in session:
  95. response.set_cookie(key, value=str(session[key]))
  96. def check_session(func):
  97. """ Check if the session has required token cookie set.
  98. If not, redirects to the login page. """
  99. @wraps(func)
  100. def check(*args, **kwargs):
  101. try:
  102. if session['token'] == request.cookies['token'] and len(session['token']) > 0:
  103. return func(*args, **kwargs)
  104. else:
  105. session['token'] = ''
  106. response = app.make_response(render_template('login.html', message=''))
  107. sync_cookies(response, session)
  108. return response
  109. except KeyError:
  110. return render_template('login.html', message='')
  111. return check
  112. def check_login(login, password):
  113. """ Puts the login verification code here """
  114. password = password.encode('utf-8')
  115. hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
  116. stored_hash = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.password).first()
  117. if stored_hash:
  118. if bcrypt.checkpw(password, stored_hash[0].encode('utf-8')):
  119. return True
  120. return False
  121. def gen_token():
  122. """ Generate a random token to be stored in session and cookie """
  123. token = binascii.hexlify(os.urandom(42))
  124. return token
  125. ########################################################################
  126. # Routes:
  127. # -------
  128. # Except for the index function, the function name MUST have the same
  129. # name than the URL endpoint to make the menu work properly
  130. ########################################################################
  131. @app.errorhandler(404)
  132. def page_not_found(e):
  133. """ 404 not found """
  134. return render_template('error.html'), 404
  135. @app.route("/login", methods=['GET', 'POST'])
  136. def login():
  137. login = request.form.get('login')
  138. password = request.form.get('password')
  139. if check_login(login, password):
  140. # Generate and store a token in session
  141. session['token'] = gen_token()
  142. # Return user to index page
  143. page = '/'
  144. menu = get_menu(page)
  145. response = app.make_response(render_template('index.html', menu=menu))
  146. # Push token to cookie
  147. sync_cookies(response, session)
  148. return response
  149. # Credentials are not valid
  150. response = app.make_response(render_template('login.html', message='Invalid user or password'))
  151. session['token'] = ''
  152. sync_cookies(response, session)
  153. return response
  154. @app.route("/", methods=['GET', 'POST'])
  155. @check_session
  156. def index():
  157. """ Index page """
  158. page = str(request.url_rule)
  159. menu = get_menu(page)
  160. return render_template('index.html', menu=menu)
  161. @app.route("/articles", methods=['GET', 'POST'])
  162. @check_session
  163. def articles():
  164. """ Arcticles page """
  165. page = str(request.url_rule)
  166. menu = get_menu(page)
  167. navbar = get_navbar(page, '')
  168. return render_template('articles.html', menu=menu, navbar=navbar)
  169. @app.route("/articles/<ID>", methods=['GET', 'POST'])
  170. @check_session
  171. def articles_by_id(ID):
  172. """ Arcticles page """
  173. page = str(request.url_rule)
  174. menu = get_menu(page)
  175. selected = page.replace('<ID>', ID)
  176. navbar = get_navbar(page, selected)
  177. return render_template('articles_by_id.html', menu=menu, navbar=navbar, ID=ID)
  178. @app.route("/basics", methods=['GET', 'POST'])
  179. @check_session
  180. def basics():
  181. """ Basics page """
  182. page = str(request.url_rule)
  183. menu = get_menu(page)
  184. return render_template('basics.html', menu=menu)
  185. @app.route("/inputs", methods=['GET', 'POST'])
  186. @check_session
  187. def inputs():
  188. """ Show the input collection """
  189. page = str(request.url_rule)
  190. menu = get_menu(page)
  191. return render_template('inputs.html', menu=menu)
  192. @app.route("/ajax", methods=['GET', 'POST'])
  193. @check_session
  194. def ajax():
  195. """ Propose various AJAX tests """
  196. page = str(request.url_rule)
  197. menu = get_menu(page)
  198. return render_template('ajax.html', menu=menu)
  199. @app.route("/database", methods=['GET', 'POST'])
  200. @check_session
  201. def database():
  202. """ A blah on using databases """
  203. page = str(request.url_rule)
  204. menu = get_menu(page)
  205. return render_template('database.html', menu=menu)
  206. @app.route("/todo", methods=['GET', 'POST'])
  207. @check_session
  208. def todo():
  209. """ The famous TODO list """
  210. page = str(request.url_rule)
  211. menu = get_menu(page)
  212. return render_template('todo.html', menu=menu)
  213. ########################################################################
  214. # AJAX routes
  215. ########################################################################
  216. @app.route("/get_html_from_ajax", methods=['GET', 'POST'])
  217. @check_session
  218. def get_html_from_ajax():
  219. """ Return HTML code to an AJAX request
  220. It may generate a 404 http error for testing purpose """
  221. if int(random.random()*10) % 2:
  222. # Randomly generate 404 HTTP response
  223. return render_template('error.html'), 404
  224. return render_template('ajax_html.html')
  225. @app.route("/get_value_from_ajax", methods=['GET', 'POST'])
  226. @check_session
  227. def get_value_from_ajax():
  228. """ Return a randomly generated value to an AJAX request
  229. It may return an error code for testing purpose """
  230. err_code = 'TETA_ERR'
  231. RND = int(random.random()*10)
  232. if RND % 2:
  233. # Randomly generate error
  234. return err_code
  235. return str(RND)
  236. @app.route("/set_value_from_ajax/<value>", methods=['GET', 'POST'])
  237. @check_session
  238. def set_value_from_ajax(value):
  239. """ Accept a value from an AJAX request
  240. It may return an error code for testing purpose """
  241. err_code = 'TETA_ERR'
  242. if value != 'We Make Porn':
  243. return 'True'
  244. return err_code
  245. @app.route("/upload", methods=['POST'])
  246. @check_session
  247. def upload():
  248. """ Save a file from AJAX request
  249. Files are saved in UPLOADED_FILES_DEST (see config.local.py) """
  250. err_code = 'TETA_ERR'
  251. RND = int(random.random()*10)
  252. if RND % 2:
  253. # Randomly generate error
  254. print err_code
  255. return err_code
  256. uploaded_files = []
  257. if len(request.files) > 0 and request.files['files']:
  258. uploaded_files = request.files.getlist("files")
  259. print "Uploaded files:"
  260. for f in uploaded_files:
  261. print ' [+] %s [%s]' % (f.filename, f.content_type)
  262. # Before saving you should:
  263. # - Secure the filename
  264. # - Check file size
  265. # - Check content type
  266. f.save(os.path.join(app.config['UPLOADED_FILES_DEST'], f.filename))
  267. f.close()
  268. return "OK"
  269. ########################################################################
  270. # Main
  271. ########################################################################
  272. if __name__ == '__main__':
  273. app.run(host='0.0.0.0')