Le repo des sources pour le site web des JM2L
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.
 
 
 
 
 

1086 lines
47 KiB

  1. # -*- coding: utf8 -*-
  2. from pyramid.httpexceptions import HTTPFound, HTTPNotFound, HTTPForbidden
  3. from pyramid.httpexceptions import HTTPBadRequest, HTTPUnauthorized
  4. from pyramid.renderers import render_to_response
  5. from pyramid.response import Response
  6. from pyramid.view import notfound_view_config, forbidden_view_config
  7. from pyramid.view import view_config
  8. from pyramid_mailer import get_mailer
  9. from mako.template import Template
  10. # Import Web Forms
  11. from .forms import *
  12. # Database access imports
  13. from .models import *
  14. from sqlalchemy.exc import DBAPIError
  15. from sqlalchemy import func, or_
  16. # Usefull tools
  17. from slugify import slugify
  18. from icalendar import Calendar
  19. from pytz import timezone
  20. from icalendar import Event as Evt
  21. from pyramid_mailer import get_mailer
  22. from pyramid_mailer.message import Attachment, Message
  23. # Then, standard libs
  24. import webhelpers.paginate as paginate
  25. import unicodedata
  26. import time
  27. import datetime
  28. import re
  29. CurrentYear = 2015
  30. ## =-=- Here, We keep some usefull function -=-=
  31. def remove_accents(input_str):
  32. """ This function is intended to remove all accent from input unicode string """
  33. nkfd_form = unicodedata.normalize('NFKD', input_str)
  34. only_ascii = nkfd_form.encode('ASCII', 'ignore')
  35. return only_ascii
  36. ## =-=- Here, We handle ICal requests -=-=
  37. @view_config(route_name='progr_iCal', renderer="string")
  38. def ICal_Progamme_Request(request):
  39. year = int(request.matchdict.get('year', CurrentYear))
  40. # Initialization
  41. DicResult = dict()
  42. # Query database
  43. # Compute days used by all events matching the specified input year
  44. Events = DBSession.query(Event)\
  45. .filter(Event.for_year == year)\
  46. .filter(Event.event_type != 'Stand')\
  47. .order_by(Event.start_time)
  48. cal = Calendar()
  49. cal.add('prodid', '-//Programme %d//jm2l.linux-azur.org//' % year)
  50. cal.add('version', '2.0')
  51. tz = timezone('Europe/Paris')
  52. for ev in Events:
  53. if ev.event_type:
  54. event = Evt()
  55. event['uid'] = "%d/%d" % ( year, ev.uid )
  56. event.add('summary', ev.name )
  57. event.add('dtstart', ev.start_time.replace(tzinfo=tz) )
  58. event.add('dtend', ev.end_time.replace(tzinfo=tz) )
  59. event.add('created', ev.last_change.replace(tzinfo=tz) )
  60. event.add('description', "http://www.linux-azur.org/event/%s/%s" % (ev.for_year, ev.slug) )
  61. event.add('url', "http://www.linux-azur.org/event/%s/%s" % (ev.for_year, ev.slug) )
  62. event.add('priority', 5)
  63. cal.add_component(event)
  64. return cal.to_ical()
  65. ## =-=- Here, We handle Json requests -=-=
  66. @view_config(route_name='users_json', renderer="json")
  67. def JSON_User_Request(request):
  68. """ Build a JSON answer with active users and pagination handling """
  69. # Check arguments consitency
  70. pageSize = request.params.get('pageSize',"8")
  71. current_page = request.params.get('pageNum',"1")
  72. UserQuery = request.params.get('searchTerm', u"")
  73. # Don't answer to users that aren't logged
  74. if not request.user:
  75. return HTTPUnauthorized('You have to be logged to hope an answer.')
  76. # Check consistancy of parameters
  77. if pageSize.isdigit() and current_page.isdigit():
  78. current_page = int(current_page)
  79. pageSize = int(pageSize)
  80. else:
  81. return HTTPBadRequest('pageSize and pageNum accept only digits.')
  82. # Query database
  83. Users = DBSession.query(User.uid, User.nom, User.prenom)\
  84. .filter(User.slug.contains( remove_accents(UserQuery) ))
  85. page_url = paginate.PageURL_WebOb(request)
  86. records = paginate.Page(Users, current_page, url=page_url, items_per_page=pageSize)
  87. ListMatchUser = map( lambda u:{"id": u.uid, "text":"%s %s" % ( u.prenom, u.nom )}, records )
  88. return { "Results": ListMatchUser, "Total":records.item_count,
  89. "logged_in":request.authenticated_userid }
  90. @view_config(route_name='tiers_json', renderer="json")
  91. def JSON_Tiers_Request(request):
  92. """ Build a JSON answer with active users and pagination handling """
  93. # Check arguments consitency
  94. pageSize = request.params.get('pageSize',"8")
  95. current_page = request.params.get('pageNum',"1")
  96. TiersQuery = request.params.get('searchTerm', u"")
  97. # Don't answer to users that aren't logged
  98. if not request.user:
  99. return HTTPUnauthorized('You have to be logged to hope an answer.')
  100. # Check consistancy of parameters
  101. if pageSize.isdigit() and current_page.isdigit():
  102. current_page = int(current_page)
  103. pageSize = int(pageSize)
  104. else:
  105. return HTTPBadRequest('pageSize and pageNum accept only digits.')
  106. # Query database
  107. JTiers = DBSession.query(Tiers.uid, Tiers.name)\
  108. .filter(Tiers.slug.contains( remove_accents(TiersQuery) ))
  109. page_url = paginate.PageURL_WebOb(request)
  110. records = paginate.Page(JTiers, current_page, url=page_url, items_per_page=pageSize)
  111. ListMatchTiers = map( lambda t:{"id": t.uid, "text": t.name }, records )
  112. return { "Results": ListMatchTiers, "Total":records.item_count,
  113. "logged_in":request.authenticated_userid }
  114. @view_config(route_name='progr_json', renderer="json")
  115. def JSON_Progamme_Request(request):
  116. year = int(request.matchdict.get('year', CurrentYear))
  117. # Initialization
  118. DicResult = dict()
  119. # Query database
  120. # Compute days used by all events matching the specified input year
  121. Days = DBSession.query( func.strftime('%d', Event.start_time).label('day') )\
  122. .filter(Event.for_year == year)\
  123. .filter(Event.event_type != None)\
  124. .group_by(func.strftime('%d', Event.start_time)).all()
  125. for Day in Days:
  126. Events = DBSession.query(Event)\
  127. .filter(Event.for_year == year)\
  128. .filter(Event.event_type != 'Stand')\
  129. .filter("strftime('%d', start_time) = :dow").params(dow=Day.day)\
  130. .order_by(Event.start_time)
  131. ListEv = []
  132. for ev in Events:
  133. if ev.event_type:
  134. ListEv.append( {
  135. "uid":"%d/%d" % ( year, ev.uid ),
  136. "desc":ev.name,
  137. "startDate":ev.start_time.strftime('%Y-%m-%dT%H:%M:%S'),
  138. "endDate":ev.end_time.strftime('%Y-%m-%dT%H:%M:%S'),
  139. "placeName":ev.Salle and (ev.Salle.name or "unk") ,
  140. "status":ev.event_type
  141. } )
  142. DicResult[Day.day] = ListEv
  143. return { 'all':DicResult }
  144. @view_config(route_name='timeline_json', renderer="json")
  145. def JSON_TimeLine_Request(request):
  146. year = int(request.matchdict.get('year', CurrentYear))
  147. # Initialization
  148. DicResult = dict()
  149. # Query database
  150. # Compute days used by all events matching the specified input year
  151. Days = DBSession.query( func.strftime('%d', Event.start_time).label('day') )\
  152. .filter(Event.for_year == year)\
  153. .filter(Event.event_type != None)\
  154. .group_by(func.strftime('%d', Event.start_time)).all()
  155. ListEv = []
  156. for Day in Days:
  157. Events = DBSession.query(Event)\
  158. .filter(Event.for_year == year)\
  159. .filter(Event.event_type != 'Stand')\
  160. .filter("strftime('%d', start_time) = :dow").params(dow=Day.day)\
  161. .order_by(Event.start_time)
  162. ListEv = []
  163. for ev in Events:
  164. if ev.event_type:
  165. ListEv.append( {
  166. #"uid":"%d/%d" % ( year, ev.uid ),
  167. "headline":ev.name,
  168. "startDate":ev.start_time.strftime('%Y,%m,%d,%H,%M'),
  169. "endDate":ev.end_time.strftime('%Y,%m,%d,%H,%M'),
  170. "text":ev.Salle and (ev.Salle.name or "unk"),
  171. "tags":ev.Salle and (ev.Salle.name or "unk") ,
  172. #"status":ev.event_type,
  173. "asset": {
  174. "media":"", #http://jm2l.linux-azur.org/sites/jm2l.linux-azur.org/files/videos/2012/2012_Introduction_aux_logiciels_libres__Frederic_Couchet.ogv",
  175. "credit":"",
  176. "caption":"" }
  177. } )
  178. break
  179. DicResult = {
  180. "lang":"fr",
  181. "headline":"JM2L 2015",
  182. "type":"default",
  183. "startDate":"2015,11,28,10",
  184. "text":"<i><span class='c1'>9ème Édition</span></i>",
  185. "asset":
  186. {
  187. "media":"",
  188. "credit":"JM2L",
  189. "caption":""
  190. }
  191. }
  192. DicResult["date"] = ListEv
  193. return { 'timeline':DicResult }
  194. ## =-=- Here, We handle HTTP requests - Public Part -=-=
  195. @view_config(route_name='home', renderer="jm2l:templates/NewIndex.mako")
  196. def index_page(request):
  197. MainTab = {'programme':'','presse':'', 'plan':'', 'participer':'',
  198. "logged_in":request.authenticated_userid }
  199. return MainTab
  200. @view_config(route_name='programme', renderer="jm2l:templates/Public/Programme.mako")
  201. def programme(request):
  202. year = int(request.matchdict.get('year'))
  203. if 2006 > year:
  204. return HTTPBadRequest('The first JM2L event was in 2006.')
  205. # Query database about selected Year.
  206. Events = DBSession.query(Event)\
  207. .filter(Event.for_year == year)\
  208. .order_by(Event.start_time)
  209. Days = DBSession.query(func.strftime('%d-%m-%Y', Event.start_time))\
  210. .filter(Event.for_year == year)\
  211. .filter(Event.event_type != None)\
  212. .group_by(func.strftime('%d', Event.start_time)).all()
  213. ListDay = []
  214. for day in Days:
  215. RefDay = datetime.datetime.strptime(day[0],'%d-%m-%Y')
  216. ListDay.append( ( RefDay.strftime('%A %d %b %Y'),
  217. RefDay.strftime('%d') ) )
  218. MainTab = {'programme':'active','presse':'', 'plan':'', 'participer':'', 'DisplayYear':year, \
  219. 'Events':Events, 'Event':Event, 'Days':ListDay, "logged_in":request.authenticated_userid }
  220. return MainTab
  221. @view_config(route_name='presse', renderer="jm2l:templates/Public/Presse.mako")
  222. def static_presse(request):
  223. year = int(request.matchdict.get('year', None))
  224. content = DBSession.query(JM2L_Year).filter(JM2L_Year.year_uid==year).first()
  225. MainTab = {'programme':'','presse':'active', 'plan':'', 'participer':'',
  226. "logged_in":request.authenticated_userid, 'content':content, 'DisplayYear':year}
  227. return MainTab
  228. @view_config(route_name='edit_presse', renderer="jm2l:templates/Staff/EditPresse.mako")
  229. def edit_presse(request):
  230. year = int(request.matchdict.get('year', None))
  231. content = DBSession.query(JM2L_Year).filter(JM2L_Year.year_uid==year).first()
  232. form = DossPresse(request.POST, content, meta={'csrf_context': request.session})
  233. if request.method == 'POST' and form.validate():
  234. form.populate_obj(content)
  235. MainTab = {'programme':'','presse':'active', 'plan':'', 'participer':'',
  236. "logged_in":request.authenticated_userid, 'form':form, 'DisplayYear':year}
  237. return MainTab
  238. @view_config(route_name='plan', renderer="jm2l:templates/Public/Plan.mako")
  239. def static_plan(request):
  240. MainTab = {'programme':'','presse':'', 'plan':'active', 'participer':'',
  241. "logged_in":request.authenticated_userid }
  242. return MainTab
  243. ## =-=- Here, We handle HTTP requests - Staff Logged Part -=-=
  244. @view_config(route_name='list_task', renderer='jm2l:templates/Staff/list.mako')
  245. def list_view(request):
  246. DicTask = {}
  247. taskgroup = DBSession.query( TasksArea ).all()
  248. for grp in taskgroup:
  249. tasks = DBSession.query( Tasks )\
  250. .filter( Tasks.area_uid==grp.uid )\
  251. .order_by(Tasks.closed, Tasks.due_date).all()
  252. DicTask[grp] = tasks
  253. return {'tasks': DicTask }
  254. @view_config(route_name='handle_task', renderer='jm2l:templates/Staff/tasks.mako')
  255. def tasks(request):
  256. task_id = request.matchdict.get('task_id')
  257. # Convert the pole_id GET parameter to int or 0
  258. try:
  259. pole_id = int(request.params.get('pole_id'))
  260. except (ValueError, TypeError):
  261. pole_id = 0
  262. # Get areas from db
  263. Areas = DBSession.query(TasksArea.uid, TasksArea.name)\
  264. .order_by('name').all()
  265. # Get users from db
  266. Users = DBSession.query(User)\
  267. .filter(User.Staff==1)\
  268. .order_by('nom').all()
  269. if task_id:
  270. Task = Tasks.by_id(int(task_id))
  271. if not Task:
  272. raise HTTPNotFound()
  273. form = EditStaffTasks(request.POST, Task, meta={'csrf_context': request.session})
  274. else:
  275. Task = Tasks()
  276. # Check if the supplied pole_id is in the Areas' range
  277. Task.area_uid = pole_id if 1 < pole_id <= len(Areas) else 1
  278. form = StaffTasks(request.POST, Task, meta={'csrf_context': request.session})
  279. # Put some areas on form
  280. form.area_uid.choices = Areas
  281. # Put some users on form
  282. form.closed_by.choices = [(u.uid, "%s %s" % (u.nom, u.prenom))
  283. for u in Users]
  284. form.due_date.type = "date"
  285. if request.method == 'POST' and form.validate():
  286. form.populate_obj(Task)
  287. Task.closed = False
  288. if 'uid' in form._fields.keys():
  289. DBSession.merge(Task)
  290. else:
  291. DBSession.add(Task)
  292. DBSession.flush()
  293. return HTTPFound(location=request.route_url('list_task')+"#"+slugify(Task.area.name))
  294. return {'form':form, 'area':slugify(Areas[Task.area_uid-1].name)}
  295. @view_config(route_name='handle_pole', renderer='jm2l:templates/Staff/pole.mako')
  296. def tasks_area(request):
  297. pole_id = request.matchdict.get('pole_id')
  298. if pole_id:
  299. Pole = TasksArea.by_id(int(pole_id))
  300. if not Pole:
  301. raise HTTPNotFound()
  302. form = EditStaffArea(request.POST, Pole, meta={'csrf_context': request.session})
  303. else:
  304. Pole = TasksArea()
  305. form = StaffArea(request.POST, Pole, meta={'csrf_context': request.session})
  306. if request.method == 'POST' and form.validate():
  307. form.populate_obj(Pole)
  308. if 'uid' in form._fields.keys():
  309. DBSession.merge(Pole)
  310. else:
  311. DBSession.add(Pole)
  312. return HTTPFound(location=request.route_url('list_task'))
  313. return {'form':form }
  314. @view_config(route_name='action_task')
  315. def action_task(request):
  316. action = request.matchdict.get('action')
  317. task_id = request.matchdict.get('task_id')
  318. Task = Tasks.by_id(int(task_id))
  319. if action=='close':
  320. Task.closed = True
  321. if action=='open':
  322. Task.closed = False
  323. DBSession.merge(Task)
  324. request.session.flash('Task was successfully closed!')
  325. return HTTPFound(location=request.route_url('list_task')+"#"+slugify(Task.area.name))
  326. ## =-=- Here, We handle HTTP requests - User Logged Part -=-=
  327. @view_config(route_name='exchange', renderer="jm2l:templates/Logistique/Logistique.mako")
  328. def exchange(request):
  329. modtype = request.matchdict.get('modtype', None)
  330. action = request.matchdict.get('action', None)
  331. uid = int(request.matchdict.get('id', -1))
  332. Exch = Exchange.by_id(uid)
  333. if not Exch:
  334. MainTab = {
  335. 'Exchanges':Exchange,
  336. 'Type':modtype[-1:],
  337. 'reload':True,
  338. 'logged_in':request.authenticated_userid
  339. }
  340. return MainTab
  341. if action in ['delete', 'accept', 'refuse', 'deal']:
  342. if action=='delete': # delete exchange
  343. DBSession.delete(Exch)
  344. elif action=='accept': # accept exchange
  345. Exch.exch_done=True
  346. DBSession.merge(Exch)
  347. elif action=='refuse': # refuse exchange
  348. Exch.exch_done=False
  349. if Exch.exch_state=="Ask":
  350. Exch.provider_id = None
  351. elif Exch.exch_state=="Proposal":
  352. Exch.asker_id = None
  353. DBSession.merge(Exch)
  354. elif action=='deal':
  355. # ask to deal the exchange
  356. if Exch.exch_state=="Ask":
  357. Exch.provider_id = request.user.uid
  358. elif Exch.exch_state=="Proposal":
  359. Exch.asker_id = request.user.uid
  360. # Return javascript to parent page
  361. response = render_to_response('jm2l:templates/modals_js.mako',
  362. {'modtype':modtype, 'action':action},
  363. request=request)
  364. response.content_type = 'text/javascript'
  365. return response
  366. else:
  367. MainTab = {
  368. 'Exchanges':Exchange,
  369. 'Type':modtype[-1:],
  370. 'reload':True,
  371. 'logged_in':request.authenticated_userid
  372. }
  373. return MainTab
  374. @view_config(route_name='jm2l', renderer="jm2l:templates/jm2l.mako")
  375. def jm2l_page(request):
  376. if request.user is None:
  377. # Don't answer to users that aren't logged
  378. return HTTPUnauthorized('You have to be logged to hope an answer.')
  379. page = int(request.params.get('page', 1))
  380. UserNum = request.params.get('user')
  381. if UserNum:
  382. profil = User.by_id(int(UserNum))
  383. if not profil:
  384. raise HTTPNotFound()
  385. else:
  386. profil = request.user
  387. # Build Form
  388. profil_form = ProfilForm(request.POST, profil, meta={'csrf_context': request.session})
  389. if request.method == 'POST' and profil_form.validate():
  390. ToDelete = list()
  391. # First, we remove entries no more present
  392. for obj in profil_form.tiersship.object_data:
  393. MatchEntry = filter( lambda x: x.object_data and x.object_data._sa_instance_state == obj._sa_instance_state,
  394. profil_form.tiersship.entries )
  395. if not MatchEntry:
  396. ToDelete.append(obj)
  397. # Then, it's time to consider new entries
  398. for entry in profil_form.tiersship.entries:
  399. if entry.object_data is None:
  400. TmpUser = User_Tiers()
  401. entry.object_data = TmpUser
  402. profil.tiersship.append(TmpUser)
  403. profil_form.tiersship.object_data = profil.tiersship
  404. profil_form.populate_obj(profil)
  405. # We should remove it as it's not in original data
  406. for obj in ToDelete:
  407. #profil.tiersship.remove(obj)
  408. DBSession.delete(obj)
  409. profil.last_change = datetime.datetime.utcnow()
  410. profil.slug = slugify(remove_accents('%s %s' % (profil.prenom, profil.nom)).lower().strip())
  411. DBSession.merge(profil)
  412. MainTab = {'participer':'active',
  413. 'Places':Place.get_list(False),
  414. 'DBTiers':Tiers,
  415. 'DBTiersOpt':TiersOpt,
  416. 'Exchanges':Exchange,
  417. 'profil_form':profil_form,
  418. 'uprofil':profil,
  419. 'logged_in':request.authenticated_userid
  420. }
  421. return MainTab
  422. @view_config(route_name='modal', renderer="jm2l:templates/modals.mako")
  423. def Modal(request):
  424. year = int(request.matchdict.get('year', None))
  425. modtype = request.matchdict.get('modtype', None)
  426. uid = int(request.matchdict.get('id', -1))
  427. session = request.session
  428. if modtype=='Password':
  429. form = UserPasswordForm(request.POST, request.user, meta={'csrf_context': request.session})
  430. if request.method == 'POST' and form.validate():
  431. response = render_to_response('jm2l:templates/modals_js.mako',
  432. {'modtype':modtype},
  433. request=request)
  434. response.content_type = 'text/javascript'
  435. return response
  436. if modtype=='UserPicture':
  437. form = None
  438. if request.method == 'POST':
  439. response = render_to_response('jm2l:templates/modals_js.mako',
  440. {'modtype':modtype},
  441. request=request)
  442. response.content_type = 'text/javascript'
  443. return response
  444. if modtype=='Place':
  445. if uid>0:
  446. place = Place.by_id(uid)
  447. if not place:
  448. raise HTTPNotFound()
  449. form = PlaceUpdateForm(request.POST, place, meta={'csrf_context': request.session})
  450. else:
  451. place = Place()
  452. form = PlaceCreateForm(request.POST, meta={'csrf_context': request.session})
  453. if request.method == 'POST' and form.validate():
  454. form.populate_obj(place)
  455. place.created_by=request.user.uid
  456. if uid>0:
  457. DBSession.merge(place)
  458. else:
  459. DBSession.add(place)
  460. response = render_to_response('jm2l:templates/modals_js.mako',
  461. {'modtype':modtype},
  462. request=request)
  463. response.content_type = 'text/javascript'
  464. return response
  465. if modtype in ['AskC', 'AskH', 'AskM', 'PropC', 'PropH', 'PropM']:
  466. if uid>0:
  467. Exch = Exchange.by_id(uid)
  468. if not Exch:
  469. raise HTTPNotFound()
  470. if modtype in ['AskC','PropC']:
  471. form = globals()["Update%sForm" % modtype](request.POST, Exch,
  472. start_place = Exch.Itin.start_place,
  473. arrival_place = Exch.Itin.arrival_place,
  474. Hour_start = Exch.start_time.strftime("%H:%M"),
  475. Day_start = Exch.start_time.strftime("%w"),
  476. exch_id = uid, meta={'csrf_context': request.session}
  477. )
  478. elif modtype in ['AskM','PropM']:
  479. form = globals()["Update%sForm" % modtype](request.POST, Exch,
  480. description = Exch.description,
  481. exch_categ = Exch.exch_categ,
  482. Hour_start = Exch.start_time.strftime("%H:%M"),
  483. Day_start = Exch.start_time.strftime("%w"),
  484. Hour_end = Exch.end_time.strftime("%H:%M"),
  485. Day_end = Exch.end_time.strftime("%w"),
  486. exch_id = uid, meta={'csrf_context': request.session}
  487. )
  488. elif modtype in ['AskH','PropH']:
  489. form = globals()["Update%sForm" % modtype](request.POST, Exch,
  490. description = Exch.description,
  491. exch_categ = Exch.exch_categ,
  492. Day_start = Exch.start_time.strftime("%w"),
  493. exch_id = uid, meta={'csrf_context': request.session}
  494. )
  495. # Itinerary, first get itinerary
  496. if 0:
  497. form.itin.form.start_place.data = Exch.Itin.start_place
  498. form.itin.form.arrival_place.data = Exch.Itin.arrival_place
  499. form.dateform.form.Hour.data = Exch.start_time.strftime("%H:%M")
  500. form.dateform.form.Day.data = Exch.start_time.strftime("%w")
  501. form.exch_id.data = uid
  502. else:
  503. Exch = Exchange()
  504. form = globals()["%sForm" % modtype](request.POST, meta={'csrf_context': request.session})
  505. if modtype in ['AskC', 'PropC']:
  506. # Put some place on form
  507. Places = DBSession.query(Place.place_id, Place.display_name)\
  508. .order_by('name').all()
  509. form.start_place.choices = Places
  510. form.arrival_place.choices = Places
  511. if modtype in ['PropH']:
  512. form.exch_categ.choices = DBSession.query( Exchange_Cat.cat_id, Exchange_Cat.exch_subtype)\
  513. .filter( Exchange_Cat.exch_type=='H' ).all()
  514. form.place_id.choices = DBSession.query( Place.place_id, Place.display_name)\
  515. .filter( Place.created_by==request.user.uid ).all()
  516. if modtype in ['AskM', 'PropM']:
  517. form.exch_categ.choices = DBSession.query( Exchange_Cat.cat_id, Exchange_Cat.exch_subtype)\
  518. .filter( Exchange_Cat.exch_type=='M' ).all()
  519. if request.method == 'POST' and form.validate():
  520. # Form has been validated, it's time to create our Exchange
  521. Exch.for_year = year
  522. Exch.exch_state = {'Ask':'Ask', 'Prop':'Proposal'}[modtype[:-1]]
  523. Exch.exch_type = modtype[-1:]
  524. if modtype in ['AskC', 'PropC']:
  525. # Itinerary, first Let's see if itinerary exist
  526. Itinerary = DBSession.query(Itineraire)\
  527. .filter(Itineraire.start_place==form.start_place.data) \
  528. .filter(Itineraire.arrival_place==form.arrival_place.data) \
  529. .filter(Itineraire.tr_voiture==True) \
  530. .first()
  531. if not Itinerary: # Not exist yet !
  532. Itinerary = Itineraire(start_place=form.start_place.data, \
  533. arrival_place=form.arrival_place.data, \
  534. tr_voiture=True, \
  535. created_by=1
  536. )
  537. DBSession.add(Itinerary)
  538. DBSession.flush()
  539. Exch.itin_id = Itinerary.itin_id
  540. # Start Time
  541. StartEvent = DBSession.query(JM2L_Year.start_time).filter(JM2L_Year.year_uid==year).first()
  542. Week = StartEvent[0].strftime("%W")
  543. # populate
  544. form.populate_obj(Exch)
  545. if modtype in ['AskC', 'PropC']:
  546. Exch.itin_id = Itinerary.itin_id
  547. if form._fields.has_key("Hour_start"):
  548. TargetTime = datetime.datetime.strptime('%d %d %d %s' % (year, int(Week), \
  549. int(form.Day_start.data), form.Hour_start.data), "%Y %W %w %H:%M")
  550. Exch.start_time = TargetTime
  551. elif form._fields.has_key("Day_start"):
  552. TargetTime = datetime.datetime.strptime('%d %d %d' % (year, int(Week), \
  553. int(form.Day_start.data)), "%Y %W %w")
  554. Exch.start_time = TargetTime
  555. if form._fields.has_key("Hour_end"):
  556. TargetTime = datetime.datetime.strptime('%d %d %d %s' % (year, int(Week), \
  557. int(form.Day_end.data), form.Hour_end.data), "%Y %W %w %H:%M")
  558. Exch.end_time = TargetTime
  559. elif form._fields.has_key("Day_end"):
  560. TargetTime = datetime.datetime.strptime('%d %d %d' % (year, int(Week), \
  561. int(form.Day_end.data)), "%Y %W %w")
  562. Exch.end_time = TargetTime
  563. Exch.last_change = datetime.datetime.utcnow()
  564. if Exch.exch_state=='Ask':
  565. Exch.asker_id = request.user.uid
  566. elif Exch.exch_state=='Proposal':
  567. Exch.provider_id = request.user.uid
  568. #print vars(form.itin.form)
  569. if uid>0:
  570. DBSession.merge(Exch)
  571. else:
  572. DBSession.add(Exch)
  573. response = render_to_response('jm2l:templates/modals_js.mako',
  574. {'modtype':modtype},
  575. request=request)
  576. response.content_type = 'text/javascript'
  577. return response
  578. # Fallback to HTML Display with errors
  579. return {'modtype':modtype, 'form':form, 'update':uid>0,
  580. 'logged_in':request.authenticated_userid }
  581. if modtype in ['ShowC', 'ShowH', 'ShowM']:
  582. if uid>0:
  583. Exch = Exchange.by_id(uid)
  584. if not Exch:
  585. raise HTTPNotFound()
  586. else:
  587. raise HTTPNotFound()
  588. # Show Details around the Current Exchange
  589. return {'modtype':modtype, 'Exch':Exch, 'logged_in':request.authenticated_userid }
  590. MainTab = {'modtype':modtype, 'form':form, 'update':uid>0, 'uid':uid,
  591. 'DisplayYear':year, 'session':session,
  592. 'logged_in':request.authenticated_userid }
  593. return MainTab
  594. @view_config(route_name='participer', renderer="jm2l:templates/Participer.mako")
  595. def participer(request):
  596. session = request.session
  597. session['year'] = 2015
  598. TmpUsr = User()
  599. form = UserRegisterForm(request.POST, TmpUsr, meta={'csrf_context': request.session})
  600. MyLink=None
  601. if request.method == 'POST' and form.validate():
  602. # Prepare mailer
  603. form.populate_obj(TmpUsr)
  604. TmpUsr.nom = TmpUsr.nom.capitalize()
  605. TmpUsr.prenom = TmpUsr.prenom.capitalize()
  606. TmpUsr.slug = slugify(remove_accents('%s %s' % (form.prenom.data, form.nom.data)).lower().strip())
  607. TmpUsr.password = TmpUsr.my_hash
  608. if len(TmpUsr.slug):
  609. CheckExist = DBSession.query(User)\
  610. .filter(User.slug==TmpUsr.slug)\
  611. .first()
  612. else:
  613. CheckExist=None
  614. if CheckExist:
  615. MyLink = CheckExist.my_hash
  616. NewUser = CheckExist
  617. else:
  618. DBSession.add(TmpUsr)
  619. DBSession.flush()
  620. MyLink = TmpUsr.my_hash
  621. NewUser = TmpUsr
  622. # Send the Welcome Mail
  623. mailer = request.registry['mailer']
  624. # Prepare Plain Text Message :
  625. Mail_template = Template(filename='jm2l/templates/mail_plain.mako')
  626. mail_plain = Mail_template.render(request=request, User=NewUser, action="Welcome")
  627. body = Attachment(data=mail_plain, transfer_encoding="quoted-printable")
  628. # Prepare HTML Message :
  629. Mail_template = Template(filename='jm2l/templates/mail_html.mako')
  630. mail_html = Mail_template.render(request=request, User=NewUser, action="Welcome")
  631. html = Attachment(data=mail_html, transfer_encoding="quoted-printable")
  632. # Prepare Message
  633. message = Message(subject="[JM2L] Mon inscription au site web JM2L",
  634. sender="contact@jm2l.linux-azur.org",
  635. recipients=[NewUser.mail],
  636. body=body, html=html)
  637. message.add_bcc("spam@style-python.fr")
  638. mailer.send(message)
  639. MainTab = {'programme':'','presse':'', 'plan':'',
  640. 'participer':'active', 'form':form, "link": MyLink,
  641. 'logged_in':request.authenticated_userid }
  642. return MainTab
  643. @view_config(route_name='year')
  644. def change_year(request):
  645. year = int(request.matchdict.get('year', -1))
  646. session = request.session
  647. if year>-1:
  648. session['year'] = year
  649. return HTTPFound(location='/%s/le-programme' % year)
  650. return HTTPFound(location=request.route_url('home'))
  651. @view_config(route_name='pict_user', renderer="jm2l:templates/Profil/pict_user.mako")
  652. def pict_user(request):
  653. return {"uprofil":request.user}
  654. @view_config(route_name='event', renderer="jm2l:templates/view_event.mako")
  655. def show_event(request):
  656. year = int(request.matchdict.get('year', -1))
  657. event_id = request.matchdict.get('event_id')
  658. if event_id.isdigit():
  659. TheEvent = Event.by_id(event_id)
  660. if TheEvent is None:
  661. raise HTTPNotFound()
  662. else:
  663. TheEvent = Event.by_slug(event_id, year)
  664. if TheEvent is None:
  665. raise HTTPNotFound()
  666. MainTab = {'programme':'','presse':'', 'plan':'', 'participer':'',
  667. 'event':TheEvent, 'logged_in':request.authenticated_userid }
  668. return MainTab
  669. @view_config(route_name='link_event')
  670. def link_event(request):
  671. """ Create user if not exist, add it to current event """
  672. year = int(request.matchdict.get('year', -1))
  673. form = AddIntervenant(request.POST, meta={'csrf_context': request.session})
  674. intervention = request.matchdict.get('intervention', None)
  675. TargetEvent = Event.by_id(form.event_uid.data)
  676. Exist = DBSession.query(User)\
  677. .filter(User.nom==form.nom.data)\
  678. .filter(User.prenom==form.prenom.data)\
  679. .first()
  680. if Exist:
  681. TargetUser = Exist
  682. else:
  683. # Add it to user base
  684. TargetUser = User(nom=form.nom.data,
  685. prenom=form.prenom.data, password=form.nom.data)
  686. DBSession.add(TargetUser)
  687. DBSession.flush()
  688. uev = User_Event(year_uid=year, role="Animateur", user_uid=TargetUser.uid)
  689. TargetEvent.interventions.append( uev )
  690. return HTTPFound(location=request.route_url('edit_event', sep='/',
  691. year=str(year), intervention=intervention, uid=str(TargetEvent.uid)))
  692. @view_config(route_name='edit_event', renderer="jm2l:templates/edit_event.mako")
  693. def edit_event(request):
  694. year = int(request.matchdict.get('year', -1))
  695. event_id = request.matchdict.get('event_id')
  696. intervention = request.matchdict.get('intervention', None)
  697. IntervLabel = intervention.replace('_',' ').lower()
  698. if intervention=='Conference':
  699. IntervLabel = u'conférence'
  700. # Check intervention
  701. if not intervention in ['Stand', 'Table ronde', 'Atelier', 'Conference']:
  702. raise HTTPNotFound(u"Ce type d'évenement n'est pas reconnu")
  703. TheYear = DBSession.query(JM2L_Year)\
  704. .filter(JM2L_Year.year_uid==year)\
  705. .all()
  706. # Check year avaibility
  707. if not TheYear:
  708. raise HTTPNotFound(u"Cette année n'est pas pris en charge")
  709. # Generate Timeslots for current year
  710. TimeSlots = list(enumerate( [ x.strftime('%a %d %b %H:%M') for x in
  711. TheYear[0].AvailableTimeSlots ] ))
  712. if event_id:
  713. # We try to update an existing record
  714. if event_id.isdigit():
  715. TheEvent = Event.by_id(event_id)
  716. if TheEvent is None:
  717. raise HTTPNotFound(u"Cette réference n'existe pas")
  718. else:
  719. TheEvent = Event.by_slug(event_id, year)
  720. if TheEvent is None:
  721. raise HTTPNotFound(u"Cette réference n'existe pas")
  722. if request.user is None or not (request.user.Staff or request.user in TheEvent.intervenants):
  723. return HTTPForbidden(u"Vous n'êtes pas identifié comme étant un participant à cette intervention.")
  724. # Compute some field value from selected event
  725. if TheEvent.start_time in TheYear[0].AvailableTimeSlots:
  726. start_sel = TheYear[0].AvailableTimeSlots.index(TheEvent.start_time)
  727. else:
  728. start_sel = len(TimeSlots)
  729. TimeSlots.append( (len(TimeSlots), TheEvent.start_time.strftime('%a %d %b %H:%M')))
  730. duration = (TheEvent.end_time - TheEvent.start_time).total_seconds()/60
  731. end = TheEvent.start_time + datetime.timedelta(minutes=duration)
  732. # prepare the form with update
  733. form = ConfUpdateForm(request.POST, TheEvent, start_sel=start_sel, duration=duration, end_time=end,
  734. meta={'csrf_context': request.session} )
  735. # Customize labels
  736. form.name.label.text += IntervLabel
  737. form.description.label.text += IntervLabel
  738. # Each event can get severals members
  739. formAdd = AddIntervenant(event_uid=TheEvent.uid)
  740. else:
  741. TheEvent = Event()
  742. # prepare the form for creation
  743. form = ConfCreateForm(request.POST,
  744. event_type=intervention,
  745. for_year=str(year), meta={'csrf_context': request.session}
  746. )
  747. # Customize labels
  748. form.name.label.text += IntervLabel
  749. form.description.label.text += IntervLabel
  750. duration=60
  751. # No intervenant
  752. formAdd = None
  753. if intervention=="Conference":
  754. form.duration.choices =[
  755. (15,u'Lighting talk ( 5 min)'),
  756. (30,u'Conférence (20 min)'),
  757. (60,u'Conférence (50 min)'),
  758. (90,u'Conférence (75 min)'),
  759. ]
  760. if not duration in [15, 30, 60, 90]:
  761. form.duration.choices.append( (duration,u'Conférence (%d min)' % duration) )
  762. if not form._fields.has_key("uid"):
  763. form.duration.data=60
  764. elif intervention=="Stand":
  765. form.duration.choices =[
  766. (8*60, u'Toute la journée'),
  767. (4*60, u'une demi-journée')
  768. ]
  769. elif intervention=="Atelier":
  770. form.duration.choices = map( lambda d:(d, u'Atelier (%dh%.2d)' % (d/60, d%60) ), \
  771. [60, 90, 120, 150, 180, 210, 240] )
  772. if not duration in map(lambda (d,y): d, form.duration.choices):
  773. form.duration.choices.append( (duration,u'Atelier (%dh%.2d)' % (duration/60, duration%60) ) )
  774. elif intervention=="Table_Ronde":
  775. form.duration.choices = map( lambda d:(d, u'Table ronde (%dh%.2d)' % (d/60, d%60) ), \
  776. [60, 90, 120, 150] )
  777. if not duration in map(lambda (d,y): d, form.duration.choices):
  778. form.duration.choices.append( (duration,u'Table ronde (%dh%.2d)' % (duration/60, duration%60) ) )
  779. else:
  780. return HTTPForbidden(u"Pas encore disponible.")
  781. SalleDispo = DBSession.query(Salles)\
  782. .filter(Salles.year_uid==year)\
  783. .order_by('name')
  784. form.salle_uid.choices = [(s.salle_id, s.name) for s in SalleDispo]
  785. form.start_sel.choices = TimeSlots
  786. if request.method == 'POST' and form.validate():
  787. form.populate_obj(TheEvent)
  788. TheEvent.start_time = TheYear[0].AvailableTimeSlots[form.start_sel.data]
  789. TheEvent.end_time = TheEvent.start_time + datetime.timedelta(minutes=form.duration.data)
  790. # Ok, time to put in database
  791. if not form._fields.has_key("uid"):
  792. TheEvent.slug = slugify(TheEvent.name)
  793. DBSession.add(TheEvent)
  794. # Append creator by default
  795. if request.user.uid!=1:
  796. uev = User_Event(year_uid=TheYear.year_uid, role="Animateur")
  797. uev.user_uid = request.user.uid
  798. TheEvent.interventions.append( uev )
  799. DBSession.flush()
  800. return HTTPFound(location=request.route_url('edit_event', sep='/',
  801. year=str(year), intervention=intervention, event_id=str(TheEvent.slug)))
  802. else:
  803. DBSession.merge(TheEvent)
  804. MainTab = {'programme':'','presse':'', 'plan':'', 'participer':'',
  805. 'event':TheEvent, 'form':form, 'formAdd':formAdd,
  806. 'logged_in':request.authenticated_userid }
  807. return MainTab
  808. @view_config(route_name='entities', renderer="jm2l:templates/list_tiers.mako")
  809. def list_tiers(request):
  810. Entities = dict()
  811. EntityType = DBSession.query(TiersOpt.entity_type)\
  812. .group_by(TiersOpt.entity_type).all()
  813. for EType in EntityType:
  814. Entities[EType.entity_type] = DBSession.query(Tiers).join(TiersOpt)\
  815. .filter(TiersOpt.entity_type==EType.entity_type)\
  816. .order_by(TiersOpt.entity_subtype, Tiers.name)
  817. MainTab = {'programme':'','presse':'', 'plan':'', 'participer':'',
  818. 'entities':Entities, 'logged_in':request.authenticated_userid }
  819. return MainTab
  820. @view_config(route_name='show_entity', renderer="jm2l:templates/view_tiers.mako")
  821. def show_tiers(request):
  822. tiers_type = request.matchdict.get('tiers_type')
  823. entity_id = request.matchdict.get('entity_id')
  824. if entity_id.isdigit():
  825. TheTiers = Tiers.by_id(entity_id)
  826. if TheTiers is None:
  827. raise HTTPNotFound()
  828. else:
  829. TheTiers = Tiers.by_slug(entity_id)
  830. if TheTiers is None:
  831. raise HTTPNotFound()
  832. MainTab = {'programme':'','presse':'', 'plan':'', 'participer':'',
  833. 'entity':TheTiers, 'logged_in':request.authenticated_userid }
  834. return MainTab
  835. @view_config(route_name='add_entity', renderer="jm2l:templates/edit_tiers.mako")
  836. @view_config(route_name='edit_entity', renderer="jm2l:templates/edit_tiers.mako",
  837. permission='edit')
  838. def edit_tiers(request):
  839. entity_id = request.matchdict.get('entity_id', None)
  840. TargetList = list()
  841. entity_types = DBSession.query(TiersOpt.entity_type).group_by(TiersOpt.entity_type).all()
  842. for entity_type in entity_types:
  843. entity_subtypes = DBSession.query(TiersOpt)\
  844. .filter(TiersOpt.entity_type==entity_type.entity_type)\
  845. .group_by(TiersOpt.entity_subtype).all()
  846. ListType = [(i.uid, i.entity_subtype) for i in entity_subtypes]
  847. TargetList.append( (entity_type.entity_type, ListType) )
  848. if entity_id:
  849. if entity_id.isdigit():
  850. TheTiers = Tiers.by_id(entity_id)
  851. if TheTiers is None:
  852. raise HTTPNotFound()
  853. else:
  854. TheTiers = Tiers.by_slug(entity_id)
  855. if TheTiers is None:
  856. raise HTTPNotFound()
  857. form = UpdateTiersForm(request.POST, TheTiers, meta={'csrf_context': request.session})
  858. UserOptions = DBSession.query(TiersOpt)\
  859. .filter(TiersOpt.entity_type==TheTiers.tiers_type)\
  860. .all()
  861. form.tiers_type.choices = TargetList
  862. else:
  863. TheTiers = Tiers()
  864. # prepare the form for creation
  865. form = TiersForm(request.POST, TheTiers, meta={'csrf_context': request.session})
  866. form.tiers_type.choices = TargetList
  867. UserOptions = list()
  868. #test_form = TiersForm(request.POST, TheTiers, meta={'csrf_context': request.session})
  869. if request.method == 'POST' and form.validate():
  870. ToDelete = list()
  871. # First, we remove entries no more present
  872. for obj in form.membership.object_data:
  873. MatchEntry = filter( lambda x: x.object_data and x.object_data._sa_instance_state == obj._sa_instance_state,
  874. form.membership.entries )
  875. if not MatchEntry:
  876. ToDelete.append(obj)
  877. # We should remove it as it's not in original data
  878. for obj in ToDelete:
  879. TheTiers.membership.remove(obj)
  880. DBSession.delete(obj)
  881. # Then, it's time to consider new entries
  882. for entry in form.membership.entries:
  883. if entry.object_data is None:
  884. TmpUser = User_Tiers()
  885. entry.object_data = TmpUser
  886. TheTiers.membership.append(TmpUser)
  887. form.membership.object_data = TheTiers.membership
  888. form.populate_obj(TheTiers)
  889. # Handle Remove of accents
  890. TheTiers.slug = slugify(form.name.data)
  891. if not form._fields.has_key('uid'):
  892. TheTiers.creator_id = request.user.uid
  893. DBSession.add(TheTiers)
  894. DBSession.flush()
  895. return HTTPFound(location=request.route_url('edit_entity', sep='/',
  896. entity_id=str(TheTiers.slug), tiers_type=TheTiers.get_entity_type.entity_type))
  897. DBSession.merge(TheTiers)
  898. return HTTPFound(location=request.route_url('entities'))
  899. MainTab = {'programme':'','presse':'', 'plan':'', 'participer':'',
  900. 'form':form, 'DBUser':User, 'UserOptions':UserOptions,
  901. 'logged_in':request.authenticated_userid }
  902. return MainTab
  903. @view_config(route_name='edit_entity_cat', renderer="jm2l:templates/edit_tiers_categ.mako")
  904. def edit_tiers_category(request):
  905. DicResult = dict()
  906. ListChanges = list()
  907. if request.method == 'POST':
  908. # Reformat data
  909. RegExist = re.compile('collection\[(?P<slug>[\w-]+)\]\[(?P<num>\d+)\]\[(?P<id>\d+)\]')
  910. RegTitle = re.compile('collection\[(?P<slug>[\w-]+)\]\[title]')
  911. RegNew = re.compile('collection\[(?P<slug>[\w-]+)\]\[(?P<num>\d+)\]\[id\]')
  912. for key, value in request.POST.iteritems():
  913. regN= RegNew.match(key)
  914. regT= RegTitle.match(key)
  915. reg = RegExist.match(key)
  916. if reg:
  917. if not DicResult.has_key(reg.group('slug')):
  918. DicResult[reg.group('slug')] = dict()
  919. if DicResult[reg.group('slug')].has_key('items'):
  920. DicResult[reg.group('slug')]['items'].append( ( int(reg.group('id')), value ) )
  921. else:
  922. DicResult[reg.group('slug')]['items'] = [ ( int(reg.group('id')), value ) ]
  923. elif regN:
  924. if not DicResult.has_key(regN.group('slug')):
  925. DicResult[regN.group('slug')] = dict()
  926. if DicResult[regN.group('slug')].has_key('items'):
  927. DicResult[regN.group('slug')]['items'].append( ( 'id', value ) )
  928. else:
  929. DicResult[regN.group('slug')]['items'] = [ ( 'id', value ) ]
  930. ListChanges.append(('add', 0, DicResult[regN.group('slug')]['title'], value))
  931. elif regT:
  932. if not DicResult.has_key(regT.group('slug')):
  933. DicResult[regT.group('slug')] = dict()
  934. DicResult[regT.group('slug')]['title'] = value
  935. else:
  936. raise
  937. for opt in DBSession.query(TiersOpt).all():
  938. if DicResult.has_key(opt.slug_entity_type):
  939. found = filter( lambda (x,y): opt.uid==x,
  940. DicResult[opt.slug_entity_type].get('items', []))
  941. if not found:
  942. ListChanges.append(('remove', opt.uid, opt.entity_type, opt.entity_subtype))
  943. else:
  944. for tst in found:
  945. # Check changes on Cat Name
  946. if DicResult[opt.slug_entity_type]['title']!=opt.entity_type or \
  947. tst[1]!=opt.entity_subtype:
  948. ListChanges.append(('changed', opt.uid,
  949. DicResult[opt.slug_entity_type]['title'],
  950. tst[1]))
  951. else:
  952. ListChanges.append(('remove', opt.uid, opt.entity_type, opt.entity_subtype))
  953. # Do The change
  954. for action, uid, entity, subentity in ListChanges:
  955. if action=="changed":
  956. opt = TiersOpt.by_id(uid)
  957. opt.entity_type = entity
  958. opt.entity_subtype = subentity
  959. elif action=="remove":
  960. opt = TiersOpt.by_id(uid)
  961. DBSession.delete(opt)
  962. elif action=="add":
  963. opt = TiersOpt()
  964. opt.entity_type = entity
  965. opt.entity_subtype = subentity
  966. DBSession.add(opt)
  967. MainTab = {'programme':'','presse':'', 'plan':'', 'participer':'',
  968. 'logged_in':request.authenticated_userid, 'TiersOpt':TiersOpt }
  969. return MainTab
  970. @view_config(route_name='show_user', renderer="jm2l:templates/view_user.mako")
  971. def show_user(request):
  972. user_slug = request.matchdict.get('user_slug', None)
  973. # Query database
  974. DispUser = User.by_slug(user_slug)
  975. MainTab = {'programme':'','presse':'', 'plan':'', 'participer':'',
  976. 'DispUser':DispUser, 'logged_in':request.authenticated_userid }
  977. return MainTab
  978. #@view_config(route_name='link_user_entity')
  979. def link_user_entity(request):
  980. uid = int(request.matchdict.get('uid', -1))
  981. year = int(request.matchdict.get('year', -1))
  982. user_id = int(request.matchdict.get('uid', -1))
  983. TheTiers = Tiers.by_id(uid)
  984. if TheTiers is None:
  985. raise HTTPNotFound()
  986. return HTTPFound(location=request.route_url('edit_entity', uid=uid) )
  987. #@view_config(route_name='link_role_entity')
  988. def link_role_entity(request):
  989. uid = int(request.matchdict.get('uid', -1))
  990. year = int(request.matchdict.get('year', -1))
  991. role_id = int(request.matchdict.get('role_id', -1))
  992. TheTiers = Tiers.by_id(uid)
  993. if TheTiers is None:
  994. raise HTTPNotFound()
  995. return HTTPFound(location=request.route_url('edit_entity', uid=uid) )
  996. @notfound_view_config()
  997. def notfound(reason, request):
  998. request.response.status = 404
  999. return render_to_response('jm2l:templates/Errors/404.mako', { "reason":reason },
  1000. request=request)
  1001. @forbidden_view_config()
  1002. def forbidden(reason, request):
  1003. #return Response('forbidden')
  1004. request.response.status = 404
  1005. return render_to_response('jm2l:templates/Errors/404.mako', { "reason":reason },
  1006. request=request)