backend.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import sys
  5. import sqlite3
  6. import urlparse
  7. import datetime
  8. import json
  9. from email import utils
  10. from os.path import join, dirname
  11. from bottle import route, run, static_file, request, template, FormsDict, redirect, response
  12. ORIENTATIONS = (
  13. ('N', 'Nord'),
  14. ('NO', 'Nord-Ouest'),
  15. ('O', 'Ouest'),
  16. ('SO', 'Sud-Ouest'),
  17. ('S', 'Sud'),
  18. ('SE', 'Sud-Est'),
  19. ('E', 'Est'),
  20. ('NE', 'Nord-Est'),
  21. )
  22. TABLE_NAME = 'contribs'
  23. DB_FILENAME = join(dirname(__file__), 'db.sqlite3')
  24. DB = sqlite3.connect(DB_FILENAME)
  25. DB_COLS = (
  26. ('id', 'INTEGER PRIMARY KEY'),
  27. ('name', 'TEXT'),
  28. ('contrib_type', 'TEXT'),
  29. ('latitude', 'REAL'),
  30. ('longitude', 'REAL'),
  31. ('phone', 'TEXT'),
  32. ('email', 'TEXT'),
  33. ('access_type', 'TEXT'),
  34. ('bandwidth', 'REAL'),
  35. ('share_part', 'REAL'),
  36. ('floor', 'INTEGER'),
  37. ('floor_total', 'INTEGER'),
  38. ('orientations', 'TEXT'),
  39. ('roof', 'INTEGER'),
  40. ('comment', 'TEXT'),
  41. ('privacy_name', 'INTEGER'),
  42. ('privacy_email', 'INTEGER'),
  43. ('privacy_coordinates', 'INTEGER'),
  44. ('privacy_place_details', 'INTEGER'),
  45. ('privacy_comment', 'INTEGER'),
  46. ('date', 'TEXT'),
  47. )
  48. @route('/')
  49. def home():
  50. redirect("/wifi-form")
  51. @route('/wifi-form')
  52. def show_wifi_form():
  53. return template('wifi-form', errors=None, data = FormsDict(),
  54. orientations=ORIENTATIONS)
  55. def create_tabble(db, name, columns):
  56. col_defs = ','.join(['{} {}'.format(*i) for i in columns])
  57. db.execute('CREATE TABLE {} ({})'.format(name, col_defs))
  58. def save_to_db(db, dic):
  59. # SQLite is picky about encoding else
  60. tosave = {bytes(k):v.decode('utf-8') if isinstance(v,str) else v for k,v in dic.items()}
  61. tosave['date'] = utils.formatdate()
  62. return db.execute("""
  63. INSERT INTO {}
  64. (name, contrib_type, latitude, longitude, phone, email, access_type, bandwidth, share_part, floor, floor_total, orientations, roof, comment,
  65. privacy_name, privacy_email, privacy_place_details, privacy_coordinates, privacy_comment, date)
  66. VALUES (:name, :contrib_type, :latitude, :longitude, :phone, :email, :access_type, :bandwidth, :share_part, :floor, :floor_total, :orientations, :roof, :comment,
  67. :privacy_name, :privacy_email, :privacy_place_details, :privacy_coordinates, :privacy_comment, :date)
  68. """.format(TABLE_NAME), tosave)
  69. @route('/wifi-form', method='POST')
  70. def submit_wifi_form():
  71. required = ('name', 'contrib-type',
  72. 'latitude', 'longitude')
  73. required_or = (('email', 'phone'),)
  74. required_if = (
  75. ('contrib-type', 'share',('access-type', 'bandwidth',
  76. 'share-part')),
  77. )
  78. field_names = {
  79. 'name' : 'Nom/Pseudo',
  80. 'contrib-type': 'Type de participation',
  81. 'latitude' : 'Localisation',
  82. 'longitude' : 'Localisation',
  83. 'phone' : 'Téléphone',
  84. 'email' : 'Email',
  85. 'access-type' : 'Type de connexion',
  86. 'bandwidth' : 'Bande passante',
  87. 'share-part' : 'Débit partagé',
  88. 'floor' : 'Étage',
  89. 'floor_total' : 'Nombre d\'étages total'
  90. }
  91. errors = []
  92. for name in required:
  93. if (not request.forms.get(name)):
  94. errors.append((field_names[name], 'ce champ est requis'))
  95. for name_list in required_or:
  96. filleds = [True for name in name_list if request.forms.get(name)]
  97. if len(filleds) <= 0:
  98. errors.append((
  99. ' ou '.join([field_names[i] for i in name_list]),
  100. 'au moins un des de ces champs est requis'))
  101. for key, value, fields in required_if:
  102. if request.forms.get('key') == value:
  103. for name in fields:
  104. if not request.forms.get(name):
  105. errors.append(
  106. (field_names[name], 'ce champ est requis'))
  107. if request.forms.get('floor') and not request.forms.get('floor_total'):
  108. errors.append((field_names['floor_total'], "ce champ est requis"))
  109. elif not request.forms.get('floor') and request.forms.get('floor_total'):
  110. errors.append((field_names['floor'], "ce champ est requis"))
  111. elif request.forms.get('floor') and request.forms.get('floor_total') and (request.forms.get('floor') > request.forms.get('floor_total')):
  112. errors.append((field_names['floor'], "Étage supérieur au nombre total"))
  113. if errors:
  114. return template('wifi-form', errors=errors, data=request.forms,
  115. orientations=ORIENTATIONS)
  116. else:
  117. d = request.forms
  118. save_to_db(DB, {
  119. 'name' : d.get('name'),
  120. 'contrib_type' : d.get('contrib-type'),
  121. 'latitude' : d.get('latitude'),
  122. 'longitude' : d.get('longitude'),
  123. 'phone' : d.get('phone'),
  124. 'email' : d.get('email'),
  125. 'phone' : d.get('phone'),
  126. 'access_type' : d.get('access-type'),
  127. 'bandwidth' : d.get('bandwidth'),
  128. 'share_part' : d.get('share-part'),
  129. 'floor' : d.get('floor'),
  130. 'floor_total' : d.get('floor_total'),
  131. 'orientations' : ','.join(d.getall('orientation')),
  132. 'roof' : d.get('roof'),
  133. 'comment' : d.get('comment'),
  134. 'privacy_name' : 'name' in d.getall('privacy'),
  135. 'privacy_email' : 'email' in d.getall('privacy'),
  136. 'privacy_place_details': 'place_details' in d.getall('privacy'),
  137. 'privacy_coordinates' : 'coordinates' in d.getall('privacy'),
  138. 'privacy_comment' : 'comment' in d.getall('privacy'),
  139. })
  140. DB.commit()
  141. # Rebuild GeoJSON
  142. build_geojson()
  143. return redirect(urlparse.urljoin(request.path,'thanks'))
  144. @route('/thanks')
  145. def wifi_form_thanks():
  146. return template('thanks')
  147. @route('/assets/<filename:path>')
  148. def send_asset(filename):
  149. return static_file(filename, root=join(dirname(__file__), 'assets'))
  150. @route('/legal')
  151. def legal():
  152. return template('legal')
  153. """
  154. Results Map
  155. """
  156. @route('/map')
  157. def public_map():
  158. geojsonPath = '/public.json'
  159. return template('map', geojson=geojsonPath)
  160. @route('/public.json')
  161. def public_geojson():
  162. return static_file('public.json', root=join(dirname(__file__), 'json/'))
  163. """
  164. GeoJSON Functions
  165. """
  166. # Save feature collection to a json file
  167. def save_featurecollection_json(id, features):
  168. with open('json/' + id + '.json', 'w') as outfile:
  169. json.dump({
  170. "type" : "FeatureCollection",
  171. "features" : features,
  172. "id" : id,
  173. }, outfile)
  174. # Build GeoJSON files from DB
  175. def build_geojson():
  176. # Read from DB
  177. DB.row_factory = sqlite3.Row
  178. cur = DB.execute("""
  179. SELECT * FROM {} ORDER BY id DESC
  180. """.format(TABLE_NAME))
  181. public_features = []
  182. private_features = []
  183. # Loop through results
  184. rows = cur.fetchall()
  185. for row in rows:
  186. # Private JSON file
  187. private_features.append({
  188. "type" : "Feature",
  189. "geometry" : {
  190. "type": "Point",
  191. "coordinates": [row['longitude'], row['latitude']],
  192. },
  193. "id" : row['id'],
  194. "properties": {
  195. "name" : row['name'],
  196. "place" : {
  197. 'floor' : row['floor'],
  198. 'floor_total' : row['floor_total'],
  199. 'orientations' : row['orientations'].split(','),
  200. 'roof' : row['roof'],
  201. },
  202. "comment" : row['comment']
  203. }
  204. })
  205. # Bypass non-public points
  206. if not row['privacy_coordinates']:
  207. continue
  208. # Public JSON file
  209. public_feature = {
  210. "type" : "Feature",
  211. "geometry" : {
  212. "type": "Point",
  213. "coordinates": [row['longitude'], row['latitude']],
  214. },
  215. "id" : row['id'],
  216. "properties": {}
  217. }
  218. # Add optionnal variables
  219. if not row['privacy_name']:
  220. public_feature['properties']['name'] = row['name']
  221. if not row['privacy_comment']:
  222. public_feature['properties']['comment'] = row['comment']
  223. if not row['privacy_place_details']:
  224. public_feature['properties']['place'] = {
  225. 'floor' : row['floor'],
  226. 'floor_total' : row['floor_total'],
  227. 'orientations' : row['orientations'].split(','),
  228. 'roof' : row['roof'],
  229. }
  230. # Add to public features list
  231. public_features.append(public_feature)
  232. # Build GeoJSON Feature Collection
  233. save_featurecollection_json('private', private_features)
  234. save_featurecollection_json('public', public_features)
  235. DEBUG = bool(os.environ.get('DEBUG', False))
  236. if __name__ == '__main__':
  237. if len(sys.argv) > 1:
  238. if sys.argv[1] == 'createdb':
  239. create_tabble(DB, TABLE_NAME, DB_COLS)
  240. if sys.argv[1] == 'buildgeojson':
  241. build_geojson()
  242. else:
  243. run(host='localhost', port=8080, reloader=DEBUG)
  244. DB.close()