views.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. # -*- coding: utf-8 -*-
  2. from flask import request, g, redirect, url_for, abort, \
  3. render_template, flash, json, session, Response, Markup
  4. from flask.ext.babel import gettext as _
  5. from flask.ext.mail import Message
  6. import itsdangerous
  7. import docutils.core
  8. import ispformat.specs
  9. from datetime import date, time, timedelta, datetime
  10. from urlparse import urlunsplit
  11. import locale
  12. locale.setlocale(locale.LC_ALL, '')
  13. from time import time
  14. import os.path
  15. from . import forms
  16. from .constants import *
  17. from . import app, db, cache, mail
  18. from .models import ISP, ISPWhoosh
  19. from .crawler import WebValidator, PrettyValidator
  20. @app.route('/')
  21. def home():
  22. return render_template('index.html', active_button="home")
  23. @app.route('/isp/')
  24. def project_list():
  25. return render_template('project_list.html', projects=ISP.query.filter_by(is_disabled=False))
  26. # this needs to be cached
  27. @app.route('/isp/map_data.json', methods=['GET'])
  28. def isp_map_data():
  29. isps=ISP.query.filter_by(is_disabled=False)
  30. data=[]
  31. for isp in isps:
  32. d=dict(isp.json)
  33. for k in d.keys():
  34. if k not in ('name', 'shortname', 'coordinates'):
  35. del d[k]
  36. d['id']=isp.id
  37. d['ffdn_member']=isp.is_ffdn_member
  38. d['popup']=render_template('map_popup.html', isp=isp)
  39. data.append(d)
  40. return Response(json.dumps(data), mimetype='application/json')
  41. @app.route('/isp/<projectid>/covered_areas.json', methods=['GET'])
  42. def isp_covered_areas(projectid):
  43. p=ISP.query.filter_by(id=projectid, is_disabled=False).first()
  44. if not p:
  45. abort(404)
  46. return Response(json.dumps(p.json['coveredAreas']), mimetype='application/json')
  47. @app.route('/isp/<projectid>/')
  48. def project(projectid):
  49. p=ISP.query.filter_by(id=projectid, is_disabled=False).first()
  50. if not p:
  51. abort(404)
  52. return render_template('project_detail.html', project_row=p, project=p.json)
  53. @app.route('/isp/<projectid>/edit', methods=['GET', 'POST'])
  54. def edit_project(projectid):
  55. MAX_TOKEN_AGE=3600
  56. isp=ISP.query.filter_by(id=projectid, is_disabled=False).first_or_404()
  57. sess_token=session.get('edit_tokens', {}).get(isp.id)
  58. if 'token' in request.args:
  59. print session
  60. s = itsdangerous.URLSafeTimedSerializer(app.secret_key, salt='edit')
  61. try:
  62. r = s.loads(request.args['token'], max_age=MAX_TOKEN_AGE,
  63. return_timestamp=True)
  64. except:
  65. abort(403)
  66. if r[0] != isp.id:
  67. abort(403)
  68. tokens = session.setdefault('edit_tokens', {})
  69. tokens[r[0]] = r[1]
  70. # refresh page, without the token in the url
  71. return redirect(url_for('edit_project', projectid=r[0]))
  72. elif (sess_token is None or (datetime.utcnow()-sess_token).total_seconds() > MAX_TOKEN_AGE):
  73. return redirect(url_for('gen_edit_token', projectid=isp.id))
  74. if isp.is_local:
  75. form = forms.ProjectForm.edit_json(isp)
  76. if form.validate_on_submit():
  77. isp.name = form.name.data
  78. isp.shortname = form.shortname.data or None
  79. isp.json = form.to_json(isp.json)
  80. isp.tech_email = form.tech_email.data
  81. db.session.add(isp)
  82. db.session.commit()
  83. flash(_(u'Project modified'), 'info')
  84. return redirect(url_for('project', projectid=isp.id))
  85. return render_template('edit_project_form.html', form=form)
  86. else:
  87. form = forms.ProjectJSONForm(obj=isp)
  88. if form.validate_on_submit():
  89. isp.tech_email = form.tech_email.data
  90. u = list(form.json_url.data)
  91. u[2]='/isp.json' # new path
  92. url=urlunsplit(u)
  93. isp.json_url = url
  94. db.session.add(isp)
  95. db.session.commit()
  96. flash(_(u'Project modified'), 'info')
  97. return redirect(url_for('project', projectid=isp.id))
  98. return render_template('edit_project_json_form.html', form=form)
  99. @app.route('/isp/<projectid>/gen_edit_token', methods=['GET', 'POST'])
  100. def gen_edit_token(projectid):
  101. isp=ISP.query.filter_by(id=projectid, is_disabled=False).first_or_404()
  102. form = forms.RequestEditToken()
  103. if form.validate_on_submit(): # validated
  104. if form.tech_email.data == isp.tech_email:
  105. s = itsdangerous.URLSafeTimedSerializer(app.secret_key, salt='edit')
  106. token = s.dumps(isp.id)
  107. msg = Message("Edit request of your ISP", sender=app.config['EMAIL_SENDER'])
  108. msg.body="""
  109. Hello,
  110. You are receiving this message because your are listed as technical contact for "%s" on the FFDN ISP database.
  111. Someone asked to edit your ISP's data in our database. If it's not you, please ignore this message.
  112. To proceed to the editing form, please click on the following link:
  113. %s?token=%s
  114. Note: the link is only valid for one hour from the moment we send you this email.
  115. Thanks,
  116. The FFDN ISP Database team
  117. https://db.ffdn.org
  118. """.strip() % (isp.complete_name,
  119. url_for('edit_project', projectid=isp.id, _external=True),
  120. token)
  121. msg.add_recipient(isp.tech_email)
  122. mail.send(msg)
  123. # if the email provided is not the correct one, we still redirect
  124. flash(_(u'If you provided the correct email adress, '
  125. 'you must will receive a message shortly (check your spam folder)'), 'info')
  126. return redirect(url_for('project', projectid=isp.id))
  127. return render_template('gen_edit_token.html', form=form)
  128. @app.route('/add-a-project', methods=['GET'])
  129. def add_project():
  130. return render_template('add_project.html')
  131. @app.route('/isp/create/form', methods=['GET', 'POST'])
  132. def create_project_form():
  133. form = forms.ProjectForm()
  134. if form.validate_on_submit():
  135. isp=ISP()
  136. isp.name = form.name.data
  137. isp.shortname = form.shortname.data or None
  138. isp.tech_email = form.tech_email.data
  139. isp.json=form.to_json(isp.json)
  140. db.session.add(isp)
  141. db.session.commit()
  142. flash(_(u'Project created'), 'info')
  143. return redirect(url_for('project', projectid=isp.id))
  144. return render_template('add_project_form.html', form=form)
  145. @app.route('/isp/create/validator', methods=['GET'])
  146. def json_url_validator():
  147. if 'form_json' not in session or \
  148. session['form_json'].get('validated', False):
  149. abort(403)
  150. v=session['form_json'].get('validator')
  151. if v is not None:
  152. if v > time()-5:
  153. abort(429)
  154. else:
  155. session['form_json']['validator']=time()
  156. validator=WebValidator(session._get_current_object(), 'form_json')
  157. return Response(validator(session['form_json']['url']),
  158. mimetype="text/event-stream")
  159. @app.route('/isp/create', methods=['GET', 'POST'])
  160. def create_project_json():
  161. form = forms.ProjectJSONForm()
  162. if form.validate_on_submit():
  163. u=list(form.json_url.data)
  164. u[2]='/isp.json' # new path
  165. url=urlunsplit(u)
  166. session['form_json'] = {'url': url, 'tech_email': form.tech_email.data}
  167. return render_template('project_json_validator.html')
  168. return render_template('add_project_json_form.html', form=form)
  169. @app.route('/isp/create/confirm', methods=['POST'])
  170. def create_project_json_confirm():
  171. if 'form_json' in session and session['form_json'].get('validated', False):
  172. if not forms.is_url_unique(session['form_json']['url']):
  173. abort(409)
  174. jdict=session['form_json']['jdict']
  175. isp=ISP()
  176. isp.name=jdict['name']
  177. if 'shortname' in jdict:
  178. isp.shortname=jdict['shortname']
  179. isp.json_url=session['form_json']['url']
  180. isp.json=jdict
  181. isp.tech_email=session['form_json']['tech_email']
  182. isp.last_update_success=session['form_json']['last_update']
  183. isp.next_update=session['form_json']['next_update']
  184. isp.cache_info=session['form_json']['cache_info']
  185. del session['form_json']
  186. db.session.add(isp)
  187. db.session.commit()
  188. flash(_(u'Project created'), 'info')
  189. return redirect(url_for('project', projectid=isp.id))
  190. else:
  191. return redirect(url_for('create_project_json'))
  192. @app.route('/isp/reactivate-validator', methods=['GET'])
  193. def reactivate_validator():
  194. if 'form_reactivate' not in session or \
  195. session['form_reactivate'].get('validated', False):
  196. abort(403)
  197. p=ISP.query.get(session['form_reactivate']['isp_id'])
  198. if not p:
  199. abort(403)
  200. v=session['form_reactivate'].get('validator')
  201. if v is not None:
  202. if v > time()-5:
  203. abort(429)
  204. else:
  205. session['form_reactivate']['validator']=time()
  206. validator=PrettyValidator(session._get_current_object(), 'form_reactivate')
  207. return Response(validator(p.json_url, p.cache_info or {}),
  208. mimetype="text/event-stream")
  209. @app.route('/isp/<projectid>/reactivate', methods=['GET', 'POST'])
  210. def reactivate_isp(projectid):
  211. """
  212. Allow to reactivate an ISP after it has been disabled
  213. because of problems with the JSON file.
  214. """
  215. p=ISP.query.filter(ISP.id==projectid, ISP.is_disabled==False,
  216. ISP.update_error_strike>=3).first_or_404()
  217. if request.method == 'GET':
  218. key = request.args.get('key')
  219. try:
  220. s=itsdangerous.URLSafeSerializer(app.secret_key,
  221. salt='reactivate')
  222. d=s.loads(key)
  223. except Exception as e:
  224. abort(403)
  225. if (len(d) != 2 or d[0] != p.id or
  226. d[1] != str(p.last_update_attempt)):
  227. abort(403)
  228. session['form_reactivate'] = {'isp_id': p.id}
  229. return render_template('reactivate_validator.html', isp=p)
  230. else:
  231. if 'form_reactivate' not in session or \
  232. not session['form_reactivate'].get('validated', False):
  233. abort(409)
  234. p=ISP.query.get(session['form_reactivate']['isp_id'])
  235. p.json=session['form_reactivate']['jdict']
  236. p.cache_info=session['form_reactivate']['cache_info']
  237. p.last_update_attempt=datetime.now()
  238. p.last_update_success=p.last_update_attempt
  239. db.session.add(p)
  240. db.session.commit()
  241. flash(_(u'Automatic updates activated'), 'info')
  242. return redirect(url_for('project', projectid=p.id))
  243. @app.route('/search', methods=['GET', 'POST'])
  244. def search():
  245. terms=request.args.get('q')
  246. if not terms:
  247. return redirect(url_for('home'))
  248. res=ISPWhoosh.search(terms)
  249. return render_template('search_results.html', results=res, search_terms=terms)
  250. @app.route('/format', methods=['GET'])
  251. def format():
  252. parts = cache.get('format-spec')
  253. if parts is None:
  254. spec=open(ispformat.specs.versions[0.1]).read()
  255. overrides = {
  256. 'initial_header_level' : 3,
  257. }
  258. parts = docutils.core.publish_parts(spec,
  259. source_path=os.path.dirname(ispformat.specs.versions[0.1]),
  260. destination_path=None, writer_name='html',
  261. settings_overrides=overrides)
  262. cache.set('format-spec', parts, timeout=60*60*24)
  263. return render_template('format_spec.html', spec=Markup(parts['html_body']))
  264. #------
  265. # Filters
  266. @app.template_filter('step_to_label')
  267. def step_to_label(step):
  268. if step:
  269. return u"<a href='#' rel='tooltip' data-placement='right' title='" + STEPS[step] + "'><span class='badge badge-" + STEPS_LABELS[step] + "'>" + str(step) + "</span></a>"
  270. else:
  271. return u'-'
  272. @app.template_filter('stepname')
  273. def stepname(step):
  274. return STEPS[step]