main.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from flask import Flask, request, session, g, redirect, url_for, abort, \
  4. render_template, flash
  5. import sqlite3
  6. from datetime import date, time, timedelta
  7. import time
  8. from contextlib import closing
  9. import locale
  10. locale.setlocale(locale.LC_ALL, '')
  11. import os
  12. import hashlib
  13. import smtplib
  14. import string
  15. DATABASE = '/tmp/cavote.db'
  16. SECRET_KEY = '{J@uRKO,xO-PK7B,jF?>iHbxLasF9s#zjOoy=+:'
  17. DEBUG = True
  18. TITLE = u"Cavote FFDN"
  19. EMAIL = '"' + TITLE + '"' + ' <' + u"cavote@ffdn.org" + '>'
  20. BASEURL = "http://localhost:5000"
  21. VERSION = "cavote 0.0.1"
  22. SMTP_SERVER = "10.33.33.30"
  23. app = Flask(__name__)
  24. app.config.from_object(__name__)
  25. def connect_db():
  26. return sqlite3.connect(app.config['DATABASE'])
  27. @app.before_request
  28. def before_request():
  29. g.db = connect_db()
  30. @app.teardown_request
  31. def teardown_request(exception):
  32. g.db.close()
  33. @app.route('/')
  34. def home():
  35. return render_template('index.html', active_button="home")
  36. def query_db(query, args=(), one=False):
  37. cur = g.db.execute(query, args)
  38. rv = [dict((cur.description[idx][0], value)
  39. for idx, value in enumerate(row)) for row in cur.fetchall()]
  40. return (rv[0] if rv else None) if one else rv
  41. def init_db():
  42. with closing(connect_db()) as db:
  43. with app.open_resource('schema.sql') as f:
  44. db.cursor().executescript(f.read())
  45. db.commit()
  46. #----------------
  47. # Login / Logout
  48. def valid_login(username, password):
  49. return query_db('select * from users where email = ? and password = ?', [username, crypt(password)], one=True)
  50. def connect_user(user):
  51. session['user'] = user
  52. del session['user']['password']
  53. del session['user']['key']
  54. def disconnect_user():
  55. session.pop('user', None)
  56. def crypt(passwd):
  57. return hashlib.sha1(passwd).hexdigest()
  58. def keygen():
  59. return hashlib.sha1(os.urandom(24)).hexdigest()
  60. def get_userid():
  61. user = session.get('user')
  62. if user is None:
  63. return -1
  64. elif user.get('id') < 0:
  65. return -1
  66. else:
  67. return user.get('id')
  68. @app.route('/login', methods=['GET', 'POST'])
  69. def login():
  70. if request.method == 'POST':
  71. user = valid_login(request.form['username'], request.form['password'])
  72. if user is None:
  73. flash('Invalid username/password', 'error')
  74. else:
  75. connect_user(user)
  76. flash('You were logged in', 'success')
  77. return redirect(url_for('home'))
  78. return render_template('login.html')
  79. @app.route('/logout')
  80. def logout():
  81. disconnect_user()
  82. flash('You were logged out', 'info')
  83. return redirect(url_for('home'))
  84. #-----------------
  85. # Change password
  86. @app.route('/password/lost', methods=['GET', 'POST'])
  87. def password_lost():
  88. info = None
  89. if request.method == 'POST':
  90. user = query_db('select * from users where email = ?', [request.form['email']], one=True)
  91. if user is None:
  92. flash('Cet utilisateur n\'existe pas !', 'error')
  93. else:
  94. key = keygen()
  95. g.db.execute('update users set key = ? where id = ?', [key, user['id']])
  96. g.db.commit()
  97. link = BASEURL + url_for('login_key', userid=user['id'], key=key)
  98. BODY = string.join((
  99. "From: %s" % EMAIL,
  100. "To: %s" % user['email'],
  101. "Subject: [Cavote] Password lost",
  102. "Date: %s" % time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()),
  103. "X-Mailer: %s" % VERSION,
  104. "",
  105. "You have lost your password.",
  106. "This link will log you without password.",
  107. "Don't forget to define a new one as soon a possible!",
  108. "This link will only work one time.",
  109. "",
  110. link,
  111. "",
  112. "If you think this mail is not for you, please ignore and delete it."
  113. ), "\r\n")
  114. server = smtplib.SMTP(SMTP_SERVER)
  115. server.sendmail(EMAIL, [user['email']], BODY)
  116. server.quit()
  117. flash(u"Un mail a été envoyé à " + user['email'], 'info')
  118. return render_template('password_lost.html')
  119. @app.route('/login/<userid>/<key>')
  120. def login_key(userid, key):
  121. user = query_db('select * from users where id = ? and key = ?', [userid, key], one=True)
  122. if user is None or user['key'] == "invalid":
  123. abort(404)
  124. else:
  125. connect_user(user)
  126. g.db.execute('update users set key = "invalid" where id = ?', [user['id']])
  127. g.db.commit()
  128. flash(u"Veuillez mettre à jour votre mot de passe", 'info')
  129. return redirect(url_for('user_password', userid=user['id']))
  130. #---------------
  131. # User settings
  132. @app.route('/user/<userid>')
  133. def user(userid):
  134. if int(userid) != get_userid():
  135. abort(401)
  136. groups = query_db('select * from groups join user_group on id=id_group where id_user = ?', userid)
  137. return render_template('user.html', groups=groups)
  138. @app.route('/user/settings/<userid>', methods=['GET', 'POST'])
  139. def user_edit(userid):
  140. if int(userid) != get_userid():
  141. abort(401)
  142. if request.method == 'POST':
  143. if query_db('select * from users where email=? and id!=?', [request.form['email'], userid], one=True) is None:
  144. if query_db('select * from users where name=? and id!=?', [request.form['name'], userid], one=True) is None:
  145. g.db.execute('update users set email = ?, name = ?, organization = ? where id = ?',
  146. [request.form['email'], request.form['name'], request.form['organization'], session['user']['id']])
  147. g.db.commit()
  148. disconnect_user()
  149. user = query_db('select * from users where id=?', [userid], one=True)
  150. if user is None:
  151. flash(u'Une erreur s\'est produite.', 'error')
  152. return redirect(url_for('login'))
  153. connect_user(user)
  154. flash(u'Votre profil a été mis à jour !', 'success')
  155. else:
  156. flash(u'Le nom ' + request.form['name'] + u' est déjà pris ! Veuillez en choisir un autre.', 'error')
  157. else:
  158. flash(u'Il existe déjà un compte pour cette adresse e-mail : ' + request.form['email'], 'error')
  159. return render_template('user_edit.html')
  160. @app.route('/user/password/<userid>', methods=['GET', 'POST'])
  161. def user_password(userid):
  162. if int(userid) != get_userid():
  163. abort(401)
  164. if request.method == 'POST':
  165. if request.form['password'] == request.form['password2']:
  166. g.db.execute('update users set password = ? where id = ?', [crypt(request.form['password']), session['user']['id']])
  167. g.db.commit()
  168. flash(u'Votre mot de passe a été mis à jour.', 'success')
  169. else:
  170. flash(u'Les mots de passe sont différents.', 'error')
  171. return render_template('user_edit.html')
  172. #------------
  173. # User admin
  174. @app.route('/admin/users')
  175. def admin_users():
  176. if not session.get('user').get('is_admin'):
  177. abort(401)
  178. tuples = query_db('select *, groups.name as groupname from (select *, id as userid, name as username from users join user_group on id=id_user order by id desc) join groups on id_group=groups.id')
  179. users = dict()
  180. for t in tuples:
  181. if t['userid'] in users:
  182. users[t['userid']]['groups'].append(t["groupname"])
  183. else:
  184. users[t['userid']] = dict()
  185. users[t['userid']]['userid'] = t['userid']
  186. users[t['userid']]['email'] = t['email']
  187. users[t['userid']]['username'] = t['username']
  188. users[t['userid']]['is_admin'] = t['is_admin']
  189. users[t['userid']]['groups'] = [t['groupname']]
  190. return render_template('admin_users.html', users=users.values())
  191. @app.route('/admin/users/add', methods=['GET', 'POST'])
  192. def admin_user_add():
  193. if not session.get('user').get('is_admin'):
  194. abort(401)
  195. if request.method == 'POST':
  196. if request.form['email']:
  197. # :TODO:maethor:120528: Check fields
  198. password = "toto" # :TODO:maethor:120528: Generate password
  199. admin = 0
  200. if 'admin' in request.form.keys():
  201. admin = 1
  202. g.db.execute('insert into users (email, name, organization, password, is_admin, key) values (?, ?, ?, ?, ?, "invalid")',
  203. [request.form['email'], request.form['username'], request.form['organization'], password, admin])
  204. g.db.commit()
  205. user = query_db('select * from users where email = ?', [request.form["email"]], one=True)
  206. if user:
  207. for group in request.form.getlist('groups'):
  208. if query_db('select id from groups where id = ?', group, one=True) is None:
  209. abort(401)
  210. g.db.execute('insert into user_group values (?, ?)', [user['id'], group])
  211. g.db.commit()
  212. # :TODO:maethor:120528: Send mail
  213. flash(u'Le nouvel utilisateur a été créé avec succès', 'success')
  214. return redirect(url_for('admin_users'))
  215. else:
  216. flash(u'Une erreur s\'est produite.', 'error')
  217. else:
  218. flash(u"Vous devez spécifier une adresse email.", 'error')
  219. groups = query_db('select * from groups where system=0')
  220. return render_template('admin_user_new.html', groups=groups)
  221. #-------------
  222. # Roles admin
  223. @app.route('/admin/groups')
  224. def admin_groups():
  225. if not session.get('user').get('is_admin'):
  226. abort(401)
  227. groups = query_db('select * from groups')
  228. return render_template('admin_groups.html', groups=groups)
  229. @app.route('/admin/groups/add', methods=['POST'])
  230. def admin_group_add():
  231. if not session.get('user').get('is_admin'):
  232. abort(401)
  233. if request.method == 'POST':
  234. if request.form['name']:
  235. g.db.execute('insert into groups (name) values (?)', [request.form['name']])
  236. g.db.commit()
  237. else:
  238. flash(u"Vous devez spécifier un nom.", "error")
  239. return redirect(url_for('admin_groups'))
  240. @app.route('/admin/groups/delete/<idgroup>')
  241. def admin_group_del(idgroup):
  242. if not session.get('user').get('is_admin'):
  243. abort(401)
  244. group = query_db('select * from groups where id = ?', [idgroup], one=True)
  245. if group is None:
  246. abort(404)
  247. if group['system']:
  248. abort(401)
  249. g.db.execute('delete from groups where id = ?', [idgroup])
  250. g.db.commit()
  251. return redirect(url_for('admin_groups'))
  252. #------------
  253. # Votes list
  254. @app.route('/votes/<votes>')
  255. def votes(votes):
  256. today = date.today()
  257. active_button = votes
  258. basequery = 'select *, votes.id as voteid, groups.name as groupname from votes join groups on groups.id = id_group where is_open=1'
  259. if votes == 'all':
  260. votes = query_db(basequery + ' order by id desc')
  261. elif votes == 'archive':
  262. votes = query_db(basequery + ' and date_end < (?) order by id desc', [today])
  263. elif votes == 'current':
  264. votes = query_db(basequery + ' and date_end >= (?) order by id desc', [today])
  265. else:
  266. abort(404)
  267. return render_template('votes.html', votes=votes, active_button=active_button)
  268. #------
  269. # Vote
  270. def can_see_vote(idvote, iduser=-1):
  271. vote = query_db('select * from votes where id=?', [idvote], one=True)
  272. if vote is None:
  273. abort(404)
  274. if not vote['is_public']:
  275. user = query_db('select * from users where id=?', [iduser], one=True)
  276. if user is None: # :TODO:maethor:120604: Check others things (groups)
  277. return False
  278. return True
  279. def can_vote(idvote, iduser=-1):
  280. if iduser > 0:
  281. if can_see_vote(idvote, iduser):
  282. if not has_voted(idvote, iduser):
  283. return True # :TODO:maethor:120529: Check others things (groups)
  284. return False
  285. def has_voted(idvote, iduser=-1):
  286. vote = query_db('select * from user_choice join choices on id_choice=choices.id where id_vote = ? and id_user = ?', [idvote, iduser], one=True)
  287. return (vote is not None)
  288. @app.route('/vote/<idvote>', methods=['GET', 'POST'])
  289. def vote(idvote):
  290. vote = query_db('select votes.*, groups.name as groupname from votes join groups on groups.id=votes.id_group where votes.id=?', [idvote], one=True)
  291. if vote is None:
  292. abort(404)
  293. if can_see_vote(idvote, get_userid()):
  294. if request.method == 'POST':
  295. if can_vote(idvote, get_userid()):
  296. choices = query_db('select name, id from choices where id_vote=?', [idvote])
  297. for choice in choices:
  298. if str(choice['id']) in request.form.keys():
  299. g.db.execute('insert into user_choice (id_user, id_choice) values (?, ?)',
  300. [session.get('user').get('id'), choice['id']])
  301. g.db.commit()
  302. if vote['is_multiplechoice'] == 0:
  303. break
  304. else:
  305. abort(401)
  306. tuples = query_db('select choiceid, choicename, users.id as userid, users.name as username from (select choices.id as choiceid, choices.name as choicename, id_user as userid from choices join user_choice on choices.id = user_choice.id_choice where id_vote = ?) join users on userid = users.id', [idvote])
  307. users = dict()
  308. for t in tuples:
  309. if t['userid'] in users:
  310. users[t['userid']]['choices'].append(t['choiceid'])
  311. else:
  312. users[t['userid']] = dict()
  313. users[t['userid']]['userid'] = t['userid']
  314. users[t['userid']]['username'] = t['username']
  315. users[t['userid']]['choices'] = [t['choiceid']]
  316. choices = query_db('select choices.name, choices.id, choices.name, choices.id_vote, count(id_choice) as nb from choices left join user_choice on id_choice = choices.id where id_vote = ? group by id_choice, name, id_vote order by id', [idvote])
  317. attachments = query_db('select * from attachments where id_vote=?', [idvote])
  318. return render_template('vote.html', vote=vote, attachments=attachments, choices=choices, users=users.values(), can_vote=can_vote(idvote, get_userid()))
  319. flash('Vous n\'avez pas le droit de voir ce vote, désolé.')
  320. return(url_for('home'))
  321. @app.route('/vote/deletechoices/<idvote>/<iduser>')
  322. def vote_deletechoices(idvote, iduser):
  323. if int(iduser) != get_userid():
  324. abort(401)
  325. g.db.execute('delete from user_choice where id_user = ? and id_choice in (select id from choices where id_vote = ?)',
  326. [iduser, idvote])
  327. g.db.commit()
  328. return redirect(url_for('vote', idvote=idvote))
  329. #-------------
  330. # Votes admin
  331. @app.route('/admin/votes/list')
  332. def admin_votes():
  333. if not session.get('user').get('is_admin'):
  334. abort(401)
  335. votes = query_db('select *, votes.id as voteid, groups.name as groupname from votes join groups on groups.id=votes.id_group order by id desc')
  336. return render_template('admin_votes.html', votes=votes)
  337. @app.route('/admin/votes/add', methods=['GET', 'POST'])
  338. def admin_vote_add():
  339. if not session.get('user').get('is_admin'):
  340. abort(401)
  341. if request.method == 'POST':
  342. if request.form['title']:
  343. if query_db('select * from votes where title = ?', [request.form['title']], one=True) is None:
  344. date_begin = date.today()
  345. date_end = date.today() + timedelta(days=int(request.form['days']))
  346. transparent = 0
  347. public = 0
  348. multiplechoice = 0
  349. if 'transparent' in request.form.keys():
  350. transparent = 1
  351. if 'public' in request.form.keys():
  352. public = 1
  353. if 'multiplechoice' in request.form.keys():
  354. multiplechoice = 1
  355. group = query_db('select id from groups where name = ?', [request.form['group']], one=True)
  356. if group is None:
  357. group[id] = 1
  358. g.db.execute('insert into votes (title, description, category, date_begin, date_end, is_transparent, is_public, is_multiplechoice, id_group, id_author) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
  359. [request.form['title'], request.form['description'], request.form['category'], date_begin, date_end, transparent, public, multiplechoice, group['id'], session['user']['id']])
  360. g.db.commit()
  361. vote = query_db('select * from votes where title = ? and date_begin = ? order by id desc',
  362. [request.form['title'], date_begin], one=True)
  363. if vote is None:
  364. flash(u'Une erreur est survenue !', 'error')
  365. return redirect(url_for('home'))
  366. else:
  367. flash(u"Le vote a été créé", 'info')
  368. return redirect(url_for('admin_vote_edit', voteid=vote['id']))
  369. else:
  370. flash(u'Le titre que vous avez choisi est déjà pris.', 'error')
  371. else:
  372. flash(u'Vous devez spécifier un titre.', 'error')
  373. groups = query_db('select * from groups')
  374. return render_template('admin_vote_new.html', groups=groups)
  375. @app.route('/admin/votes/edit/<voteid>', methods=['GET', 'POST'])
  376. def admin_vote_edit(voteid):
  377. if not session.get('user').get('is_admin'):
  378. abort(401)
  379. vote = query_db('select * from votes where id = ?', [voteid], one=True)
  380. if vote is None:
  381. abort(404)
  382. if request.method == 'POST':
  383. if request.form['title']:
  384. # :TODO:maethor:120529: Calculer date_begin pour pouvoir y ajouter duration et obtenir date_end
  385. transparent = 0
  386. public = 0
  387. if 'transparent' in request.form.keys():
  388. transparent = 1
  389. if 'public' in request.form.keys():
  390. public = 1
  391. isopen = 0
  392. if request.form['status'] == 'Ouvert':
  393. choices = query_db('select id_vote, count(*) as nb from choices where id_vote = ? group by id_vote', [voteid], one=True)
  394. if choices is not None and choices['nb'] >= 2:
  395. isopen = 1
  396. else:
  397. flash(u'Vous devez proposer au moins deux choix pour ouvrir le vote.', 'error')
  398. g.db.execute('update votes set title = ?, description = ?, category = ?, is_transparent = ?, is_public = ?, is_open = ? where id = ?',
  399. [request.form['title'], request.form['description'], request.form['category'], transparent, public, isopen, voteid])
  400. g.db.commit()
  401. vote = query_db('select * from votes where id = ?', [voteid], one=True)
  402. flash(u"Le vote a bien été mis à jour.", "success")
  403. else:
  404. flash(u'Vous devez spécifier un titre.', 'error')
  405. # :TODO:maethor:120529: Calculer la durée du vote (différence date_end - date_begin)
  406. vote['duration'] = 15
  407. group = query_db('select name from groups where id = ?', [vote['id_group']], one=True)
  408. choices = query_db('select * from choices where id_vote = ?', [voteid])
  409. attachments = query_db('select * from attachments where id_vote = ?', [voteid])
  410. return render_template('admin_vote_edit.html', vote=vote, group=group, choices=choices, attachments=attachments)
  411. @app.route('/admin/votes/addchoice/<voteid>', methods=['POST'])
  412. def admin_vote_addchoice(voteid):
  413. if not session.get('user').get('is_admin'):
  414. abort(401)
  415. vote = query_db('select * from votes where id = ?', [voteid], one=True)
  416. if vote is None:
  417. abort(404)
  418. g.db.execute('insert into choices (name, id_vote) values (?, ?)', [request.form['title'], voteid])
  419. g.db.commit()
  420. return redirect(url_for('admin_vote_edit', voteid=voteid))
  421. @app.route('/admin/votes/editchoice/<voteid>/<choiceid>', methods=['POST', 'DELETE'])
  422. def admin_vote_editchoice(voteid, choiceid):
  423. if not session.get('user').get('is_admin'):
  424. abort(401)
  425. choice = query_db('select * from choices where id = ? and id_vote = ?', [choiceid, voteid], one=True)
  426. if choice is None:
  427. abort(404)
  428. if request.method == 'POST':
  429. g.db.execute('update choices set name=? where id = ? and id_vote = ?', [request.form['title'], choiceid, voteid])
  430. g.db.commit()
  431. return redirect(url_for('admin_vote_edit', voteid=voteid))
  432. @app.route('/admin/votes/deletechoice/<voteid>/<choiceid>')
  433. def admin_vote_deletechoice(voteid, choiceid):
  434. if not session.get('user').get('is_admin'):
  435. abort(401)
  436. choice = query_db('select * from choices where id = ? and id_vote = ?', [choiceid, voteid], one=True)
  437. if choice is None:
  438. abort(404)
  439. g.db.execute('delete from choices where id = ? and id_vote = ?', [choiceid, voteid])
  440. g.db.commit()
  441. choices = query_db('select id_vote, count(*) as nb from choices where id_vote = ? group by id_vote', [voteid], one=True)
  442. if choices is None or choices['nb'] < 2:
  443. g.db.execute('update votes set is_open=0 where id = ?', [voteid])
  444. g.db.commit()
  445. flash(u'Attention ! Il y a moins de deux choix. Le vote a été fermé.', 'error')
  446. return redirect(url_for('admin_vote_edit', voteid=voteid))
  447. @app.route('/admin/votes/addattachment/<voteid>', methods=['POST'])
  448. def admin_vote_addattachment(voteid):
  449. if not session.get('user').get('is_admin'):
  450. abort(401)
  451. vote = query_db('select * from votes where id = ?', [voteid], one=True)
  452. if vote is None:
  453. abort(404)
  454. g.db.execute('insert into attachments (url, id_vote) values (?, ?)', [request.form['url'], voteid])
  455. g.db.commit()
  456. return redirect(url_for('admin_vote_edit', voteid=voteid))
  457. @app.route('/admin/votes/deleteattachment/<voteid>/<attachmentid>')
  458. def admin_vote_deleteattachment(voteid, attachmentid):
  459. if not session.get('user').get('is_admin'):
  460. abort(401)
  461. attachment = query_db('select * from attachments where id = ? and id_vote = ?', [attachmentid, voteid], one=True)
  462. if attachment is None:
  463. abort(404)
  464. g.db.execute('delete from attachments where id = ? and id_vote = ?', [attachmentid, voteid])
  465. g.db.commit()
  466. return redirect(url_for('admin_vote_edit', voteid=voteid))
  467. #------
  468. # Main
  469. if __name__ == '__main__':
  470. app.run()