0001-convert-remaining-files-from-.ptl-to-.py-3930.patch
extra/modules/events_ui.ptl | ||
---|---|---|
1 |
import time |
|
2 | ||
3 |
from quixote import get_request, get_response, get_session, redirect |
|
4 |
from quixote.directory import Directory, AccessControlled |
|
5 | ||
6 |
import wcs |
|
7 |
import wcs.admin.root |
|
8 |
from wcs.backoffice.menu import * |
|
9 | ||
10 |
from qommon import errors, misc |
|
11 |
from qommon.form import * |
|
12 |
from qommon.strftime import strftime |
|
13 | ||
14 |
from events import Event, RemoteCalendar, get_default_event_tags |
|
15 | ||
16 | ||
17 | ||
18 |
class RemoteCalendarDirectory(Directory): |
|
19 |
_q_exports = ['', 'edit', 'delete', 'update'] |
|
20 | ||
21 |
def __init__(self, calendar): |
|
22 |
self.calendar = calendar |
|
23 | ||
24 |
def _q_index [html] (self): |
|
25 |
form = Form(enctype='multipart/form-data') |
|
26 |
form.add_submit('edit', _('Edit')) |
|
27 |
form.add_submit('delete', _('Delete')) |
|
28 |
form.add_submit('update', _('Update')) |
|
29 |
form.add_submit('back', _('Back')) |
|
30 | ||
31 |
if form.get_submit() == 'edit': |
|
32 |
return redirect('edit') |
|
33 |
if form.get_submit() == 'update': |
|
34 |
return redirect('update') |
|
35 |
if form.get_submit() == 'delete': |
|
36 |
return redirect('delete') |
|
37 |
if form.get_submit() == 'back': |
|
38 |
return redirect('..') |
|
39 | ||
40 |
html_top('events', title = _('Remote Calendar: %s') % self.calendar.label) |
|
41 |
'<h2>%s</h2>' % _('Remote Calendar: %s') % self.calendar.label |
|
42 | ||
43 |
get_session().display_message() |
|
44 | ||
45 |
'<p>' |
|
46 |
self.calendar.url |
|
47 |
if self.calendar.error: |
|
48 |
' - <span class="error-message">%s</span>' % self.calendar.get_error_message() |
|
49 |
'</p>' |
|
50 | ||
51 |
if not self.calendar.content: |
|
52 |
'<p>' |
|
53 |
_('No content has been retrieved yet.') |
|
54 |
'</p>' |
|
55 |
else: |
|
56 |
'<ul>' |
|
57 |
for ev in sorted(self.calendar.events, lambda x,y: cmp(x.date_start, y.date_start)): |
|
58 |
'<li>' |
|
59 |
if ev.date_start: |
|
60 |
strftime(misc.date_format(), ev.date_start) |
|
61 |
if ev.date_end and ev.date_start[:3] != ev.date_end[:3]: |
|
62 |
' - ' |
|
63 |
strftime(misc.date_format(), ev.date_start) |
|
64 | ||
65 |
' : ' |
|
66 |
if ev.url: |
|
67 |
'<a href="%s">%s</a>' % (ev.url, ev.title) |
|
68 |
else: |
|
69 |
ev.title |
|
70 |
'</li>' |
|
71 |
'</ul>' |
|
72 | ||
73 |
form.render() |
|
74 | ||
75 |
def edit [html] (self): |
|
76 |
form = self.form() |
|
77 |
if form.get_submit() == 'cancel': |
|
78 |
return redirect('.') |
|
79 | ||
80 |
if form.is_submitted() and not form.has_errors(): |
|
81 |
self.submit(form) |
|
82 |
return redirect('..') |
|
83 | ||
84 |
html_top('events', title = _('Edit Remote Calendar: %s') % self.calendar.label) |
|
85 |
'<h2>%s</h2>' % _('Edit Remote Calendar: %s') % self.calendar.label |
|
86 |
form.render() |
|
87 | ||
88 | ||
89 |
def form(self): |
|
90 |
form = Form(enctype='multipart/form-data') |
|
91 |
form.add(StringWidget, 'label', title = _('Label'), required = True, |
|
92 |
value = self.calendar.label) |
|
93 |
form.add(StringWidget, 'url', title = _('URL'), required = True, |
|
94 |
value = self.calendar.url, size = 40) |
|
95 |
form.add_submit('submit', _('Submit')) |
|
96 |
form.add_submit('cancel', _('Cancel')) |
|
97 |
return form |
|
98 | ||
99 |
def submit(self, form): |
|
100 |
for k in ('label', 'url'): |
|
101 |
widget = form.get_widget(k) |
|
102 |
if widget: |
|
103 |
setattr(self.calendar, k, widget.parse()) |
|
104 |
self.calendar.store() |
|
105 | ||
106 |
def delete [html] (self): |
|
107 |
form = Form(enctype='multipart/form-data') |
|
108 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
109 |
'You are about to irrevocably delete this remote calendar.'))) |
|
110 |
form.add_submit('submit', _('Submit')) |
|
111 |
form.add_submit('cancel', _('Cancel')) |
|
112 |
if form.get_submit() == 'cancel': |
|
113 |
return redirect('..') |
|
114 |
if not form.is_submitted() or form.has_errors(): |
|
115 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
116 |
html_top('events', title = _('Delete Remote Calendar')) |
|
117 |
'<h2>%s</h2>' % _('Deleting Remote Calendar: %s') % self.calendar.label |
|
118 |
form.render() |
|
119 |
else: |
|
120 |
self.calendar.remove_self() |
|
121 |
return redirect('..') |
|
122 | ||
123 |
def update(self): |
|
124 |
get_session().message = ('info', |
|
125 |
_('Calendar update has been requested, reload in a few moments')) |
|
126 |
get_response().add_after_job('updating remote calendar', |
|
127 |
self.calendar.download_and_parse, |
|
128 |
fire_and_forget = True) |
|
129 |
return redirect('.') |
|
130 | ||
131 | ||
132 | ||
133 |
class RemoteCalendarsDirectory(Directory): |
|
134 |
_q_exports = ['', 'new'] |
|
135 | ||
136 |
def _q_traverse(self, path): |
|
137 |
get_response().breadcrumb.append(('remote/', _('Remote Calendars'))) |
|
138 |
return Directory._q_traverse(self, path) |
|
139 | ||
140 |
def _q_index [html] (self): |
|
141 |
return redirect('..') |
|
142 | ||
143 |
def new [html] (self): |
|
144 |
calendar_ui = RemoteCalendarDirectory(RemoteCalendar()) |
|
145 | ||
146 |
form = calendar_ui.form() |
|
147 |
if form.get_submit() == 'cancel': |
|
148 |
return redirect('.') |
|
149 | ||
150 |
if form.is_submitted() and not form.has_errors(): |
|
151 |
calendar_ui.submit(form) |
|
152 |
return redirect('%s/' % calendar_ui.calendar.id) |
|
153 | ||
154 |
get_response().breadcrumb.append(('new', _('New Remote Calendar'))) |
|
155 |
html_top('events', title = _('New Remote Calendar')) |
|
156 |
'<h2>%s</h2>' % _('New Remote Calendar') |
|
157 |
form.render() |
|
158 | ||
159 |
def _q_lookup(self, component): |
|
160 |
try: |
|
161 |
event = RemoteCalendar.get(component) |
|
162 |
except KeyError: |
|
163 |
raise errors.TraversalError() |
|
164 |
get_response().breadcrumb.append((str(event.id), event.label)) |
|
165 |
return RemoteCalendarDirectory(event) |
|
166 | ||
167 | ||
168 |
class EventDirectory(Directory): |
|
169 |
_q_exports = ['', 'edit', 'delete'] |
|
170 | ||
171 |
def __init__(self, event): |
|
172 |
self.event = event |
|
173 | ||
174 |
def _q_index [html] (self): |
|
175 |
form = Form(enctype='multipart/form-data') |
|
176 |
form.add_submit('edit', _('Edit')) |
|
177 |
form.add_submit('delete', _('Delete')) |
|
178 |
form.add_submit('back', _('Back')) |
|
179 | ||
180 |
if form.get_submit() == 'edit': |
|
181 |
return redirect('edit') |
|
182 |
if form.get_submit() == 'delete': |
|
183 |
return redirect('delete') |
|
184 |
if form.get_submit() == 'back': |
|
185 |
return redirect('..') |
|
186 | ||
187 |
html_top('events', title = _('Event: %s') % self.event.title) |
|
188 |
'<h2>%s</h2>' % _('Event: %s') % self.event.title |
|
189 |
'<p>' |
|
190 |
self.event.description |
|
191 |
'</p>' |
|
192 |
'<ul>' |
|
193 |
if self.event.location: |
|
194 |
'<li>%s: %s</li>' % (_('Location'), self.event.location) |
|
195 |
if self.event.organizer: |
|
196 |
'<li>%s: %s</li>' % (_('Organizer'), self.event.organizer) |
|
197 |
if self.event.url: |
|
198 |
'<li>%s: <a href="%s">%s</a></li>' % (_('URL'), self.event.url, self.event.url) |
|
199 |
'</ul>' |
|
200 | ||
201 |
if self.event.more_infos: |
|
202 |
'<p>' |
|
203 |
self.event.more_infos |
|
204 |
'</p>' |
|
205 | ||
206 |
form.render() |
|
207 | ||
208 |
def edit [html] (self): |
|
209 |
form = self.form() |
|
210 |
if form.get_submit() == 'cancel': |
|
211 |
return redirect('.') |
|
212 | ||
213 |
if form.is_submitted() and not form.has_errors(): |
|
214 |
self.submit(form) |
|
215 |
return redirect('..') |
|
216 | ||
217 |
html_top('events', title = _('Edit Event: %s') % self.event.title) |
|
218 |
'<h2>%s</h2>' % _('Edit Event: %s') % self.event.title |
|
219 |
form.render() |
|
220 | ||
221 | ||
222 |
def form(self): |
|
223 |
form = Form(enctype='multipart/form-data') |
|
224 |
form.add(StringWidget, 'title', title = _('Title'), required = True, |
|
225 |
value = self.event.title) |
|
226 |
form.add(TextWidget, 'description', title = _('Description'), |
|
227 |
cols = 70, rows = 10, |
|
228 |
required = True, value = self.event.description) |
|
229 |
form.add(StringWidget, 'url', title = _('URL'), required = False, |
|
230 |
value = self.event.url, size = 40) |
|
231 |
form.add(DateWidget, 'date_start', title = _('Start Date'), required = True, |
|
232 |
value = strftime(misc.date_format(), self.event.date_start)) |
|
233 |
form.add(DateWidget, 'date_end', title = _('End Date'), required = False, |
|
234 |
value = strftime(misc.date_format(), self.event.date_end)) |
|
235 |
form.add(TextWidget, 'location', title = _('Location'), |
|
236 |
cols = 70, rows = 4, |
|
237 |
required = False, value = self.event.location) |
|
238 |
form.add(StringWidget, 'organizer', title = _('Organizer'), required = False, |
|
239 |
value = self.event.organizer, size = 40) |
|
240 |
form.add(TextWidget, 'more_infos', title = _('More informations'), |
|
241 |
cols = 70, rows = 10, |
|
242 |
required = False, value = self.event.more_infos) |
|
243 |
form.add(TagsWidget, 'keywords', title = _('Keywords'), |
|
244 |
value = self.event.keywords, size = 50, |
|
245 |
known_tags = get_cfg('misc', {}).get('event_tags', get_default_event_tags())) |
|
246 |
form.add_submit('submit', _('Submit')) |
|
247 |
form.add_submit('cancel', _('Cancel')) |
|
248 |
return form |
|
249 | ||
250 |
def submit(self, form): |
|
251 |
for k in ('title', 'description', 'url', 'date_start', 'date_end', |
|
252 |
'organizer', 'location', 'more_infos', 'keywords'): |
|
253 |
widget = form.get_widget(k) |
|
254 |
if widget: |
|
255 |
if k in ('date_start', 'date_end'): |
|
256 |
# convert dates to 9-item tuples |
|
257 |
v = widget.parse() |
|
258 |
if v: |
|
259 |
setattr(self.event, k, time.strptime(v, misc.date_format())) |
|
260 |
else: |
|
261 |
setattr(self.event, k, None) |
|
262 |
else: |
|
263 |
setattr(self.event, k, widget.parse()) |
|
264 |
self.event.store() |
|
265 | ||
266 |
def delete [html] (self): |
|
267 |
form = Form(enctype='multipart/form-data') |
|
268 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
269 |
'You are about to irrevocably delete this event.'))) |
|
270 |
form.add_submit('submit', _('Submit')) |
|
271 |
form.add_submit('cancel', _('Cancel')) |
|
272 |
if form.get_submit() == 'cancel': |
|
273 |
return redirect('..') |
|
274 |
if not form.is_submitted() or form.has_errors(): |
|
275 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
276 |
html_top('events', title = _('Delete Event')) |
|
277 |
'<h2>%s</h2>' % _('Deleting Event: %s') % self.event.title |
|
278 |
form.render() |
|
279 |
else: |
|
280 |
self.event.remove_self() |
|
281 |
return redirect('..') |
|
282 | ||
283 | ||
284 | ||
285 | ||
286 |
class EventsDirectory(AccessControlled, Directory): |
|
287 |
_q_exports = ['', 'new', 'listing', 'remote'] |
|
288 |
label = N_('Events') |
|
289 | ||
290 |
remote = RemoteCalendarsDirectory() |
|
291 | ||
292 |
def _q_access(self): |
|
293 |
user = get_request().user |
|
294 |
if not user: |
|
295 |
raise errors.AccessUnauthorizedError() |
|
296 |
admin_role = get_cfg('aq-permissions', {}).get('events', None) |
|
297 |
if not (user.is_admin or admin_role in (user.roles or [])): |
|
298 |
raise errors.AccessForbiddenError( |
|
299 |
public_msg = _('You are not allowed to access Events Management'), |
|
300 |
location_hint = 'backoffice') |
|
301 | ||
302 |
get_response().breadcrumb.append(('events/', _('Events'))) |
|
303 | ||
304 | ||
305 |
def _q_index [html] (self): |
|
306 |
html_top('events', _('Events')) |
|
307 | ||
308 |
'<ul id="main-actions">' |
|
309 |
' <li><a class="new-item" href="new">%s</a></li>' % _('New Event') |
|
310 |
' <li><a class="new-item" href="remote/new">%s</a></li>' % _('New Remote Calendar') |
|
311 |
'</ul>' |
|
312 | ||
313 |
'<div class="splitcontent-left">' |
|
314 | ||
315 |
'<div class="bo-block">' |
|
316 |
events = Event.select() |
|
317 |
'<h2>%s</h2>' % _('Events') |
|
318 |
if not events: |
|
319 |
'<p>' |
|
320 |
_('There is no event defined at the moment.') |
|
321 |
'</p>' |
|
322 |
'<ul class="biglist" id="events-list">' |
|
323 |
for l in events: |
|
324 |
event_id = l.id |
|
325 |
'<li class="biglistitem" id="itemId_%s">' % event_id |
|
326 |
'<strong class="label"><a href="%s/">%s</a></strong>' % (event_id, l.title) |
|
327 |
' - ' |
|
328 |
l.format_date() |
|
329 |
'<p class="commands">' |
|
330 |
command_icon('%s/edit' % event_id, 'edit') |
|
331 |
command_icon('%s/delete' % event_id, 'remove') |
|
332 |
'</p></li>' |
|
333 |
'</ul>' |
|
334 |
'</div>' |
|
335 |
'</div>' |
|
336 | ||
337 |
'<div class="splitcontent-right">' |
|
338 |
'<div class="bo-block">' |
|
339 |
rcalendars = RemoteCalendar.select() |
|
340 |
'<h2>%s</h2>' % _('Remote Calendars') |
|
341 |
if not rcalendars: |
|
342 |
'<p>' |
|
343 |
_('There is no remote calendars defined at the moment.') |
|
344 |
'</p>' |
|
345 | ||
346 |
'<ul class="biglist" id="events-list">' |
|
347 |
for l in rcalendars: |
|
348 |
rcal_id = l.id |
|
349 |
'<li class="biglistitem" id="itemId_%s">' % rcal_id |
|
350 |
'<strong class="label"><a href="remote/%s/">%s</a></strong>' % (rcal_id, l.label) |
|
351 |
'<p class="details">' |
|
352 |
l.url |
|
353 |
if l.error: |
|
354 |
'<br /><span class="error-message">%s</span>' % l.get_error_message() |
|
355 |
'</p>' |
|
356 |
'<p class="commands">' |
|
357 |
command_icon('remote/%s/edit' % rcal_id, 'edit') |
|
358 |
command_icon('remote/%s/delete' % rcal_id, 'remove') |
|
359 |
'</p></li>' |
|
360 |
'</ul>' |
|
361 |
'</div>' |
|
362 |
'</div>' |
|
363 | ||
364 |
def new [html] (self): |
|
365 |
event_ui = EventDirectory(Event()) |
|
366 | ||
367 |
form = event_ui.form() |
|
368 |
if form.get_submit() == 'cancel': |
|
369 |
return redirect('.') |
|
370 | ||
371 |
if form.is_submitted() and not form.has_errors(): |
|
372 |
event_ui.submit(form) |
|
373 |
return redirect('%s/' % event_ui.event.id) |
|
374 | ||
375 |
get_response().breadcrumb.append(('new', _('New Event'))) |
|
376 |
html_top('events', title = _('New Event')) |
|
377 |
'<h2>%s</h2>' % _('New Event') |
|
378 |
form.render() |
|
379 | ||
380 |
def _q_lookup(self, component): |
|
381 |
try: |
|
382 |
event = Event.get(component) |
|
383 |
except KeyError: |
|
384 |
raise errors.TraversalError() |
|
385 |
get_response().breadcrumb.append((str(event.id), event.title)) |
|
386 |
return EventDirectory(event) |
|
387 | ||
388 |
def listing(self): |
|
389 |
return redirect('.') |
|
390 |
extra/modules/events_ui.py | ||
---|---|---|
1 |
import time |
|
2 | ||
3 |
from quixote import get_request, get_response, get_session, redirect |
|
4 |
from quixote.directory import Directory, AccessControlled |
|
5 |
from quixote.html import TemplateIO, htmltext |
|
6 | ||
7 |
import wcs |
|
8 |
import wcs.admin.root |
|
9 |
from wcs.backoffice.menu import * |
|
10 | ||
11 |
from qommon import errors, misc |
|
12 |
from qommon.form import * |
|
13 |
from qommon.strftime import strftime |
|
14 | ||
15 |
from events import Event, RemoteCalendar, get_default_event_tags |
|
16 | ||
17 | ||
18 | ||
19 |
class RemoteCalendarDirectory(Directory): |
|
20 |
_q_exports = ['', 'edit', 'delete', 'update'] |
|
21 | ||
22 |
def __init__(self, calendar): |
|
23 |
self.calendar = calendar |
|
24 | ||
25 |
def _q_index(self): |
|
26 |
form = Form(enctype='multipart/form-data') |
|
27 |
form.add_submit('edit', _('Edit')) |
|
28 |
form.add_submit('delete', _('Delete')) |
|
29 |
form.add_submit('update', _('Update')) |
|
30 |
form.add_submit('back', _('Back')) |
|
31 | ||
32 |
if form.get_submit() == 'edit': |
|
33 |
return redirect('edit') |
|
34 |
if form.get_submit() == 'update': |
|
35 |
return redirect('update') |
|
36 |
if form.get_submit() == 'delete': |
|
37 |
return redirect('delete') |
|
38 |
if form.get_submit() == 'back': |
|
39 |
return redirect('..') |
|
40 | ||
41 |
html_top('events', title = _('Remote Calendar: %s') % self.calendar.label) |
|
42 |
r = TemplateIO(html=True) |
|
43 |
r += htmltext('<h2>%s</h2>') % _('Remote Calendar: %s') % self.calendar.label |
|
44 | ||
45 |
r += get_session().display_message() |
|
46 | ||
47 |
r += htmltext('<p>') |
|
48 |
self.calendar.url |
|
49 |
if self.calendar.error: |
|
50 |
r += htmltext(' - <span class="error-message">%s</span>') % self.calendar.get_error_message() |
|
51 |
r += htmltext('</p>') |
|
52 | ||
53 |
if not self.calendar.content: |
|
54 |
r += htmltext('<p>') |
|
55 |
r += _('No content has been retrieved yet.') |
|
56 |
r += htmltext('</p>') |
|
57 |
else: |
|
58 |
r += htmltext('<ul>') |
|
59 |
for ev in sorted(self.calendar.events, lambda x,y: cmp(x.date_start, y.date_start)): |
|
60 |
r += htmltext('<li>') |
|
61 |
if ev.date_start: |
|
62 |
r += strftime(misc.date_format(), ev.date_start) |
|
63 |
if ev.date_end and ev.date_start[:3] != ev.date_end[:3]: |
|
64 |
r += ' - ' |
|
65 |
r += strftime(misc.date_format(), ev.date_start) |
|
66 | ||
67 |
r += ' : ' |
|
68 |
if ev.url: |
|
69 |
r += htmltext('<a href="%s">%s</a>') % (ev.url, ev.title) |
|
70 |
else: |
|
71 |
r += ev.title |
|
72 |
r += htmltext('</li>') |
|
73 |
r += htmltext('</ul>') |
|
74 | ||
75 |
r += form.render() |
|
76 |
return r.getvalue() |
|
77 | ||
78 |
def edit(self): |
|
79 |
form = self.form() |
|
80 |
if form.get_submit() == 'cancel': |
|
81 |
return redirect('.') |
|
82 | ||
83 |
if form.is_submitted() and not form.has_errors(): |
|
84 |
self.submit(form) |
|
85 |
return redirect('..') |
|
86 | ||
87 |
html_top('events', title = _('Edit Remote Calendar: %s') % self.calendar.label) |
|
88 |
r = TemplateIO(html=True) |
|
89 |
r += htmltext('<h2>%s</h2>') % _('Edit Remote Calendar: %s') % self.calendar.label |
|
90 |
r += form.render() |
|
91 |
return r.getvalue() |
|
92 | ||
93 |
def form(self): |
|
94 |
form = Form(enctype='multipart/form-data') |
|
95 |
form.add(StringWidget, 'label', title = _('Label'), required = True, |
|
96 |
value = self.calendar.label) |
|
97 |
form.add(StringWidget, 'url', title = _('URL'), required = True, |
|
98 |
value = self.calendar.url, size = 40) |
|
99 |
form.add_submit('submit', _('Submit')) |
|
100 |
form.add_submit('cancel', _('Cancel')) |
|
101 |
return form |
|
102 | ||
103 |
def submit(self, form): |
|
104 |
for k in ('label', 'url'): |
|
105 |
widget = form.get_widget(k) |
|
106 |
if widget: |
|
107 |
setattr(self.calendar, k, widget.parse()) |
|
108 |
self.calendar.store() |
|
109 | ||
110 |
def delete(self): |
|
111 |
form = Form(enctype='multipart/form-data') |
|
112 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
113 |
'You are about to irrevocably delete this remote calendar.'))) |
|
114 |
form.add_submit('submit', _('Submit')) |
|
115 |
form.add_submit('cancel', _('Cancel')) |
|
116 |
if form.get_submit() == 'cancel': |
|
117 |
return redirect('..') |
|
118 |
if not form.is_submitted() or form.has_errors(): |
|
119 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
120 |
html_top('events', title = _('Delete Remote Calendar')) |
|
121 |
r = TemplateIO(html=True) |
|
122 |
r += htmltext('<h2>%s</h2>') % _('Deleting Remote Calendar: %s') % self.calendar.label |
|
123 |
r += form.render() |
|
124 |
return r.getvalue() |
|
125 |
else: |
|
126 |
self.calendar.remove_self() |
|
127 |
return redirect('..') |
|
128 | ||
129 |
def update(self): |
|
130 |
get_session().message = ('info', |
|
131 |
_('Calendar update has been requested, reload in a few moments')) |
|
132 |
get_response().add_after_job('updating remote calendar', |
|
133 |
self.calendar.download_and_parse, |
|
134 |
fire_and_forget = True) |
|
135 |
return redirect('.') |
|
136 | ||
137 | ||
138 | ||
139 |
class RemoteCalendarsDirectory(Directory): |
|
140 |
_q_exports = ['', 'new'] |
|
141 | ||
142 |
def _q_traverse(self, path): |
|
143 |
get_response().breadcrumb.append(('remote/', _('Remote Calendars'))) |
|
144 |
return Directory._q_traverse(self, path) |
|
145 | ||
146 |
def _q_index(self): |
|
147 |
return redirect('..') |
|
148 | ||
149 |
def new(self): |
|
150 |
calendar_ui = RemoteCalendarDirectory(RemoteCalendar()) |
|
151 | ||
152 |
form = calendar_ui.form() |
|
153 |
if form.get_submit() == 'cancel': |
|
154 |
return redirect('.') |
|
155 | ||
156 |
if form.is_submitted() and not form.has_errors(): |
|
157 |
calendar_ui.submit(form) |
|
158 |
return redirect('%s/' % calendar_ui.calendar.id) |
|
159 | ||
160 |
get_response().breadcrumb.append(('new', _('New Remote Calendar'))) |
|
161 |
html_top('events', title = _('New Remote Calendar')) |
|
162 |
r = TemplateIO(html=True) |
|
163 |
r += htmltext('<h2>%s</h2>') % _('New Remote Calendar') |
|
164 |
r += form.render() |
|
165 |
return r.getvalue() |
|
166 | ||
167 |
def _q_lookup(self, component): |
|
168 |
try: |
|
169 |
event = RemoteCalendar.get(component) |
|
170 |
except KeyError: |
|
171 |
raise errors.TraversalError() |
|
172 |
get_response().breadcrumb.append((str(event.id), event.label)) |
|
173 |
return RemoteCalendarDirectory(event) |
|
174 | ||
175 | ||
176 |
class EventDirectory(Directory): |
|
177 |
_q_exports = ['', 'edit', 'delete'] |
|
178 | ||
179 |
def __init__(self, event): |
|
180 |
self.event = event |
|
181 | ||
182 |
def _q_index(self): |
|
183 |
form = Form(enctype='multipart/form-data') |
|
184 |
form.add_submit('edit', _('Edit')) |
|
185 |
form.add_submit('delete', _('Delete')) |
|
186 |
form.add_submit('back', _('Back')) |
|
187 | ||
188 |
if form.get_submit() == 'edit': |
|
189 |
return redirect('edit') |
|
190 |
if form.get_submit() == 'delete': |
|
191 |
return redirect('delete') |
|
192 |
if form.get_submit() == 'back': |
|
193 |
return redirect('..') |
|
194 | ||
195 |
html_top('events', title = _('Event: %s') % self.event.title) |
|
196 |
r = TemplateIO(html=True) |
|
197 |
r += htmltext('<h2>%s</h2>') % _('Event: %s') % self.event.title |
|
198 |
r += htmltext('<p>') |
|
199 |
r += self.event.description |
|
200 |
r += htmltext('</p>') |
|
201 |
r += htmltext('<ul>') |
|
202 |
if self.event.location: |
|
203 |
r += htmltext('<li>%s: %s</li>') % (_('Location'), self.event.location) |
|
204 |
if self.event.organizer: |
|
205 |
r += htmltext('<li>%s: %s</li>') % (_('Organizer'), self.event.organizer) |
|
206 |
if self.event.url: |
|
207 |
r += htmltext('<li>%s: <a href="%s">%s</a></li>') % (_('URL'), self.event.url, self.event.url) |
|
208 |
r += htmltext('</ul>') |
|
209 | ||
210 |
if self.event.more_infos: |
|
211 |
r += htmltext('<p>') |
|
212 |
r += self.event.more_infos |
|
213 |
r += htmltext('</p>') |
|
214 | ||
215 |
r += form.render() |
|
216 |
return r.getvalue() |
|
217 | ||
218 |
def edit(self): |
|
219 |
form = self.form() |
|
220 |
if form.get_submit() == 'cancel': |
|
221 |
return redirect('.') |
|
222 | ||
223 |
if form.is_submitted() and not form.has_errors(): |
|
224 |
self.submit(form) |
|
225 |
return redirect('..') |
|
226 | ||
227 |
html_top('events', title = _('Edit Event: %s') % self.event.title) |
|
228 |
r = TemplateIO(html=True) |
|
229 |
r += htmltext('<h2>%s</h2>') % _('Edit Event: %s') % self.event.title |
|
230 |
r += form.render() |
|
231 |
return r.getvalue() |
|
232 | ||
233 |
def form(self): |
|
234 |
form = Form(enctype='multipart/form-data') |
|
235 |
form.add(StringWidget, 'title', title = _('Title'), required = True, |
|
236 |
value = self.event.title) |
|
237 |
form.add(TextWidget, 'description', title = _('Description'), |
|
238 |
cols = 70, rows = 10, |
|
239 |
required = True, value = self.event.description) |
|
240 |
form.add(StringWidget, 'url', title = _('URL'), required = False, |
|
241 |
value = self.event.url, size = 40) |
|
242 |
form.add(DateWidget, 'date_start', title = _('Start Date'), required = True, |
|
243 |
value = strftime(misc.date_format(), self.event.date_start)) |
|
244 |
form.add(DateWidget, 'date_end', title = _('End Date'), required = False, |
|
245 |
value = strftime(misc.date_format(), self.event.date_end)) |
|
246 |
form.add(TextWidget, 'location', title = _('Location'), |
|
247 |
cols = 70, rows = 4, |
|
248 |
required = False, value = self.event.location) |
|
249 |
form.add(StringWidget, 'organizer', title = _('Organizer'), required = False, |
|
250 |
value = self.event.organizer, size = 40) |
|
251 |
form.add(TextWidget, 'more_infos', title = _('More informations'), |
|
252 |
cols = 70, rows = 10, |
|
253 |
required = False, value = self.event.more_infos) |
|
254 |
form.add(TagsWidget, 'keywords', title = _('Keywords'), |
|
255 |
value = self.event.keywords, size = 50, |
|
256 |
known_tags = get_cfg('misc', {}).get('event_tags', get_default_event_tags())) |
|
257 |
form.add_submit('submit', _('Submit')) |
|
258 |
form.add_submit('cancel', _('Cancel')) |
|
259 |
return form |
|
260 | ||
261 |
def submit(self, form): |
|
262 |
for k in ('title', 'description', 'url', 'date_start', 'date_end', |
|
263 |
'organizer', 'location', 'more_infos', 'keywords'): |
|
264 |
widget = form.get_widget(k) |
|
265 |
if widget: |
|
266 |
if k in ('date_start', 'date_end'): |
|
267 |
# convert dates to 9-item tuples |
|
268 |
v = widget.parse() |
|
269 |
if v: |
|
270 |
setattr(self.event, k, time.strptime(v, misc.date_format())) |
|
271 |
else: |
|
272 |
setattr(self.event, k, None) |
|
273 |
else: |
|
274 |
setattr(self.event, k, widget.parse()) |
|
275 |
self.event.store() |
|
276 | ||
277 |
def delete(self): |
|
278 |
form = Form(enctype='multipart/form-data') |
|
279 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
280 |
'You are about to irrevocably delete this event.'))) |
|
281 |
form.add_submit('submit', _('Submit')) |
|
282 |
form.add_submit('cancel', _('Cancel')) |
|
283 |
if form.get_submit() == 'cancel': |
|
284 |
return redirect('..') |
|
285 |
if not form.is_submitted() or form.has_errors(): |
|
286 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
287 |
html_top('events', title = _('Delete Event')) |
|
288 |
r = TemplateIO(html=True) |
|
289 |
r += htmltext('<h2>%s</h2>') % _('Deleting Event: %s') % self.event.title |
|
290 |
r += form.render() |
|
291 |
return r.getvalue() |
|
292 |
else: |
|
293 |
self.event.remove_self() |
|
294 |
return redirect('..') |
|
295 | ||
296 | ||
297 | ||
298 | ||
299 |
class EventsDirectory(AccessControlled, Directory): |
|
300 |
_q_exports = ['', 'new', 'listing', 'remote'] |
|
301 |
label = N_('Events') |
|
302 | ||
303 |
remote = RemoteCalendarsDirectory() |
|
304 | ||
305 |
def _q_access(self): |
|
306 |
user = get_request().user |
|
307 |
if not user: |
|
308 |
raise errors.AccessUnauthorizedError() |
|
309 |
admin_role = get_cfg('aq-permissions', {}).get('events', None) |
|
310 |
if not (user.is_admin or admin_role in (user.roles or [])): |
|
311 |
raise errors.AccessForbiddenError( |
|
312 |
public_msg = _('You are not allowed to access Events Management'), |
|
313 |
location_hint = 'backoffice') |
|
314 | ||
315 |
get_response().breadcrumb.append(('events/', _('Events'))) |
|
316 | ||
317 | ||
318 |
def _q_index(self): |
|
319 |
html_top('events', _('Events')) |
|
320 |
r = TemplateIO(html=True) |
|
321 | ||
322 |
r += htmltext('<ul id="main-actions">') |
|
323 |
r += htmltext(' <li><a class="new-item" href="new">%s</a></li>') % _('New Event') |
|
324 |
r += htmltext(' <li><a class="new-item" href="remote/new">%s</a></li>') % _('New Remote Calendar') |
|
325 |
r += htmltext('</ul>') |
|
326 | ||
327 |
r += htmltext('<div class="splitcontent-left">') |
|
328 | ||
329 |
r += htmltext('<div class="bo-block">') |
|
330 |
events = Event.select() |
|
331 |
r += htmltext('<h2>%s</h2>') % _('Events') |
|
332 |
if not events: |
|
333 |
r += htmltext('<p>') |
|
334 |
r += _('There is no event defined at the moment.') |
|
335 |
r += htmltext('</p>') |
|
336 |
r += htmltext('<ul class="biglist" id="events-list">') |
|
337 |
for l in events: |
|
338 |
event_id = l.id |
|
339 |
r += htmltext('<li class="biglistitem" id="itemId_%s">') % event_id |
|
340 |
r += htmltext('<strong class="label"><a href="%s/">%s</a></strong>') % (event_id, l.title) |
|
341 |
r += ' - ' |
|
342 |
r += l.format_date() |
|
343 |
r += htmltext('<p class="commands">') |
|
344 |
r += command_icon('%s/edit' % event_id, 'edit') |
|
345 |
r += command_icon('%s/delete' % event_id, 'remove') |
|
346 |
r += htmltext('</p></li>') |
|
347 |
r += htmltext('</ul>') |
|
348 |
r += htmltext('</div>') |
|
349 |
r += htmltext('</div>') |
|
350 | ||
351 |
r += htmltext('<div class="splitcontent-right">') |
|
352 |
r += htmltext('<div class="bo-block">') |
|
353 |
rcalendars = RemoteCalendar.select() |
|
354 |
r += htmltext('<h2>%s</h2>') % _('Remote Calendars') |
|
355 |
if not rcalendars: |
|
356 |
r += htmltext('<p>') |
|
357 |
r += _('There is no remote calendars defined at the moment.') |
|
358 |
r += htmltext('</p>') |
|
359 | ||
360 |
r += htmltext('<ul class="biglist" id="events-list">') |
|
361 |
for l in rcalendars: |
|
362 |
rcal_id = l.id |
|
363 |
r += htmltext('<li class="biglistitem" id="itemId_%s">') % rcal_id |
|
364 |
r += htmltext('<strong class="label"><a href="remote/%s/">%s</a></strong>') % (rcal_id, l.label) |
|
365 |
r += htmltext('<p class="details">') |
|
366 |
r += l.url |
|
367 |
if l.error: |
|
368 |
r += htmltext('<br /><span class="error-message">%s</span>') % l.get_error_message() |
|
369 |
r += htmltext('</p>') |
|
370 |
r += htmltext('<p class="commands">') |
|
371 |
r += command_icon('remote/%s/edit' % rcal_id, 'edit') |
|
372 |
r += command_icon('remote/%s/delete' % rcal_id, 'remove') |
|
373 |
r += htmltext('</p></li>') |
|
374 |
r += htmltext('</ul>') |
|
375 |
r += htmltext('</div>') |
|
376 |
r += htmltext('</div>') |
|
377 |
return r.getvalue() |
|
378 | ||
379 |
def new(self): |
|
380 |
event_ui = EventDirectory(Event()) |
|
381 | ||
382 |
form = event_ui.form() |
|
383 |
if form.get_submit() == 'cancel': |
|
384 |
return redirect('.') |
|
385 | ||
386 |
if form.is_submitted() and not form.has_errors(): |
|
387 |
event_ui.submit(form) |
|
388 |
return redirect('%s/' % event_ui.event.id) |
|
389 | ||
390 |
get_response().breadcrumb.append(('new', _('New Event'))) |
|
391 |
html_top('events', title = _('New Event')) |
|
392 |
r = TemplateIO(html=True) |
|
393 |
r += htmltext('<h2>%s</h2>') % _('New Event') |
|
394 |
r += form.render() |
|
395 |
return r.getvalue() |
|
396 | ||
397 |
def _q_lookup(self, component): |
|
398 |
try: |
|
399 |
event = Event.get(component) |
|
400 |
except KeyError: |
|
401 |
raise errors.TraversalError() |
|
402 |
get_response().breadcrumb.append((str(event.id), event.title)) |
|
403 |
return EventDirectory(event) |
|
404 | ||
405 |
def listing(self): |
|
406 |
return redirect('.') |
extra/modules/formpage.ptl | ||
---|---|---|
1 |
from quixote import get_publisher, get_request, redirect |
|
2 |
from quixote.directory import Directory |
|
3 |
from quixote.html import htmltext |
|
4 | ||
5 |
import os |
|
6 | ||
7 |
import wcs |
|
8 |
import wcs.forms.root |
|
9 |
from qommon import template |
|
10 |
from qommon import errors |
|
11 |
from qommon.form import * |
|
12 |
from wcs.roles import logged_users_role |
|
13 | ||
14 |
from qommon import emails |
|
15 | ||
16 |
OldFormPage = wcs.forms.root.FormPage |
|
17 | ||
18 |
class AlternateFormPage(OldFormPage): |
|
19 |
def step(self, *args, **kwargs): |
|
20 |
steps_html = OldFormPage.step(self, *args, **kwargs) |
|
21 |
steps_html = str(steps_html).replace('<ol>', '<h2>%s</h2>\n<ol>' % _('Steps')) |
|
22 |
get_response().filter['gauche'] = steps_html |
|
23 |
get_response().filter['steps'] = steps_html |
|
24 |
return |
|
25 | ||
26 |
wcs.forms.root.FormPage = AlternateFormPage |
|
27 | ||
28 | ||
29 |
OldFormsRootDirectory = wcs.forms.root.RootDirectory |
|
30 | ||
31 |
class AlternateFormsRootDirectory(OldFormsRootDirectory): |
|
32 |
def form_list(self, *args, **kwargs): |
|
33 |
form_list = OldFormsRootDirectory.form_list(self, *args, **kwargs) |
|
34 |
return htmltext(str(form_list).replace('h2>', 'h3>')) |
|
35 | ||
36 |
wcs.forms.root.RootDirectory = AlternateFormsRootDirectory |
extra/modules/formpage.py | ||
---|---|---|
1 |
from quixote import get_publisher, get_request, redirect |
|
2 |
from quixote.directory import Directory |
|
3 |
from quixote.html import htmltext |
|
4 | ||
5 |
import os |
|
6 | ||
7 |
import wcs |
|
8 |
import wcs.forms.root |
|
9 |
from qommon import template |
|
10 |
from qommon import errors |
|
11 |
from qommon.form import * |
|
12 |
from wcs.roles import logged_users_role |
|
13 | ||
14 |
from qommon import emails |
|
15 | ||
16 |
OldFormPage = wcs.forms.root.FormPage |
|
17 | ||
18 |
class AlternateFormPage(OldFormPage): |
|
19 |
def step(self, *args, **kwargs): |
|
20 |
steps_html = OldFormPage.step(self, *args, **kwargs) |
|
21 |
steps_html = str(steps_html).replace('<ol>', '<h2>%s</h2>\n<ol>' % _('Steps')) |
|
22 |
get_response().filter['gauche'] = steps_html |
|
23 |
get_response().filter['steps'] = steps_html |
|
24 |
return |
|
25 | ||
26 |
wcs.forms.root.FormPage = AlternateFormPage |
|
27 | ||
28 | ||
29 |
OldFormsRootDirectory = wcs.forms.root.RootDirectory |
|
30 | ||
31 |
class AlternateFormsRootDirectory(OldFormsRootDirectory): |
|
32 |
def form_list(self, *args, **kwargs): |
|
33 |
form_list = OldFormsRootDirectory.form_list(self, *args, **kwargs) |
|
34 |
return htmltext(str(form_list).replace('h2>', 'h3>')) |
|
35 | ||
36 |
wcs.forms.root.RootDirectory = AlternateFormsRootDirectory |
extra/modules/msp_ui.ptl | ||
---|---|---|
1 |
import urllib |
|
2 |
import urllib2 |
|
3 |
import urlparse |
|
4 |
import json |
|
5 |
import base64 |
|
6 |
import datetime |
|
7 | ||
8 |
from quixote import get_publisher, get_request, redirect, get_response, get_session_manager, get_session |
|
9 |
from quixote.directory import AccessControlled, Directory |
|
10 | ||
11 |
import qommon.form |
|
12 |
from qommon.misc import http_post_request, http_get_page |
|
13 | ||
14 | ||
15 |
class MSPDirectory(Directory): |
|
16 |
_q_exports = ['', 'pick', 'download'] |
|
17 | ||
18 |
@property |
|
19 |
def msp_gateway_base_url(self): |
|
20 |
return get_publisher().get_site_option('msp') |
|
21 | ||
22 |
@property |
|
23 |
def authorize_url(self): |
|
24 |
return urlparse.urljoin(self.msp_gateway_base_url, 'authorize/') |
|
25 | ||
26 |
@property |
|
27 |
def access_token_url(self): |
|
28 |
return urlparse.urljoin(self.msp_gateway_base_url, 'access_token/') |
|
29 | ||
30 |
@property |
|
31 |
def documents_url(self): |
|
32 |
return urlparse.urljoin(self.msp_gateway_base_url, 'documents/') |
|
33 | ||
34 |
@property |
|
35 |
def document_url(self): |
|
36 |
return urlparse.urljoin(self.msp_gateway_base_url, 'documents/%s/') |
|
37 | ||
38 |
@property |
|
39 |
def client_id(self): |
|
40 |
return get_publisher().get_site_option('msp_client_id') |
|
41 | ||
42 |
@property |
|
43 |
def client_secret(self): |
|
44 |
return get_publisher().get_site_option('msp_client_secret') |
|
45 | ||
46 |
def fix_document(self, document): |
|
47 |
site_charset = get_publisher().site_charset |
|
48 |
for key, value in document.iteritems(): |
|
49 |
# date are returned as millisecond POSIX timestamp, |
|
50 |
# it should be ISO-8601 instead |
|
51 |
if key.endswith('Date') and value is not None: |
|
52 |
document[key] = unicode(datetime.date.fromtimestamp(value // 1000)) |
|
53 |
value = document[key] = document[key].replace(u'-', u'\u2011') |
|
54 |
if isinstance(value, unicode) and key != 'content': |
|
55 |
document[key] = value.encode(site_charset) |
|
56 | ||
57 |
def get_documents(self, access_token): |
|
58 |
response, status, content, auth_header = http_get_page(self.documents_url, { |
|
59 |
'Authorization': 'Bearer %s' % access_token, |
|
60 |
}); |
|
61 |
documents = json.loads(content) |
|
62 |
for document in documents: |
|
63 |
self.fix_document(document) |
|
64 |
return documents |
|
65 | ||
66 |
def get_document(self, doc_id, access_token): |
|
67 |
response, status, content, auth_header = http_get_page( |
|
68 |
self.document_url % doc_id, |
|
69 |
{ 'Authorization': 'Bearer %s' % access_token, } |
|
70 |
); |
|
71 |
document = json.loads(content) |
|
72 |
self.fix_document(document) |
|
73 |
return document |
|
74 | ||
75 |
def authorize(self, self_url, scope): |
|
76 |
params = { |
|
77 |
'redirect_uri': self_url, |
|
78 |
'response_type': 'code', |
|
79 |
'scope': scope, |
|
80 |
} |
|
81 |
return redirect('%s?%s' % ( |
|
82 |
self.authorize_url, urllib.urlencode(params))) |
|
83 | ||
84 |
def access_token(self, self_url, code): |
|
85 |
params = { |
|
86 |
'code': code, |
|
87 |
'client_id': self.client_id, |
|
88 |
'client_secret': self.client_secret, |
|
89 |
'grant_type': 'authorization_code', |
|
90 |
'redirect_uri': self_url, |
|
91 |
} |
|
92 |
response, status, data, auth_header = http_post_request(self.access_token_url, |
|
93 |
urllib.urlencode(params), { 'Content-Type': 'application/x-www-form-urlencoded' }) |
|
94 |
return json.loads(data)['access_token'] |
|
95 | ||
96 |
def pick (self): |
|
97 |
request = get_request() |
|
98 |
frontoffice_url = get_publisher().get_frontoffice_url() |
|
99 |
self_url = frontoffice_url |
|
100 |
self_url += '/msp/pick' |
|
101 |
self_url = self_url.replace('://', '://iframe-') |
|
102 | ||
103 |
if 'code' not in request.form and 'error' not in request.form: |
|
104 |
return self.authorize(self_url, 'LIST_DOCS') |
|
105 |
access_token = self.access_token(self_url, request.form['code']) |
|
106 |
return self.pick_display(access_token) |
|
107 | ||
108 |
def pick_display [html] (self, access_token): |
|
109 |
request = get_request() |
|
110 | ||
111 |
get_response().add_javascript(['jquery.js', |
|
112 |
'tablesorter/jquery.tablesorter.min.js']) |
|
113 |
get_response().add_javascript_code( |
|
114 |
str('''$(function() { $("table.sortable").tablesorter(); });''')) |
|
115 |
get_response().add_css_include('../js/tablesorter/themes/blue/style.css') |
|
116 | ||
117 |
frontoffice_url = get_publisher().get_frontoffice_url() |
|
118 | ||
119 |
'<h2>%s</h2>' % _('Pick a file') |
|
120 | ||
121 |
if 'error' not in request.form: |
|
122 |
documents = self.get_documents(access_token) |
|
123 |
'<table id="msp-pick-file-table" class="sortable tablesorter">' |
|
124 |
'<thead>' |
|
125 |
'<tr>' |
|
126 |
'<th>%s</th>' % _('Filename') |
|
127 |
'<th>%s</th>' % _('Expiration date') |
|
128 |
'</tr>' |
|
129 |
'</thead>' |
|
130 |
'<tbody>' |
|
131 |
for document in documents: |
|
132 |
'<tr>' |
|
133 |
for key in ('name', 'expirationDate'): |
|
134 |
'<td class="msp-pick-file-table-%s">' % key |
|
135 |
if key == 'name': |
|
136 |
'<a href="%s/msp/download?doc_id=%s">' % \ |
|
137 |
(frontoffice_url, document['id']) |
|
138 |
'%s' % (document[key] or '') |
|
139 |
if key == 'name': |
|
140 |
'</a>' |
|
141 |
'</td>' |
|
142 |
'</tr>' |
|
143 |
'</tbody>' |
|
144 |
'</table>' |
|
145 |
else: |
|
146 |
'<p>%s</p>' % _('Unable to access your mon.Service-Public.fr documents') |
|
147 | ||
148 |
def set_token [html] (self, token, document): |
|
149 |
get_response().add_javascript(['jquery.js']) |
|
150 |
get_response().page_template_key = 'iframe' |
|
151 |
'<html><body>' |
|
152 |
'<pre>Token: %s</pre>' % token |
|
153 |
'<script>window.top.document.set_token("%s", "%s");</script>' % (token, document['name']) |
|
154 |
'</body></html>' |
|
155 | ||
156 |
def download(self): |
|
157 |
request = get_request() |
|
158 |
assert 'doc_id' in request.form |
|
159 |
doc_id = request.form['doc_id'] |
|
160 |
frontoffice_url = get_publisher().get_frontoffice_url() |
|
161 |
self_url = frontoffice_url |
|
162 |
self_url += '/msp/download?%s' % urllib.urlencode({'doc_id': doc_id}) |
|
163 |
if 'code' not in request.form and 'error' not in request.form: |
|
164 |
return self.authorize(self_url, 'GET_DOC') |
|
165 |
if 'error' in request.form: |
|
166 |
return self.download_error() |
|
167 |
else: |
|
168 |
access_token = self.access_token(self_url, request.form['code']) |
|
169 |
document = self.get_document(doc_id, access_token) |
|
170 |
download = qommon.form.PicklableUpload(document['name'], |
|
171 |
content_type='application/pdf') |
|
172 |
download.__setstate__({ |
|
173 |
'data': base64.b64decode(document['content']), |
|
174 |
}) |
|
175 |
token = get_session().add_tempfile(download) |
|
176 |
return self.set_token(token, document) |
extra/modules/msp_ui.py | ||
---|---|---|
1 |
import urllib |
|
2 |
import urllib2 |
|
3 |
import urlparse |
|
4 |
import json |
|
5 |
import base64 |
|
6 |
import datetime |
|
7 | ||
8 |
from quixote import get_publisher, get_request, redirect, get_response, get_session_manager, get_session |
|
9 |
from quixote.directory import AccessControlled, Directory |
|
10 |
from quixote.html import TemplateIO, htmltext |
|
11 | ||
12 |
import qommon.form |
|
13 |
from qommon.misc import http_post_request, http_get_page |
|
14 | ||
15 | ||
16 |
class MSPDirectory(Directory): |
|
17 |
_q_exports = ['', 'pick', 'download'] |
|
18 | ||
19 |
@property |
|
20 |
def msp_gateway_base_url(self): |
|
21 |
return get_publisher().get_site_option('msp') |
|
22 | ||
23 |
@property |
|
24 |
def authorize_url(self): |
|
25 |
return urlparse.urljoin(self.msp_gateway_base_url, 'authorize/') |
|
26 | ||
27 |
@property |
|
28 |
def access_token_url(self): |
|
29 |
return urlparse.urljoin(self.msp_gateway_base_url, 'access_token/') |
|
30 | ||
31 |
@property |
|
32 |
def documents_url(self): |
|
33 |
return urlparse.urljoin(self.msp_gateway_base_url, 'documents/') |
|
34 | ||
35 |
@property |
|
36 |
def document_url(self): |
|
37 |
return urlparse.urljoin(self.msp_gateway_base_url, 'documents/%s/') |
|
38 | ||
39 |
@property |
|
40 |
def client_id(self): |
|
41 |
return get_publisher().get_site_option('msp_client_id') |
|
42 | ||
43 |
@property |
|
44 |
def client_secret(self): |
|
45 |
return get_publisher().get_site_option('msp_client_secret') |
|
46 | ||
47 |
def fix_document(self, document): |
|
48 |
site_charset = get_publisher().site_charset |
|
49 |
for key, value in document.iteritems(): |
|
50 |
# date are returned as millisecond POSIX timestamp, |
|
51 |
# it should be ISO-8601 instead |
|
52 |
if key.endswith('Date') and value is not None: |
|
53 |
document[key] = unicode(datetime.date.fromtimestamp(value // 1000)) |
|
54 |
value = document[key] = document[key].replace(u'-', u'\u2011') |
|
55 |
if isinstance(value, unicode) and key != 'content': |
|
56 |
document[key] = value.encode(site_charset) |
|
57 | ||
58 |
def get_documents(self, access_token): |
|
59 |
response, status, content, auth_header = http_get_page(self.documents_url, { |
|
60 |
'Authorization': 'Bearer %s' % access_token, |
|
61 |
}); |
|
62 |
documents = json.loads(content) |
|
63 |
for document in documents: |
|
64 |
self.fix_document(document) |
|
65 |
return documents |
|
66 | ||
67 |
def get_document(self, doc_id, access_token): |
|
68 |
response, status, content, auth_header = http_get_page( |
|
69 |
self.document_url % doc_id, |
|
70 |
{ 'Authorization': 'Bearer %s' % access_token, } |
|
71 |
); |
|
72 |
document = json.loads(content) |
|
73 |
self.fix_document(document) |
|
74 |
return document |
|
75 | ||
76 |
def authorize(self, self_url, scope): |
|
77 |
params = { |
|
78 |
'redirect_uri': self_url, |
|
79 |
'response_type': 'code', |
|
80 |
'scope': scope, |
|
81 |
} |
|
82 |
return redirect('%s?%s' % ( |
|
83 |
self.authorize_url, urllib.urlencode(params))) |
|
84 | ||
85 |
def access_token(self, self_url, code): |
|
86 |
params = { |
|
87 |
'code': code, |
|
88 |
'client_id': self.client_id, |
|
89 |
'client_secret': self.client_secret, |
|
90 |
'grant_type': 'authorization_code', |
|
91 |
'redirect_uri': self_url, |
|
92 |
} |
|
93 |
response, status, data, auth_header = http_post_request(self.access_token_url, |
|
94 |
urllib.urlencode(params), { 'Content-Type': 'application/x-www-form-urlencoded' }) |
|
95 |
return json.loads(data)['access_token'] |
|
96 | ||
97 |
def pick (self): |
|
98 |
request = get_request() |
|
99 |
frontoffice_url = get_publisher().get_frontoffice_url() |
|
100 |
self_url = frontoffice_url |
|
101 |
self_url += '/msp/pick' |
|
102 |
self_url = self_url.replace('://', '://iframe-') |
|
103 | ||
104 |
if 'code' not in request.form and 'error' not in request.form: |
|
105 |
return self.authorize(self_url, 'LIST_DOCS') |
|
106 |
access_token = self.access_token(self_url, request.form['code']) |
|
107 |
return self.pick_display(access_token) |
|
108 | ||
109 |
def pick_display(self, access_token): |
|
110 |
r = TemplateIO(html=True) |
|
111 | ||
112 |
request = get_request() |
|
113 | ||
114 |
get_response().add_javascript(['jquery.js', |
|
115 |
'tablesorter/jquery.tablesorter.min.js']) |
|
116 |
get_response().add_javascript_code( |
|
117 |
str('''$(function() { $("table.sortable").tablesorter(); });''')) |
|
118 |
get_response().add_css_include('../js/tablesorter/themes/blue/style.css') |
|
119 | ||
120 |
frontoffice_url = get_publisher().get_frontoffice_url() |
|
121 | ||
122 |
r += htmltext('<h2>%s</h2>') % _('Pick a file') |
|
123 | ||
124 |
if 'error' not in request.form: |
|
125 |
documents = self.get_documents(access_token) |
|
126 |
r += htmltext('<table id="msp-pick-file-table" class="sortable tablesorter">') |
|
127 |
r += htmltext('<thead>') |
|
128 |
r += htmltext('<tr>') |
|
129 |
r += htmltext('<th>%s</th>') % _('Filename') |
|
130 |
r += htmltext('<th>%s</th>') % _('Expiration date') |
|
131 |
r += htmltext('</tr>') |
|
132 |
r += htmltext('</thead>') |
|
133 |
r += htmltext('<tbody>') |
|
134 |
for document in documents: |
|
135 |
r += htmltext('<tr>') |
|
136 |
for key in ('name', 'expirationDate'): |
|
137 |
r += htmltext('<td class="msp-pick-file-table-%s">' % key) |
|
138 |
if key == 'name': |
|
139 |
r += htmltext('<a href="%s/msp/download?doc_id=%s">' % \ |
|
140 |
(frontoffice_url, document['id'])) |
|
141 |
r += '%s' % (document[key] or '') |
|
142 |
if key == 'name': |
|
143 |
r += htmltext('</a>') |
|
144 |
r += htmltext('</td>') |
|
145 |
r += htmltext('</tr>') |
|
146 |
r += htmltext('</tbody>') |
|
147 |
r += htmltext('</table>') |
|
148 |
else: |
|
149 |
r += htmltext('<p>%s</p>') % _('Unable to access your mon.Service-Public.fr documents') |
|
150 |
return r.getvalue() |
|
151 | ||
152 |
def set_token(self, token, document): |
|
153 |
get_response().add_javascript(['jquery.js']) |
|
154 |
get_response().page_template_key = 'iframe' |
|
155 |
r = TemplateIO(html=True) |
|
156 |
r += htmltext('<html><body>') |
|
157 |
r += htmltext('<pre>Token: %s</pre>') % token |
|
158 |
r += htmltext('<script>window.top.document.set_token("%s", "%s");</script>' % ( |
|
159 |
token, document['name'])) |
|
160 |
r += htmltext('</body></html>') |
|
161 |
return r.getvalue() |
|
162 | ||
163 |
def download(self): |
|
164 |
request = get_request() |
|
165 |
assert 'doc_id' in request.form |
|
166 |
doc_id = request.form['doc_id'] |
|
167 |
frontoffice_url = get_publisher().get_frontoffice_url() |
|
168 |
self_url = frontoffice_url |
|
169 |
self_url += '/msp/download?%s' % urllib.urlencode({'doc_id': doc_id}) |
|
170 |
if 'code' not in request.form and 'error' not in request.form: |
|
171 |
return self.authorize(self_url, 'GET_DOC') |
|
172 |
if 'error' in request.form: |
|
173 |
return self.download_error() |
|
174 |
else: |
|
175 |
access_token = self.access_token(self_url, request.form['code']) |
|
176 |
document = self.get_document(doc_id, access_token) |
|
177 |
download = qommon.form.PicklableUpload(document['name'], |
|
178 |
content_type='application/pdf') |
|
179 |
download.__setstate__({ |
|
180 |
'data': base64.b64decode(document['content']), |
|
181 |
}) |
|
182 |
token = get_session().add_tempfile(download) |
|
183 |
return self.set_token(token, document) |
extra/modules/myspace.ptl | ||
---|---|---|
1 |
try: |
|
2 |
import lasso |
|
3 |
except ImportError: |
|
4 |
pass |
|
5 | ||
6 |
import json |
|
7 | ||
8 |
from quixote import get_publisher, get_request, redirect, get_response, get_session_manager, get_session |
|
9 |
from quixote.directory import AccessControlled, Directory |
|
10 |
from quixote.util import StaticFile, FileStream |
|
11 | ||
12 |
from qommon import template |
|
13 |
from qommon.form import * |
|
14 |
from qommon import get_cfg, get_logger |
|
15 |
from qommon import errors |
|
16 |
from wcs.api import get_user_from_api_query_string |
|
17 | ||
18 |
import qommon.ident.password |
|
19 |
from qommon.ident.password_accounts import PasswordAccount |
|
20 | ||
21 |
from qommon.admin.texts import TextsDirectory |
|
22 | ||
23 |
from wcs.formdef import FormDef |
|
24 |
import root |
|
25 | ||
26 |
from announces import AnnounceSubscription |
|
27 |
from strongbox import StrongboxItem, StrongboxType |
|
28 |
from payments import Invoice, Regie, is_payment_supported |
|
29 |
import msp_ui |
|
30 | ||
31 |
class MyInvoicesDirectory(Directory): |
|
32 |
_q_exports = [''] |
|
33 | ||
34 |
def _q_traverse(self, path): |
|
35 |
if not is_payment_supported(): |
|
36 |
raise errors.TraversalError() |
|
37 |
get_response().breadcrumb.append(('invoices/', _('Invoices'))) |
|
38 |
return Directory._q_traverse(self, path) |
|
39 | ||
40 |
def _q_index [html] (self): |
|
41 |
user = get_request().user |
|
42 |
if not user or user.anonymous: |
|
43 |
raise errors.AccessUnauthorizedError() |
|
44 | ||
45 |
template.html_top(_('Invoices')) |
|
46 |
TextsDirectory.get_html_text('aq-myspace-invoice') |
|
47 | ||
48 |
get_session().display_message() |
|
49 | ||
50 |
invoices = [] |
|
51 |
invoices.extend(Invoice.get_with_indexed_value( |
|
52 |
str('user_id'), str(user.id))) |
|
53 |
try: |
|
54 |
invoices.extend(Invoice.get_with_indexed_value( |
|
55 |
str('user_hash'), str(user.hash))) |
|
56 |
except AttributeError: |
|
57 |
pass |
|
58 | ||
59 |
def cmp_invoice(a, b): |
|
60 |
t = cmp(a.regie_id, b.regie_id) |
|
61 |
if t != 0: |
|
62 |
return t |
|
63 |
return -cmp(a.date, b.date) |
|
64 | ||
65 |
invoices.sort(cmp_invoice) |
|
66 | ||
67 |
last_regie_id = None |
|
68 |
unpaid = False |
|
69 |
for invoice in invoices: |
|
70 |
if invoice.regie_id != last_regie_id: |
|
71 |
if last_regie_id: |
|
72 |
'</ul>' |
|
73 |
if unpaid: |
|
74 |
'<input type="submit" value="%s"/>' % _('Pay Selected Invoices') |
|
75 |
'</form>' |
|
76 |
last_regie_id = invoice.regie_id |
|
77 |
'<h3>%s</h3>' % Regie.get(last_regie_id).label |
|
78 |
unpaid = False |
|
79 |
'<form action="%s/invoices/multiple">' % get_publisher().get_frontoffice_url() |
|
80 |
'<ul>' |
|
81 | ||
82 |
'<li>' |
|
83 |
if not (invoice.paid or invoice.canceled): |
|
84 |
'<input type="checkbox" name="invoice" value="%s"/>' % invoice.id |
|
85 |
unpaid = True |
|
86 |
misc.localstrftime(invoice.date) |
|
87 |
' - ' |
|
88 |
'%s' % invoice.subject |
|
89 |
' - ' |
|
90 |
'%s' % invoice.amount |
|
91 |
' €' |
|
92 |
' - ' |
|
93 |
button = '<span class="paybutton">%s</span>' % _('Pay') |
|
94 |
if invoice.canceled: |
|
95 |
_('canceled on %s') % misc.localstrftime(invoice.canceled_date) |
|
96 |
' - ' |
|
97 |
button = _('Details') |
|
98 |
if invoice.paid: |
|
99 |
_('paid on %s') % misc.localstrftime(invoice.paid_date) |
|
100 |
' - ' |
|
101 |
button = _('Details') |
|
102 |
'<a href="%s/invoices/%s">%s</a>' % (get_publisher().get_frontoffice_url(), |
|
103 |
invoice.id, button) |
|
104 |
'</li>' |
|
105 | ||
106 |
if last_regie_id: |
|
107 |
'</ul>' |
|
108 |
if unpaid: |
|
109 |
'<input type="submit" value="%s"/>' % _('Pay Selected Invoices') |
|
110 |
'</form>' |
|
111 | ||
112 |
class StrongboxDirectory(Directory): |
|
113 |
_q_exports = ['', 'add', 'download', 'remove', 'pick', 'validate'] |
|
114 | ||
115 |
def _q_traverse(self, path): |
|
116 |
if not get_cfg('misc', {}).get('aq-strongbox'): |
|
117 |
raise errors.TraversalError() |
|
118 |
get_response().breadcrumb.append(('strongbox/', _('Strongbox'))) |
|
119 |
return Directory._q_traverse(self, path) |
|
120 | ||
121 |
def get_form(self): |
|
122 |
types = [(x.id, x.label) for x in StrongboxType.select()] |
|
123 |
form = Form(action='add', enctype='multipart/form-data') |
|
124 |
form.add(StringWidget, 'description', title=_('Description'), size=60) |
|
125 |
form.add(FileWidget, 'file', title=_('File'), required=True) |
|
126 |
form.add(SingleSelectWidget, 'type_id', title=_('Document Type'), |
|
127 |
options = [(None, _('Not specified'))] + types) |
|
128 |
form.add(DateWidget, 'date_time', title = _('Document Date')) |
|
129 |
form.add_submit('submit', _('Upload')) |
|
130 |
return form |
|
131 | ||
132 |
def _q_index [html] (self): |
|
133 |
template.html_top(_('Strongbox')) |
|
134 | ||
135 |
# TODO: a paragraph of explanations here could be useful |
|
136 | ||
137 |
sffiles = StrongboxItem.get_with_indexed_value( |
|
138 |
str('user_id'), str(get_request().user.id)) |
|
139 |
if sffiles: |
|
140 |
'<table id="strongbox-items">' |
|
141 |
'<tr><th></th><th>%s</th><th>%s</th><th></th></tr>' % ( |
|
142 |
_('Type'), _('Expiration')) |
|
143 |
else: |
|
144 |
'<p>' |
|
145 |
_('There is currently nothing in your strongbox.') |
|
146 |
'</p>' |
|
147 |
has_items_to_validate = False |
|
148 |
for i, sffile in enumerate(sffiles): |
|
149 |
expired = False |
|
150 |
if not sffile.validated_time: |
|
151 |
has_items_to_validate = True |
|
152 |
continue |
|
153 |
if sffile.expiration_time and sffile.expiration_time < time.localtime(): |
|
154 |
expired = True |
|
155 |
if i%2: |
|
156 |
classnames = ['odd'] |
|
157 |
else: |
|
158 |
classnames = ['even'] |
|
159 |
if expired: |
|
160 |
classnames.append('expired') |
|
161 |
'<tr class="%s">' % ' '.join(classnames) |
|
162 |
'<td class="label">' |
|
163 |
sffile.get_display_name() |
|
164 |
'</td>' |
|
165 |
if sffile.type_id: |
|
166 |
'<td class="type">%s</td>' % StrongboxType.get(sffile.type_id).label |
|
167 |
else: |
|
168 |
'<td class="type">-</td>' |
|
169 |
if sffile.expiration_time: |
|
170 |
'<td class="expiration">%s' % strftime(misc.date_format(), sffile.expiration_time) |
|
171 |
if expired: |
|
172 |
' (%s)' % _('expired') |
|
173 |
'</td>' |
|
174 |
else: |
|
175 |
'<td class="expiration">-</td>' |
|
176 |
'<td class="actions">' |
|
177 |
' [<a href="download?id=%s">%s</a>] ' % (sffile.id, _('download')) |
|
178 |
'[<a rel="popup" href="remove?id=%s">%s</a>] ' % (sffile.id, _('remove')) |
|
179 |
'</td>' |
|
180 |
'</tr>' |
|
181 | ||
182 |
if has_items_to_validate: |
|
183 |
'<tr><td colspan="4"><h3>%s</h3></td></tr>' % _('Proposed Items') |
|
184 |
for sffile in sffiles: |
|
185 |
if sffile.validated_time: |
|
186 |
continue |
|
187 |
if sffile.expiration_time and sffile.expiration_time < time.localtime(): |
|
188 |
expired = True |
|
189 |
if i%2: |
|
190 |
classnames = ['odd'] |
|
191 |
else: |
|
192 |
classnames = ['even'] |
|
193 |
if expired: |
|
194 |
classnames.append('expired') |
|
195 |
'<tr class="%s">' % ' '.join(classnames) |
|
196 | ||
197 |
'<td class="label">' |
|
198 |
sffile.get_display_name() |
|
199 |
'</td>' |
|
200 |
if sffile.type_id: |
|
201 |
'<td class="type">%s</td>' % StrongboxType.get(sffile.type_id).label |
|
202 |
else: |
|
203 |
'<td class="type">-</td>' |
|
204 | ||
205 |
if sffile.expiration_time: |
|
206 |
'<td class="expiration">%s' % strftime(misc.date_format(), sffile.expiration_time) |
|
207 |
if expired: |
|
208 |
' (%s)' % _('expired') |
|
209 |
'</td>' |
|
210 |
else: |
|
211 |
'<td class="expiration">-</td>' |
|
212 |
'<td class="actions">' |
|
213 |
' [<a href="download?id=%s">%s</a>] ' % (sffile.id, _('download')) |
|
214 |
' [<a href="validate?id=%s">%s</a>] ' % (sffile.id, _('validate')) |
|
215 |
' [<a href="remove?id=%s">%s</a>] ' % (sffile.id, _('reject')) |
|
216 |
'</td>' |
|
217 |
'</tr>' |
|
218 |
if sffiles: |
|
219 |
'</table>' |
|
220 | ||
221 |
'<h3>%s</h3>' % _('Add a file to the strongbox') |
|
222 |
form = self.get_form() |
|
223 |
form.render() |
|
224 | ||
225 |
def add(self): |
|
226 |
form = self.get_form() |
|
227 |
if not form.is_submitted(): |
|
228 |
if get_request().form.get('mode') == 'pick': |
|
229 |
return redirect('pick') |
|
230 |
else: |
|
231 |
return redirect('.') |
|
232 | ||
233 |
sffile = StrongboxItem() |
|
234 |
sffile.user_id = get_request().user.id |
|
235 |
sffile.description = form.get_widget('description').parse() |
|
236 |
sffile.validated_time = time.localtime() |
|
237 |
sffile.type_id = form.get_widget('type_id').parse() |
|
238 |
v = form.get_widget('date_time').parse() |
|
239 |
sffile.set_expiration_time_from_date(v) |
|
240 |
sffile.store() |
|
241 |
sffile.set_file(form.get_widget('file').parse()) |
|
242 |
sffile.store() |
|
243 |
if get_request().form.get('mode') == 'pick': |
|
244 |
return redirect('pick') |
|
245 |
else: |
|
246 |
return redirect('.') |
|
247 | ||
248 |
def download(self): |
|
249 |
id = get_request().form.get('id') |
|
250 |
if not id: |
|
251 |
raise errors.TraversalError() |
|
252 |
try: |
|
253 |
sffile = StrongboxItem.get(id) |
|
254 |
except KeyError: |
|
255 |
raise errors.TraversalError() |
|
256 |
if str(sffile.user_id) != str(get_request().user.id): |
|
257 |
raise errors.TraversalError() |
|
258 | ||
259 |
filename = sffile.file.filename |
|
260 |
fd = file(filename) |
|
261 |
size = os.path.getsize(filename) |
|
262 |
response = get_response() |
|
263 |
response.set_content_type('application/octet-stream') |
|
264 |
response.set_header('content-disposition', 'attachment; filename="%s"' % sffile.file.base_filename) |
|
265 |
return FileStream(fd, size) |
|
266 | ||
267 |
def validate(self): |
|
268 |
id = get_request().form.get('id') |
|
269 |
if not id: |
|
270 |
raise errors.TraversalError() |
|
271 |
try: |
|
272 |
sffile = StrongboxItem.get(id) |
|
273 |
except KeyError: |
|
274 |
raise errors.TraversalError() |
|
275 |
if str(sffile.user_id) != str(get_request().user.id): |
|
276 |
raise errors.TraversalError() |
|
277 |
sffile.validated_time = time.time() |
|
278 |
sffile.store() |
|
279 |
return redirect('.') |
|
280 | ||
281 |
def remove [html] (self): |
|
282 |
id = get_request().form.get('id') |
|
283 |
if not id: |
|
284 |
raise errors.TraversalError() |
|
285 |
try: |
|
286 |
sffile = StrongboxItem.get(id) |
|
287 |
except KeyError: |
|
288 |
raise errors.TraversalError() |
|
289 |
if str(sffile.user_id) != str(get_request().user.id): |
|
290 |
raise errors.TraversalError() |
|
291 | ||
292 |
form = Form(enctype='multipart/form-data') |
|
293 |
form.add_hidden('id', get_request().form.get('id')) |
|
294 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
295 |
'You are about to irrevocably delete this item from your strongbox.'))) |
|
296 |
form.add_submit('submit', _('Submit')) |
|
297 |
form.add_submit('cancel', _('Cancel')) |
|
298 |
if form.get_submit() == 'cancel': |
|
299 |
return redirect('.') |
|
300 |
if not form.is_submitted() or form.has_errors(): |
|
301 |
if sffile.type_id: |
|
302 |
'<h2>%s</h2>' % _('Deleting %(filetype)s: %(filename)s') % { |
|
303 |
'filetype': StrongboxType.get(sffile.type_id).label, |
|
304 |
'filename': sffile.get_display_name() |
|
305 |
} |
|
306 |
else: |
|
307 |
'<h2>%s</h2>' % _('Deleting %(filename)s') % {'filename': sffile.get_display_name()} |
|
308 |
form.render() |
|
309 |
else: |
|
310 |
sffile.remove_self() |
|
311 |
sffile.remove_file() |
|
312 |
return redirect('.') |
|
313 | ||
314 |
def picked_file(self): |
|
315 |
get_response().set_content_type('application/json') |
|
316 |
sffile = StrongboxItem.get(get_request().form.get('val')) |
|
317 |
sffile.file.fp = file(sffile.file.filename) |
|
318 |
if sffile.user_id != get_request().user.id: |
|
319 |
raise errors.TraversalError() |
|
320 |
# XXX: this will copy the file, it would be quite nice if it was |
|
321 |
# possible to just make it a symlink to the sffile |
|
322 |
token = get_session().add_tempfile(sffile.file) |
|
323 |
return json.dumps({'token': token, 'filename': sffile.file.base_filename}) |
|
324 | ||
325 |
def pick [html] (self): |
|
326 |
if get_request().form.get('select') == 'true': |
|
327 |
return self.picked_file() |
|
328 |
root_url = get_publisher().get_root_url() |
|
329 |
sffiles = StrongboxItem.get_with_indexed_value( |
|
330 |
str('user_id'), str(get_request().user.id)) |
|
331 |
'<h2>%s</h2>' % _('Pick a file') |
|
332 | ||
333 |
if not sffiles: |
|
334 |
'<p>' |
|
335 |
_('You do not have any file in your strongbox at the moment.') |
|
336 |
'</p>' |
|
337 |
'<div class="buttons">' |
|
338 |
'<a href="%smyspace/strongbox/" target="_blank">%s</a>' % (root_url, |
|
339 |
_('Open Strongbox Management')) |
|
340 |
'</div>' |
|
341 |
else: |
|
342 |
'<form id="strongbox-pick">' |
|
343 |
'<ul>' |
|
344 |
for sffile in sffiles: |
|
345 |
'<li><label><input type="radio" name="file" value="%s"/>%s</label>' % ( |
|
346 |
sffile.id, sffile.get_display_name()) |
|
347 |
' [<a href="%smyspace/strongbox/download?id=%s">%s</a>] ' % ( |
|
348 |
root_url, sffile.id, _('view')) |
|
349 |
'</li>' |
|
350 |
'</ul>' |
|
351 | ||
352 |
'<div class="buttons">' |
|
353 |
'<input name="cancel" type="button" value="%s"/>' % _('Cancel') |
|
354 |
' ' |
|
355 |
'<input name="pick" type="button" value="%s"/>' % _('Pick') |
|
356 |
'</div>' |
|
357 |
'</form>' |
|
358 | ||
359 |
class JsonDirectory(Directory): |
|
360 |
'''Export of several lists in json, related to the current user or the |
|
361 |
SAMLv2 NameID we'd get in the URL''' |
|
362 | ||
363 |
_q_exports = ['forms'] |
|
364 | ||
365 |
user = None |
|
366 | ||
367 |
def _q_traverse(self, path): |
|
368 |
self.user = get_user_from_api_query_string() or get_request().user |
|
369 |
if not self.user: |
|
370 |
raise errors.AccessUnauthorizedError() |
|
371 |
return Directory._q_traverse(self, path) |
|
372 | ||
373 |
def forms(self): |
|
374 |
formdefs = FormDef.select(lambda x: not x.is_disabled(), order_by = 'name') |
|
375 |
user_forms = [] |
|
376 |
for formdef in formdefs: |
|
377 |
user_forms.extend(formdef.data_class().get_with_indexed_value( |
|
378 |
'user_id', self.user.id)) |
|
379 |
try: |
|
380 |
user_forms.extend(formdef.data_class().get_with_indexed_value( |
|
381 |
'user_hash', self.user.hash)) |
|
382 |
except AttributeError: |
|
383 |
pass |
|
384 |
user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) |
|
385 | ||
386 |
get_response().set_content_type('application/json') |
|
387 | ||
388 | ||
389 |
forms_output = [] |
|
390 |
for form in user_forms: |
|
391 |
visible_status = form.get_visible_status(user=self.user) |
|
392 |
# skip hidden forms |
|
393 |
if not visible_status: |
|
394 |
continue |
|
395 |
name = form.formdef.name |
|
396 |
id = form.get_display_id() |
|
397 |
status = visible_status.name |
|
398 |
title = _('%(name)s #%(id)s (%(status)s)') % { |
|
399 |
'name': name, |
|
400 |
'id': id, |
|
401 |
'status': status |
|
402 |
} |
|
403 |
url = form.get_url() |
|
404 |
d = { 'title': title, 'url': url } |
|
405 |
d.update(form.get_substitution_variables(minimal=True)) |
|
406 |
forms_output.append(d) |
|
407 |
return json.dumps(forms_output) |
|
408 | ||
409 | ||
410 |
class MyspaceDirectory(Directory): |
|
411 |
_q_exports = ['', 'profile', 'new', 'password', 'remove', 'announces', |
|
412 |
'strongbox', 'invoices', 'json', 'msp'] |
|
413 | ||
414 |
msp = msp_ui.MSPDirectory() |
|
415 |
strongbox = StrongboxDirectory() |
|
416 |
invoices = MyInvoicesDirectory() |
|
417 |
json = JsonDirectory() |
|
418 | ||
419 |
def _q_traverse(self, path): |
|
420 |
if (path[0] not in ('new', 'json')) and (not get_request().user or get_request().user.anonymous): |
|
421 |
raise errors.AccessUnauthorizedError() |
|
422 |
get_response().filter['bigdiv'] = 'profile' |
|
423 |
get_response().breadcrumb.append(('myspace/', _('My Space'))) |
|
424 | ||
425 |
# Migrate custom text settings |
|
426 |
texts_cfg = get_cfg('texts', {}) |
|
427 |
if 'text-aq-top-of-profile' in texts_cfg and ( |
|
428 |
not 'text-top-of-profile' in texts_cfg): |
|
429 |
texts_cfg['text-top-of-profile'] = texts_cfg['text-aq-top-of-profile'] |
|
430 |
del texts_cfg['text-aq-top-of-profile'] |
|
431 |
get_publisher().write_cfg() |
|
432 | ||
433 |
return Directory._q_traverse(self, path) |
|
434 | ||
435 | ||
436 |
def _q_index [html] (self): |
|
437 |
user = get_request().user |
|
438 |
if not user: |
|
439 |
raise errors.AccessUnauthorizedError() |
|
440 |
template.html_top(_('My Space')) |
|
441 |
if user.anonymous: |
|
442 |
return redirect('new') |
|
443 | ||
444 |
user_formdef = user.get_formdef() |
|
445 | ||
446 |
user_forms = [] |
|
447 |
if user: |
|
448 |
formdefs = FormDef.select(lambda x: not x.is_disabled(), order_by = 'name') |
|
449 |
user_forms = [] |
|
450 |
for formdef in formdefs: |
|
451 |
user_forms.extend(formdef.data_class().get_with_indexed_value( |
|
452 |
'user_id', user.id)) |
|
453 |
try: |
|
454 |
user_forms.extend(formdef.data_class().get_with_indexed_value( |
|
455 |
'user_hash', user.hash)) |
|
456 |
except AttributeError: |
|
457 |
pass |
|
458 |
user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) |
|
459 | ||
460 |
profile_links = [] |
|
461 |
if not get_cfg('sp', {}).get('idp-manage-user-attributes', False): |
|
462 |
if user_formdef: |
|
463 |
profile_links.append('<a href="#my-profile">%s</a>' % _('My Profile')) |
|
464 |
if user_forms: |
|
465 |
profile_links.append('<a href="#my-forms">%s</a>' % _('My Forms')) |
|
466 |
if get_cfg('misc', {}).get('aq-strongbox'): |
|
467 |
profile_links.append('<a href="strongbox/">%s</a>' % _('My Strongbox')) |
|
468 |
if is_payment_supported(): |
|
469 |
profile_links.append('<a href="invoices/">%s</a>' % _('My Invoices')) |
|
470 | ||
471 |
root_url = get_publisher().get_root_url() |
|
472 |
if user.can_go_in_backoffice(): |
|
473 |
profile_links.append('<a href="%sbackoffice/">%s</a>' % (root_url, _('Back office'))) |
|
474 |
if user.is_admin: |
|
475 |
profile_links.append('<a href="%sadmin/">%s</a>' % (root_url, _('Admin'))) |
|
476 | ||
477 |
if profile_links: |
|
478 |
'<p id="profile-links">' |
|
479 |
' - '.join(profile_links) |
|
480 |
'</p>' |
|
481 | ||
482 |
if not get_cfg('sp', {}).get('idp-manage-user-attributes', False): |
|
483 |
if user_formdef: |
|
484 |
self._my_profile(user_formdef, user) |
|
485 | ||
486 |
self._index_buttons(user_formdef) |
|
487 | ||
488 |
try: |
|
489 |
x = PasswordAccount.get_on_index(get_request().user.id, str('user_id')) |
|
490 |
except KeyError: |
|
491 |
pass |
|
492 |
else: |
|
493 |
'<p>' |
|
494 |
_('You can delete your account freely from the services portal. ' |
|
495 |
'This action is irreversible; it will destruct your personal ' |
|
496 |
'datas and destruct the access to your request history.') |
|
497 |
' <strong><a href="remove" rel="popup">%s</a></strong>.' % _('Delete My Account') |
|
498 |
'</p>' |
|
499 | ||
500 |
options = get_cfg('misc', {}).get('announce_themes') |
|
501 |
if options: |
|
502 |
try: |
|
503 |
subscription = AnnounceSubscription.get_on_index( |
|
504 |
get_request().user.id, str('user_id')) |
|
505 |
except KeyError: |
|
506 |
pass |
|
507 |
else: |
|
508 |
'<p class="command"><a href="announces">%s</a></p>' % _( |
|
509 |
'Edit my Subscription to Announces') |
|
510 | ||
511 |
if user_forms: |
|
512 |
'<h3 id="my-forms">%s</h3>' % _('My Forms') |
|
513 |
root.FormsRootDirectory().user_forms(user_forms) |
|
514 | ||
515 |
def _my_profile [html] (self, user_formdef, user): |
|
516 |
'<h3 id="my-profile">%s</h3>' % _('My Profile') |
|
517 | ||
518 |
TextsDirectory.get_html_text('top-of-profile') |
|
519 | ||
520 |
if user.form_data: |
|
521 |
'<ul>' |
|
522 |
for field in user_formdef.fields: |
|
523 |
if not hasattr(field, str('get_view_value')): |
|
524 |
continue |
|
525 |
value = user.form_data.get(field.id) |
|
526 |
'<li>' |
|
527 |
field.label |
|
528 |
' : ' |
|
529 |
if value: |
|
530 |
field.get_view_value(value) |
|
531 |
'</li>' |
|
532 |
'</ul>' |
|
533 |
else: |
|
534 |
'<p>%s</p>' % _('Empty profile') |
|
535 | ||
536 |
def _index_buttons [html] (self, form_data): |
|
537 |
passwords_cfg = get_cfg('passwords', {}) |
|
538 |
ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0] |
|
539 |
if get_session().lasso_session_dump: |
|
540 |
ident_method = 'idp' |
|
541 | ||
542 |
if form_data and ident_method != 'idp': |
|
543 |
'<p class="command"><a href="profile" rel="popup">%s</a></p>' % _('Edit My Profile') |
|
544 | ||
545 |
if ident_method == 'password' and passwords_cfg.get('can_change', False): |
|
546 |
'<p class="command"><a href="password" rel="popup">%s</a></p>' % _('Change My Password') |
|
547 | ||
548 |
def profile [html] (self): |
|
549 |
user = get_request().user |
|
550 |
if not user or user.anonymous: |
|
551 |
raise errors.AccessUnauthorizedError() |
|
552 | ||
553 |
form = Form(enctype = 'multipart/form-data') |
|
554 |
formdef = user.get_formdef() |
|
555 |
formdef.add_fields_to_form(form, form_data = user.form_data) |
|
556 | ||
557 |
form.add_submit('submit', _('Apply Changes')) |
|
558 |
form.add_submit('cancel', _('Cancel')) |
|
559 | ||
560 |
if form.get_submit() == 'cancel': |
|
561 |
return redirect('.') |
|
562 | ||
563 |
if form.is_submitted() and not form.has_errors(): |
|
564 |
self.profile_submit(form, formdef) |
|
565 |
return redirect('.') |
|
566 | ||
567 |
template.html_top(_('Edit Profile')) |
|
568 |
form.render() |
|
569 | ||
570 |
def profile_submit(self, form, formdef): |
|
571 |
user = get_request().user |
|
572 |
data = formdef.get_data(form) |
|
573 | ||
574 |
user.set_attributes_from_formdata(data) |
|
575 |
user.form_data = data |
|
576 | ||
577 |
user.store() |
|
578 | ||
579 |
def password [html] (self): |
|
580 |
ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0] |
|
581 |
if ident_method != 'password': |
|
582 |
raise errors.TraversalError() |
|
583 | ||
584 |
user = get_request().user |
|
585 |
if not user or user.anonymous: |
|
586 |
raise errors.AccessUnauthorizedError() |
|
587 | ||
588 |
form = Form(enctype = 'multipart/form-data') |
|
589 |
form.add(PasswordWidget, 'new_password', title = _('New Password'), |
|
590 |
required=True) |
|
591 |
form.add(PasswordWidget, 'new2_password', title = _('New Password (confirm)'), |
|
592 |
required=True) |
|
593 | ||
594 |
form.add_submit('submit', _('Change Password')) |
|
595 |
form.add_submit('cancel', _('Cancel')) |
|
596 | ||
597 |
if form.get_submit() == 'cancel': |
|
598 |
return redirect('.') |
|
599 | ||
600 |
if form.is_submitted() and not form.has_errors(): |
|
601 |
qommon.ident.password.check_password(form, 'new_password') |
|
602 |
new_password = form.get_widget('new_password').parse() |
|
603 |
new2_password = form.get_widget('new2_password').parse() |
|
604 |
if new_password != new2_password: |
|
605 |
form.set_error('new2_password', _('Passwords do not match')) |
|
606 | ||
607 |
if form.is_submitted() and not form.has_errors(): |
|
608 |
self.submit_password(new_password) |
|
609 |
return redirect('.') |
|
610 | ||
611 |
template.html_top(_('Change Password')) |
|
612 |
form.render() |
|
613 | ||
614 |
def submit_password(self, new_password): |
|
615 |
passwords_cfg = get_cfg('passwords', {}) |
|
616 |
account = PasswordAccount.get(get_session().username) |
|
617 |
account.hashing_algo = passwords_cfg.get('hashing_algo') |
|
618 |
account.set_password(new_password) |
|
619 |
account.store() |
|
620 | ||
621 |
def new [html] (self): |
|
622 |
if not get_request().user or not get_request().user.anonymous: |
|
623 |
raise errors.AccessUnauthorizedError() |
|
624 | ||
625 |
form = Form(enctype = 'multipart/form-data') |
|
626 |
formdef = get_publisher().user_class.get_formdef() |
|
627 |
if formdef: |
|
628 |
formdef.add_fields_to_form(form) |
|
629 |
else: |
|
630 |
get_logger().error('missing user formdef (in myspace/new)') |
|
631 | ||
632 |
form.add_submit('submit', _('Register')) |
|
633 | ||
634 |
if form.is_submitted() and not form.has_errors(): |
|
635 |
user = get_publisher().user_class() |
|
636 |
data = formdef.get_data(form) |
|
637 |
user.set_attributes_from_formdata(data) |
|
638 |
user.name_identifiers = get_request().user.name_identifiers |
|
639 |
user.lasso_dump = get_request().user.lasso_dump |
|
640 |
user.set_attributes_from_formdata(data) |
|
641 |
user.form_data = data |
|
642 |
user.store() |
|
643 |
get_session().set_user(user.id) |
|
644 |
root_url = get_publisher().get_root_url() |
|
645 |
return redirect('%smyspace' % root_url) |
|
646 | ||
647 |
template.html_top(_('Welcome')) |
|
648 |
form.render() |
|
649 | ||
650 | ||
651 |
def remove [html] (self): |
|
652 |
user = get_request().user |
|
653 |
if not user or user.anonymous: |
|
654 |
raise errors.AccessUnauthorizedError() |
|
655 | ||
656 |
form = Form(enctype = 'multipart/form-data') |
|
657 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
658 |
'Are you really sure you want to remove your account?'))) |
|
659 |
form.add_submit('submit', _('Remove my account')) |
|
660 |
form.add_submit('cancel', _('Cancel')) |
|
661 | ||
662 |
if form.get_submit() == 'cancel': |
|
663 |
return redirect('.') |
|
664 | ||
665 |
if form.is_submitted() and not form.has_errors(): |
|
666 |
user = get_request().user |
|
667 |
account = PasswordAccount.get_on_index(user.id, str('user_id')) |
|
668 |
get_session_manager().expire_session() |
|
669 |
account.remove_self() |
|
670 |
return redirect(get_publisher().get_root_url()) |
|
671 | ||
672 |
template.html_top(_('Removing Account')) |
|
673 |
form.render() |
|
674 | ||
675 |
def announces [html] (self): |
|
676 |
options = get_cfg('misc', {}).get('announce_themes') |
|
677 |
if not options: |
|
678 |
raise errors.TraversalError() |
|
679 |
subscription = AnnounceSubscription.get_on_index(get_request().user.id, str('user_id')) |
|
680 |
if not subscription: |
|
681 |
raise errors.TraversalError() |
|
682 | ||
683 |
if subscription.enabled_themes is None: |
|
684 |
enabled_themes = options |
|
685 |
else: |
|
686 |
enabled_themes = subscription.enabled_themes |
|
687 | ||
688 |
form = Form(enctype = 'multipart/form-data') |
|
689 |
form.add(CheckboxesWidget, 'themes', title=_('Announce Themes'), |
|
690 |
value=enabled_themes, elements=options, |
|
691 |
inline=False, required=False) |
|
692 | ||
693 |
form.add_submit('submit', _('Apply Changes')) |
|
694 |
form.add_submit('cancel', _('Cancel')) |
|
695 | ||
696 |
if form.get_submit() == 'cancel': |
|
697 |
return redirect('.') |
|
698 | ||
699 |
if form.is_submitted() and not form.has_errors(): |
|
700 |
chosen_themes = form.get_widget('themes').parse() |
|
701 |
if chosen_themes == options: |
|
702 |
chosen_themes = None |
|
703 |
subscription.enabled_themes = chosen_themes |
|
704 |
subscription.store() |
|
705 |
return redirect('.') |
|
706 | ||
707 |
template.html_top() |
|
708 |
get_response().breadcrumb.append(('announces', _('Announce Subscription'))) |
|
709 |
form.render() |
|
710 | ||
711 | ||
712 |
TextsDirectory.register('aq-myspace-invoice', |
|
713 |
N_('Message on top of invoices page'), |
|
714 |
category = N_('Invoices')) |
|
715 |
extra/modules/myspace.py | ||
---|---|---|
1 |
try: |
|
2 |
import lasso |
|
3 |
except ImportError: |
|
4 |
pass |
|
5 | ||
6 |
import json |
|
7 | ||
8 |
from quixote import get_publisher, get_request, redirect, get_response, get_session_manager, get_session |
|
9 |
from quixote.directory import AccessControlled, Directory |
|
10 |
from quixote.html import TemplateIO, htmltext |
|
11 |
from quixote.util import StaticFile, FileStream |
|
12 | ||
13 |
from qommon import template |
|
14 |
from qommon.form import * |
|
15 |
from qommon import get_cfg, get_logger |
|
16 |
from qommon import errors |
|
17 |
from wcs.api import get_user_from_api_query_string |
|
18 | ||
19 |
import qommon.ident.password |
|
20 |
from qommon.ident.password_accounts import PasswordAccount |
|
21 | ||
22 |
from qommon.admin.texts import TextsDirectory |
|
23 | ||
24 |
from wcs.formdef import FormDef |
|
25 |
import root |
|
26 | ||
27 |
from announces import AnnounceSubscription |
|
28 |
from strongbox import StrongboxItem, StrongboxType |
|
29 |
from payments import Invoice, Regie, is_payment_supported |
|
30 |
import msp_ui |
|
31 | ||
32 |
class MyInvoicesDirectory(Directory): |
|
33 |
_q_exports = [''] |
|
34 | ||
35 |
def _q_traverse(self, path): |
|
36 |
if not is_payment_supported(): |
|
37 |
raise errors.TraversalError() |
|
38 |
get_response().breadcrumb.append(('invoices/', _('Invoices'))) |
|
39 |
return Directory._q_traverse(self, path) |
|
40 | ||
41 |
def _q_index(self): |
|
42 |
user = get_request().user |
|
43 |
if not user or user.anonymous: |
|
44 |
raise errors.AccessUnauthorizedError() |
|
45 | ||
46 |
template.html_top(_('Invoices')) |
|
47 |
r = TemplateIO(html=True) |
|
48 |
r += TextsDirectory.get_html_text('aq-myspace-invoice') |
|
49 | ||
50 |
r += get_session().display_message() |
|
51 | ||
52 |
invoices = [] |
|
53 |
invoices.extend(Invoice.get_with_indexed_value( |
|
54 |
str('user_id'), str(user.id))) |
|
55 |
try: |
|
56 |
invoices.extend(Invoice.get_with_indexed_value( |
|
57 |
str('user_hash'), str(user.hash))) |
|
58 |
except AttributeError: |
|
59 |
pass |
|
60 | ||
61 |
def cmp_invoice(a, b): |
|
62 |
t = cmp(a.regie_id, b.regie_id) |
|
63 |
if t != 0: |
|
64 |
return t |
|
65 |
return -cmp(a.date, b.date) |
|
66 | ||
67 |
invoices.sort(cmp_invoice) |
|
68 | ||
69 |
last_regie_id = None |
|
70 |
unpaid = False |
|
71 |
for invoice in invoices: |
|
72 |
if invoice.regie_id != last_regie_id: |
|
73 |
if last_regie_id: |
|
74 |
r += htmltext('</ul>') |
|
75 |
if unpaid: |
|
76 |
r += htmltext('<input type="submit" value="%s"/>') % _('Pay Selected Invoices') |
|
77 |
r += htmltext('</form>') |
|
78 |
last_regie_id = invoice.regie_id |
|
79 |
r += htmltext('<h3>%s</h3>') % Regie.get(last_regie_id).label |
|
80 |
unpaid = False |
|
81 |
r += htmltext('<form action="%s/invoices/multiple">' % get_publisher().get_frontoffice_url()) |
|
82 |
r += htmltext('<ul>') |
|
83 | ||
84 |
r += htmltext('<li>') |
|
85 |
if not (invoice.paid or invoice.canceled): |
|
86 |
r += htmltext('<input type="checkbox" name="invoice" value="%s"/>' % invoice.id) |
|
87 |
unpaid = True |
|
88 |
r += misc.localstrftime(invoice.date) |
|
89 |
r += ' - ' |
|
90 |
r += '%s' % invoice.subject |
|
91 |
r += ' - ' |
|
92 |
r += '%s' % invoice.amount |
|
93 |
r += ' €' |
|
94 |
r += ' - ' |
|
95 |
button = '<span class="paybutton">%s</span>' % _('Pay') |
|
96 |
if invoice.canceled: |
|
97 |
r += _('canceled on %s') % misc.localstrftime(invoice.canceled_date) |
|
98 |
r += ' - ' |
|
99 |
button = _('Details') |
|
100 |
if invoice.paid: |
|
101 |
r += _('paid on %s') % misc.localstrftime(invoice.paid_date) |
|
102 |
r += ' - ' |
|
103 |
button = _('Details') |
|
104 |
r += htmltext('<a href="%s/invoices/%s">%s</a>' % (get_publisher().get_frontoffice_url(), |
|
105 |
invoice.id, button)) |
|
106 |
r += htmltext('</li>') |
|
107 | ||
108 |
if last_regie_id: |
|
109 |
r += htmltext('</ul>') |
|
110 |
if unpaid: |
|
111 |
r += htmltext('<input type="submit" value="%s"/>') % _('Pay Selected Invoices') |
|
112 |
r += htmltext('</form>') |
|
113 | ||
114 |
return r.getvalue() |
|
115 | ||
116 | ||
117 |
class StrongboxDirectory(Directory): |
|
118 |
_q_exports = ['', 'add', 'download', 'remove', 'pick', 'validate'] |
|
119 | ||
120 |
def _q_traverse(self, path): |
|
121 |
if not get_cfg('misc', {}).get('aq-strongbox'): |
|
122 |
raise errors.TraversalError() |
|
123 |
get_response().breadcrumb.append(('strongbox/', _('Strongbox'))) |
|
124 |
return Directory._q_traverse(self, path) |
|
125 | ||
126 |
def get_form(self): |
|
127 |
types = [(x.id, x.label) for x in StrongboxType.select()] |
|
128 |
form = Form(action='add', enctype='multipart/form-data') |
|
129 |
form.add(StringWidget, 'description', title=_('Description'), size=60) |
|
130 |
form.add(FileWidget, 'file', title=_('File'), required=True) |
|
131 |
form.add(SingleSelectWidget, 'type_id', title=_('Document Type'), |
|
132 |
options = [(None, _('Not specified'))] + types) |
|
133 |
form.add(DateWidget, 'date_time', title = _('Document Date')) |
|
134 |
form.add_submit('submit', _('Upload')) |
|
135 |
return form |
|
136 | ||
137 |
def _q_index(self): |
|
138 |
template.html_top(_('Strongbox')) |
|
139 |
r = TemplateIO(html=True) |
|
140 | ||
141 |
# TODO: a paragraph of explanations here could be useful |
|
142 | ||
143 |
sffiles = StrongboxItem.get_with_indexed_value( |
|
144 |
str('user_id'), str(get_request().user.id)) |
|
145 |
if sffiles: |
|
146 |
r += htmltext('<table id="strongbox-items">') |
|
147 |
r += htmltext('<tr><th></th><th>%s</th><th>%s</th><th></th></tr>') % ( |
|
148 |
_('Type'), _('Expiration')) |
|
149 |
else: |
|
150 |
r += htmltext('<p>') |
|
151 |
r += _('There is currently nothing in your strongbox.') |
|
152 |
r += htmltext('</p>') |
|
153 |
has_items_to_validate = False |
|
154 |
for i, sffile in enumerate(sffiles): |
|
155 |
expired = False |
|
156 |
if not sffile.validated_time: |
|
157 |
has_items_to_validate = True |
|
158 |
continue |
|
159 |
if sffile.expiration_time and sffile.expiration_time < time.localtime(): |
|
160 |
expired = True |
|
161 |
if i%2: |
|
162 |
classnames = ['odd'] |
|
163 |
else: |
|
164 |
classnames = ['even'] |
|
165 |
if expired: |
|
166 |
classnames.append('expired') |
|
167 |
r += htmltext('<tr class="%s">') % ' '.join(classnames) |
|
168 |
r += htmltext('<td class="label">') |
|
169 |
r += sffile.get_display_name() |
|
170 |
r += htmltext('</td>') |
|
171 |
if sffile.type_id: |
|
172 |
r += htmltext('<td class="type">%s</td>') % StrongboxType.get(sffile.type_id).label |
|
173 |
else: |
|
174 |
r += htmltext('<td class="type">-</td>') |
|
175 |
if sffile.expiration_time: |
|
176 |
r += htmltext('<td class="expiration">%s') % strftime(misc.date_format(), sffile.expiration_time) |
|
177 |
if expired: |
|
178 |
r += ' (%s)' % _('expired') |
|
179 |
r += htmltext('</td>') |
|
180 |
else: |
|
181 |
r += htmltext('<td class="expiration">-</td>') |
|
182 |
r += htmltext('<td class="actions">') |
|
183 |
r += htmltext(' [<a href="download?id=%s">%s</a>] ') % (sffile.id, _('download')) |
|
184 |
r += htmltext('[<a rel="popup" href="remove?id=%s">%s</a>] ') % (sffile.id, _('remove')) |
|
185 |
r += htmltext('</td>') |
|
186 |
r += htmltext('</tr>') |
|
187 | ||
188 |
if has_items_to_validate: |
|
189 |
r += htmltext('<tr><td colspan="4"><h3>%s</h3></td></tr>') % _('Proposed Items') |
|
190 |
for sffile in sffiles: |
|
191 |
if sffile.validated_time: |
|
192 |
continue |
|
193 |
if sffile.expiration_time and sffile.expiration_time < time.localtime(): |
|
194 |
expired = True |
|
195 |
if i%2: |
|
196 |
classnames = ['odd'] |
|
197 |
else: |
|
198 |
classnames = ['even'] |
|
199 |
if expired: |
|
200 |
classnames.append('expired') |
|
201 |
r += htmltext('<tr class="%s">') % ' '.join(classnames) |
|
202 | ||
203 |
r += htmltext('<td class="label">') |
|
204 |
r += sffile.get_display_name() |
|
205 |
r += htmltext('</td>') |
|
206 |
if sffile.type_id: |
|
207 |
r += htmltext('<td class="type">%s</td>') % StrongboxType.get(sffile.type_id).label |
|
208 |
else: |
|
209 |
r += htmltext('<td class="type">-</td>') |
|
210 | ||
211 |
if sffile.expiration_time: |
|
212 |
r += htmltext('<td class="expiration">%s') % strftime(misc.date_format(), sffile.expiration_time) |
|
213 |
if expired: |
|
214 |
r += ' (%s)' % _('expired') |
|
215 |
r += htmltext('</td>') |
|
216 |
else: |
|
217 |
r += htmltext('<td class="expiration">-</td>') |
|
218 |
r += htmltext('<td class="actions">') |
|
219 |
r += htmltext(' [<a href="download?id=%s">%s</a>] ') % (sffile.id, _('download')) |
|
220 |
r += htmltext(' [<a href="validate?id=%s">%s</a>] ') % (sffile.id, _('validate')) |
|
221 |
r += htmltext(' [<a href="remove?id=%s">%s</a>] ') % (sffile.id, _('reject')) |
|
222 |
r += htmltext('</td>') |
|
223 |
r += htmltext('</tr>') |
|
224 |
if sffiles: |
|
225 |
r += htmltext('</table>') |
|
226 | ||
227 |
r += htmltext('<h3>%s</h3>') % _('Add a file to the strongbox') |
|
228 |
form = self.get_form() |
|
229 |
r += form.render() |
|
230 |
return r.getvalue() |
|
231 | ||
232 |
def add(self): |
|
233 |
form = self.get_form() |
|
234 |
if not form.is_submitted(): |
|
235 |
if get_request().form.get('mode') == 'pick': |
|
236 |
return redirect('pick') |
|
237 |
else: |
|
238 |
return redirect('.') |
|
239 | ||
240 |
sffile = StrongboxItem() |
|
241 |
sffile.user_id = get_request().user.id |
|
242 |
sffile.description = form.get_widget('description').parse() |
|
243 |
sffile.validated_time = time.localtime() |
|
244 |
sffile.type_id = form.get_widget('type_id').parse() |
|
245 |
v = form.get_widget('date_time').parse() |
|
246 |
sffile.set_expiration_time_from_date(v) |
|
247 |
sffile.store() |
|
248 |
sffile.set_file(form.get_widget('file').parse()) |
|
249 |
sffile.store() |
|
250 |
if get_request().form.get('mode') == 'pick': |
|
251 |
return redirect('pick') |
|
252 |
else: |
|
253 |
return redirect('.') |
|
254 | ||
255 |
def download(self): |
|
256 |
id = get_request().form.get('id') |
|
257 |
if not id: |
|
258 |
raise errors.TraversalError() |
|
259 |
try: |
|
260 |
sffile = StrongboxItem.get(id) |
|
261 |
except KeyError: |
|
262 |
raise errors.TraversalError() |
|
263 |
if str(sffile.user_id) != str(get_request().user.id): |
|
264 |
raise errors.TraversalError() |
|
265 | ||
266 |
filename = sffile.file.filename |
|
267 |
fd = file(filename) |
|
268 |
size = os.path.getsize(filename) |
|
269 |
response = get_response() |
|
270 |
response.set_content_type('application/octet-stream') |
|
271 |
response.set_header('content-disposition', 'attachment; filename="%s"' % sffile.file.base_filename) |
|
272 |
return FileStream(fd, size) |
|
273 | ||
274 |
def validate(self): |
|
275 |
id = get_request().form.get('id') |
|
276 |
if not id: |
|
277 |
raise errors.TraversalError() |
|
278 |
try: |
|
279 |
sffile = StrongboxItem.get(id) |
|
280 |
except KeyError: |
|
281 |
raise errors.TraversalError() |
|
282 |
if str(sffile.user_id) != str(get_request().user.id): |
|
283 |
raise errors.TraversalError() |
|
284 |
sffile.validated_time = time.time() |
|
285 |
sffile.store() |
|
286 |
return redirect('.') |
|
287 | ||
288 |
def remove(self): |
|
289 |
id = get_request().form.get('id') |
|
290 |
if not id: |
|
291 |
raise errors.TraversalError() |
|
292 |
try: |
|
293 |
sffile = StrongboxItem.get(id) |
|
294 |
except KeyError: |
|
295 |
raise errors.TraversalError() |
|
296 |
if str(sffile.user_id) != str(get_request().user.id): |
|
297 |
raise errors.TraversalError() |
|
298 | ||
299 |
r = TemplateIO(html=True) |
|
300 |
form = Form(enctype='multipart/form-data') |
|
301 |
form.add_hidden('id', get_request().form.get('id')) |
|
302 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
303 |
'You are about to irrevocably delete this item from your strongbox.'))) |
|
304 |
form.add_submit('submit', _('Submit')) |
|
305 |
form.add_submit('cancel', _('Cancel')) |
|
306 |
if form.get_submit() == 'cancel': |
|
307 |
return redirect('.') |
|
308 |
if not form.is_submitted() or form.has_errors(): |
|
309 |
if sffile.type_id: |
|
310 |
r += htmltext('<h2>%s</h2>') % _('Deleting %(filetype)s: %(filename)s') % { |
|
311 |
'filetype': StrongboxType.get(sffile.type_id).label, |
|
312 |
'filename': sffile.get_display_name() |
|
313 |
} |
|
314 |
else: |
|
315 |
r += htmltext('<h2>%s</h2>') % _('Deleting %(filename)s') % {'filename': sffile.get_display_name()} |
|
316 |
r += form.render() |
|
317 |
return r.getvalue() |
|
318 |
else: |
|
319 |
sffile.remove_self() |
|
320 |
sffile.remove_file() |
|
321 |
return redirect('.') |
|
322 | ||
323 |
def picked_file(self): |
|
324 |
get_response().set_content_type('application/json') |
|
325 |
sffile = StrongboxItem.get(get_request().form.get('val')) |
|
326 |
sffile.file.fp = file(sffile.file.filename) |
|
327 |
if sffile.user_id != get_request().user.id: |
|
328 |
raise errors.TraversalError() |
|
329 |
# XXX: this will copy the file, it would be quite nice if it was |
|
330 |
# possible to just make it a symlink to the sffile |
|
331 |
token = get_session().add_tempfile(sffile.file) |
|
332 |
return json.dumps({'token': token, 'filename': sffile.file.base_filename}) |
|
333 | ||
334 |
def pick(self): |
|
335 |
if get_request().form.get('select') == 'true': |
|
336 |
return self.picked_file() |
|
337 |
r = TemplateIO(html=True) |
|
338 |
root_url = get_publisher().get_root_url() |
|
339 |
sffiles = StrongboxItem.get_with_indexed_value( |
|
340 |
str('user_id'), str(get_request().user.id)) |
|
341 |
r += htmltext('<h2>%s</h2>') % _('Pick a file') |
|
342 | ||
343 |
if not sffiles: |
|
344 |
r += htmltext('<p>') |
|
345 |
r += _('You do not have any file in your strongbox at the moment.') |
|
346 |
r += htmltext('</p>') |
|
347 |
r += htmltext('<div class="buttons">') |
|
348 |
r += htmltext('<a href="%smyspace/strongbox/" target="_blank">%s</a>') % (root_url, |
|
349 |
_('Open Strongbox Management')) |
|
350 |
r += htmltext('</div>') |
|
351 |
else: |
|
352 |
r += htmltext('<form id="strongbox-pick">') |
|
353 |
r += htmltext('<ul>') |
|
354 |
for sffile in sffiles: |
|
355 |
r += htmltext('<li><label><input type="radio" name="file" value="%s"/>%s</label>') % ( |
|
356 |
sffile.id, sffile.get_display_name()) |
|
357 |
r += htmltext(' [<a href="%smyspace/strongbox/download?id=%s">%s</a>] ') % ( |
|
358 |
root_url, sffile.id, _('view')) |
|
359 |
r += htmltext('</li>') |
|
360 |
r += htmltext('</ul>') |
|
361 | ||
362 |
r += htmltext('<div class="buttons">') |
|
363 |
r += htmltext('<input name="cancel" type="button" value="%s"/>') % _('Cancel') |
|
364 |
r += ' ' |
|
365 |
r += htmltext('<input name="pick" type="button" value="%s"/>') % _('Pick') |
|
366 |
r += htmltext('</div>') |
|
367 |
r += htmltext('</form>') |
|
368 |
return r.getvalue() |
|
369 | ||
370 | ||
371 |
class JsonDirectory(Directory): |
|
372 |
'''Export of several lists in json, related to the current user or the |
|
373 |
SAMLv2 NameID we'd get in the URL''' |
|
374 | ||
375 |
_q_exports = ['forms'] |
|
376 | ||
377 |
user = None |
|
378 | ||
379 |
def _q_traverse(self, path): |
|
380 |
self.user = get_user_from_api_query_string() or get_request().user |
|
381 |
if not self.user: |
|
382 |
raise errors.AccessUnauthorizedError() |
|
383 |
return Directory._q_traverse(self, path) |
|
384 | ||
385 |
def forms(self): |
|
386 |
formdefs = FormDef.select(lambda x: not x.is_disabled(), order_by = 'name') |
|
387 |
user_forms = [] |
|
388 |
for formdef in formdefs: |
|
389 |
user_forms.extend(formdef.data_class().get_with_indexed_value( |
|
390 |
'user_id', self.user.id)) |
|
391 |
try: |
|
392 |
user_forms.extend(formdef.data_class().get_with_indexed_value( |
|
393 |
'user_hash', self.user.hash)) |
|
394 |
except AttributeError: |
|
395 |
pass |
|
396 |
user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) |
|
397 | ||
398 |
get_response().set_content_type('application/json') |
|
399 | ||
400 | ||
401 |
forms_output = [] |
|
402 |
for form in user_forms: |
|
403 |
visible_status = form.get_visible_status(user=self.user) |
|
404 |
# skip hidden forms |
|
405 |
if not visible_status: |
|
406 |
continue |
|
407 |
name = form.formdef.name |
|
408 |
id = form.get_display_id() |
|
409 |
status = visible_status.name |
|
410 |
title = _('%(name)s #%(id)s (%(status)s)') % { |
|
411 |
'name': name, |
|
412 |
'id': id, |
|
413 |
'status': status |
|
414 |
} |
|
415 |
url = form.get_url() |
|
416 |
d = { 'title': title, 'url': url } |
|
417 |
d.update(form.get_substitution_variables(minimal=True)) |
|
418 |
forms_output.append(d) |
|
419 |
return json.dumps(forms_output) |
|
420 | ||
421 | ||
422 |
class MyspaceDirectory(Directory): |
|
423 |
_q_exports = ['', 'profile', 'new', 'password', 'remove', 'announces', |
|
424 |
'strongbox', 'invoices', 'json', 'msp'] |
|
425 | ||
426 |
msp = msp_ui.MSPDirectory() |
|
427 |
strongbox = StrongboxDirectory() |
|
428 |
invoices = MyInvoicesDirectory() |
|
429 |
json = JsonDirectory() |
|
430 | ||
431 |
def _q_traverse(self, path): |
|
432 |
if (path[0] not in ('new', 'json')) and (not get_request().user or get_request().user.anonymous): |
|
433 |
raise errors.AccessUnauthorizedError() |
|
434 |
get_response().filter['bigdiv'] = 'profile' |
|
435 |
get_response().breadcrumb.append(('myspace/', _('My Space'))) |
|
436 | ||
437 |
# Migrate custom text settings |
|
438 |
texts_cfg = get_cfg('texts', {}) |
|
439 |
if 'text-aq-top-of-profile' in texts_cfg and ( |
|
440 |
not 'text-top-of-profile' in texts_cfg): |
|
441 |
texts_cfg['text-top-of-profile'] = texts_cfg['text-aq-top-of-profile'] |
|
442 |
del texts_cfg['text-aq-top-of-profile'] |
|
443 |
get_publisher().write_cfg() |
|
444 | ||
445 |
return Directory._q_traverse(self, path) |
|
446 | ||
447 | ||
448 |
def _q_index(self): |
|
449 |
user = get_request().user |
|
450 |
if not user: |
|
451 |
raise errors.AccessUnauthorizedError() |
|
452 |
template.html_top(_('My Space')) |
|
453 |
r = TemplateIO(html=True) |
|
454 |
if user.anonymous: |
|
455 |
return redirect('new') |
|
456 | ||
457 |
user_formdef = user.get_formdef() |
|
458 | ||
459 |
user_forms = [] |
|
460 |
if user: |
|
461 |
formdefs = FormDef.select(lambda x: not x.is_disabled(), order_by = 'name') |
|
462 |
user_forms = [] |
|
463 |
for formdef in formdefs: |
|
464 |
user_forms.extend(formdef.data_class().get_with_indexed_value( |
|
465 |
'user_id', user.id)) |
|
466 |
try: |
|
467 |
user_forms.extend(formdef.data_class().get_with_indexed_value( |
|
468 |
'user_hash', user.hash)) |
|
469 |
except AttributeError: |
|
470 |
pass |
|
471 |
user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) |
|
472 | ||
473 |
profile_links = [] |
|
474 |
if not get_cfg('sp', {}).get('idp-manage-user-attributes', False): |
|
475 |
if user_formdef: |
|
476 |
profile_links.append('<a href="#my-profile">%s</a>' % _('My Profile')) |
|
477 |
if user_forms: |
|
478 |
profile_links.append('<a href="#my-forms">%s</a>' % _('My Forms')) |
|
479 |
if get_cfg('misc', {}).get('aq-strongbox'): |
|
480 |
profile_links.append('<a href="strongbox/">%s</a>' % _('My Strongbox')) |
|
481 |
if is_payment_supported(): |
|
482 |
profile_links.append('<a href="invoices/">%s</a>' % _('My Invoices')) |
|
483 | ||
484 |
root_url = get_publisher().get_root_url() |
|
485 |
if user.can_go_in_backoffice(): |
|
486 |
profile_links.append('<a href="%sbackoffice/">%s</a>' % (root_url, _('Back office'))) |
|
487 |
if user.is_admin: |
|
488 |
profile_links.append('<a href="%sadmin/">%s</a>' % (root_url, _('Admin'))) |
|
489 | ||
490 |
if profile_links: |
|
491 |
r += htmltext('<p id="profile-links">') |
|
492 |
r += ' - '.join(profile_links) |
|
493 |
r += htmltext('</p>') |
|
494 | ||
495 |
if not get_cfg('sp', {}).get('idp-manage-user-attributes', False): |
|
496 |
if user_formdef: |
|
497 |
r += self._my_profile(user_formdef, user) |
|
498 | ||
499 |
r += self._index_buttons(user_formdef) |
|
500 | ||
501 |
try: |
|
502 |
x = PasswordAccount.get_on_index(get_request().user.id, str('user_id')) |
|
503 |
except KeyError: |
|
504 |
pass |
|
505 |
else: |
|
506 |
r += htmltext('<p>') |
|
507 |
r += _('You can delete your account freely from the services portal. ' |
|
508 |
'This action is irreversible; it will destruct your personal ' |
|
509 |
'datas and destruct the access to your request history.') |
|
510 |
r += htmltext(' <strong><a href="remove" rel="popup">%s</a></strong>.') % _('Delete My Account') |
|
511 |
r += htmltext('</p>') |
|
512 | ||
513 |
options = get_cfg('misc', {}).get('announce_themes') |
|
514 |
if options: |
|
515 |
try: |
|
516 |
subscription = AnnounceSubscription.get_on_index( |
|
517 |
get_request().user.id, str('user_id')) |
|
518 |
except KeyError: |
|
519 |
pass |
|
520 |
else: |
|
521 |
r += htmltext('<p class="command"><a href="announces">%s</a></p>') % _( |
|
522 |
'Edit my Subscription to Announces') |
|
523 | ||
524 |
if user_forms: |
|
525 |
r += htmltext('<h3 id="my-forms">%s</h3>') % _('My Forms') |
|
526 |
r += root.FormsRootDirectory().user_forms(user_forms) |
|
527 | ||
528 |
return r.getvalue() |
|
529 | ||
530 |
def _my_profile(self, user_formdef, user): |
|
531 |
r = TemplateIO(html=True) |
|
532 |
r += htmltext('<h3 id="my-profile">%s</h3>') % _('My Profile') |
|
533 | ||
534 |
r += TextsDirectory.get_html_text('top-of-profile') |
|
535 | ||
536 |
if user.form_data: |
|
537 |
r += htmltext('<ul>') |
|
538 |
for field in user_formdef.fields: |
|
539 |
if not hasattr(field, str('get_view_value')): |
|
540 |
continue |
|
541 |
value = user.form_data.get(field.id) |
|
542 |
r += htmltext('<li>') |
|
543 |
r += field.label |
|
544 |
r += ' : ' |
|
545 |
if value: |
|
546 |
r += field.get_view_value(value) |
|
547 |
r += htmltext('</li>') |
|
548 |
r += htmltext('</ul>') |
|
549 |
else: |
|
550 |
r += htmltext('<p>%s</p>') % _('Empty profile') |
|
551 |
return r.getvalue() |
|
552 | ||
553 |
def _index_buttons(self, form_data): |
|
554 |
r = TemplateIO(html=True) |
|
555 |
passwords_cfg = get_cfg('passwords', {}) |
|
556 |
ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0] |
|
557 |
if get_session().lasso_session_dump: |
|
558 |
ident_method = 'idp' |
|
559 | ||
560 |
if form_data and ident_method != 'idp': |
|
561 |
r += htmltext('<p class="command"><a href="profile" rel="popup">%s</a></p>') % _('Edit My Profile') |
|
562 | ||
563 |
if ident_method == 'password' and passwords_cfg.get('can_change', False): |
|
564 |
r += htmltext('<p class="command"><a href="password" rel="popup">%s</a></p>') % _('Change My Password') |
|
565 | ||
566 |
return r.getvalue() |
|
567 | ||
568 |
def profile(self): |
|
569 |
user = get_request().user |
|
570 |
if not user or user.anonymous: |
|
571 |
raise errors.AccessUnauthorizedError() |
|
572 | ||
573 |
form = Form(enctype = 'multipart/form-data') |
|
574 |
formdef = user.get_formdef() |
|
575 |
formdef.add_fields_to_form(form, form_data = user.form_data) |
|
576 | ||
577 |
form.add_submit('submit', _('Apply Changes')) |
|
578 |
form.add_submit('cancel', _('Cancel')) |
|
579 | ||
580 |
if form.get_submit() == 'cancel': |
|
581 |
return redirect('.') |
|
582 | ||
583 |
if form.is_submitted() and not form.has_errors(): |
|
584 |
self.profile_submit(form, formdef) |
|
585 |
return redirect('.') |
|
586 | ||
587 |
template.html_top(_('Edit Profile')) |
|
588 |
return form.render() |
|
589 | ||
590 |
def profile_submit(self, form, formdef): |
|
591 |
user = get_request().user |
|
592 |
data = formdef.get_data(form) |
|
593 | ||
594 |
user.set_attributes_from_formdata(data) |
|
595 |
user.form_data = data |
|
596 | ||
597 |
user.store() |
|
598 | ||
599 |
def password(self): |
|
600 |
ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0] |
|
601 |
if ident_method != 'password': |
|
602 |
raise errors.TraversalError() |
|
603 | ||
604 |
user = get_request().user |
|
605 |
if not user or user.anonymous: |
|
606 |
raise errors.AccessUnauthorizedError() |
|
607 | ||
608 |
form = Form(enctype = 'multipart/form-data') |
|
609 |
form.add(PasswordWidget, 'new_password', title = _('New Password'), |
|
610 |
required=True) |
|
611 |
form.add(PasswordWidget, 'new2_password', title = _('New Password (confirm)'), |
|
612 |
required=True) |
|
613 | ||
614 |
form.add_submit('submit', _('Change Password')) |
|
615 |
form.add_submit('cancel', _('Cancel')) |
|
616 | ||
617 |
if form.get_submit() == 'cancel': |
|
618 |
return redirect('.') |
|
619 | ||
620 |
if form.is_submitted() and not form.has_errors(): |
|
621 |
qommon.ident.password.check_password(form, 'new_password') |
|
622 |
new_password = form.get_widget('new_password').parse() |
|
623 |
new2_password = form.get_widget('new2_password').parse() |
|
624 |
if new_password != new2_password: |
|
625 |
form.set_error('new2_password', _('Passwords do not match')) |
|
626 | ||
627 |
if form.is_submitted() and not form.has_errors(): |
|
628 |
self.submit_password(new_password) |
|
629 |
return redirect('.') |
|
630 | ||
631 |
template.html_top(_('Change Password')) |
|
632 |
return form.render() |
|
633 | ||
634 |
def submit_password(self, new_password): |
|
635 |
passwords_cfg = get_cfg('passwords', {}) |
|
636 |
account = PasswordAccount.get(get_session().username) |
|
637 |
account.hashing_algo = passwords_cfg.get('hashing_algo') |
|
638 |
account.set_password(new_password) |
|
639 |
account.store() |
|
640 | ||
641 |
def new(self): |
|
642 |
if not get_request().user or not get_request().user.anonymous: |
|
643 |
raise errors.AccessUnauthorizedError() |
|
644 | ||
645 |
form = Form(enctype = 'multipart/form-data') |
|
646 |
formdef = get_publisher().user_class.get_formdef() |
|
647 |
if formdef: |
|
648 |
formdef.add_fields_to_form(form) |
|
649 |
else: |
|
650 |
get_logger().error('missing user formdef (in myspace/new)') |
|
651 | ||
652 |
form.add_submit('submit', _('Register')) |
|
653 | ||
654 |
if form.is_submitted() and not form.has_errors(): |
|
655 |
user = get_publisher().user_class() |
|
656 |
data = formdef.get_data(form) |
|
657 |
user.set_attributes_from_formdata(data) |
|
658 |
user.name_identifiers = get_request().user.name_identifiers |
|
659 |
user.lasso_dump = get_request().user.lasso_dump |
|
660 |
user.set_attributes_from_formdata(data) |
|
661 |
user.form_data = data |
|
662 |
user.store() |
|
663 |
get_session().set_user(user.id) |
|
664 |
root_url = get_publisher().get_root_url() |
|
665 |
return redirect('%smyspace' % root_url) |
|
666 | ||
667 |
template.html_top(_('Welcome')) |
|
668 |
return form.render() |
|
669 | ||
670 | ||
671 |
def remove(self): |
|
672 |
user = get_request().user |
|
673 |
if not user or user.anonymous: |
|
674 |
raise errors.AccessUnauthorizedError() |
|
675 | ||
676 |
form = Form(enctype = 'multipart/form-data') |
|
677 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
678 |
'Are you really sure you want to remove your account?'))) |
|
679 |
form.add_submit('submit', _('Remove my account')) |
|
680 |
form.add_submit('cancel', _('Cancel')) |
|
681 | ||
682 |
if form.get_submit() == 'cancel': |
|
683 |
return redirect('.') |
|
684 | ||
685 |
if form.is_submitted() and not form.has_errors(): |
|
686 |
user = get_request().user |
|
687 |
account = PasswordAccount.get_on_index(user.id, str('user_id')) |
|
688 |
get_session_manager().expire_session() |
|
689 |
account.remove_self() |
|
690 |
return redirect(get_publisher().get_root_url()) |
|
691 | ||
692 |
template.html_top(_('Removing Account')) |
|
693 |
return form.render() |
|
694 | ||
695 |
def announces(self): |
|
696 |
options = get_cfg('misc', {}).get('announce_themes') |
|
697 |
if not options: |
|
698 |
raise errors.TraversalError() |
|
699 |
subscription = AnnounceSubscription.get_on_index(get_request().user.id, str('user_id')) |
|
700 |
if not subscription: |
|
701 |
raise errors.TraversalError() |
|
702 | ||
703 |
if subscription.enabled_themes is None: |
|
704 |
enabled_themes = options |
|
705 |
else: |
|
706 |
enabled_themes = subscription.enabled_themes |
|
707 | ||
708 |
form = Form(enctype = 'multipart/form-data') |
|
709 |
form.add(CheckboxesWidget, 'themes', title=_('Announce Themes'), |
|
710 |
value=enabled_themes, elements=options, |
|
711 |
inline=False, required=False) |
|
712 | ||
713 |
form.add_submit('submit', _('Apply Changes')) |
|
714 |
form.add_submit('cancel', _('Cancel')) |
|
715 | ||
716 |
if form.get_submit() == 'cancel': |
|
717 |
return redirect('.') |
|
718 | ||
719 |
if form.is_submitted() and not form.has_errors(): |
|
720 |
chosen_themes = form.get_widget('themes').parse() |
|
721 |
if chosen_themes == options: |
|
722 |
chosen_themes = None |
|
723 |
subscription.enabled_themes = chosen_themes |
|
724 |
subscription.store() |
|
725 |
return redirect('.') |
|
726 | ||
727 |
template.html_top() |
|
728 |
get_response().breadcrumb.append(('announces', _('Announce Subscription'))) |
|
729 |
return form.render() |
|
730 | ||
731 | ||
732 |
TextsDirectory.register('aq-myspace-invoice', |
|
733 |
N_('Message on top of invoices page'), |
|
734 |
category = N_('Invoices')) |
|
735 |
extra/modules/payments_ui.ptl | ||
---|---|---|
1 |
import time |
|
2 |
import pprint |
|
3 |
import locale |
|
4 |
import decimal |
|
5 |
import datetime |
|
6 | ||
7 |
from quixote import get_request, get_response, get_session, redirect |
|
8 |
from quixote.directory import Directory, AccessControlled |
|
9 | ||
10 |
import wcs |
|
11 |
import wcs.admin.root |
|
12 |
from wcs.backoffice.menu import * |
|
13 |
from wcs.formdef import FormDef |
|
14 | ||
15 |
from qommon import errors, misc, template, get_logger |
|
16 |
from qommon.form import * |
|
17 |
from qommon.strftime import strftime |
|
18 |
from qommon.admin.emails import EmailsDirectory |
|
19 | ||
20 |
from payments import (eopayment, Regie, is_payment_supported, Invoice, |
|
21 |
Transaction, notify_paid_invoice) |
|
22 | ||
23 |
from qommon.admin.texts import TextsDirectory |
|
24 | ||
25 |
if not set: |
|
26 |
from sets import Set as set |
|
27 | ||
28 |
def invoice_as_html [html] (invoice): |
|
29 |
'<div id="invoice">' |
|
30 |
'<h2>%s</h2>' % _('Invoice: %s') % invoice.subject |
|
31 |
'<h3>%s' % _('Amount: %s') % invoice.amount |
|
32 |
' €</h3>' |
|
33 |
'<!-- DEBUG \n' |
|
34 |
'Invoice:\n' |
|
35 |
pprint.pformat(invoice.__dict__) |
|
36 |
for transaction in Transaction.get_with_indexed_value('invoice_ids', invoice.id): |
|
37 |
'\nTransaction:\n' |
|
38 |
pprint.pformat(transaction.__dict__) |
|
39 |
'\n-->' |
|
40 |
if invoice.formdef_id and invoice.formdata_id and \ |
|
41 |
get_session().user == invoice.user_id: |
|
42 |
formdef = FormDef.get(invoice.formdef_id) |
|
43 |
if formdef: |
|
44 |
formdata = formdef.data_class().get(invoice.formdata_id, ignore_errors=True) |
|
45 |
if formdata: |
|
46 |
name = _('%(form_name)s #%(formdata_id)s') % { |
|
47 |
'form_name': formdata.formdef.name, |
|
48 |
'formdata_id': formdata.id } |
|
49 |
'<p class="from">%s <a href="%s">%s</a></p>' % (_('From:'), formdata.get_url(), name) |
|
50 |
'<p class="regie">%s</p>' % _('Regie: %s') % Regie.get(invoice.regie_id).label |
|
51 |
'<p class="date">%s</p>' % _('Created on: %s') % misc.localstrftime(invoice.date) |
|
52 |
if invoice.details: |
|
53 |
'<p class="details">%s</p>' % _('Details:') |
|
54 |
'<div class="details">' |
|
55 |
htmltext(invoice.details) |
|
56 |
'</div>' |
|
57 |
if invoice.canceled: |
|
58 |
'<p class="canceled">' |
|
59 |
'%s' % _('canceled on %s') % misc.localstrftime(invoice.canceled_date) |
|
60 |
if invoice.canceled_reason: |
|
61 |
' (%s)' % invoice.canceled_reason |
|
62 |
'</p>' |
|
63 |
if invoice.paid: |
|
64 |
'<p class="paid">%s</p>' % _('paid on %s') % misc.localstrftime(invoice.paid_date) |
|
65 |
'</div>' |
|
66 | ||
67 |
class InvoicesDirectory(Directory): |
|
68 |
_q_exports = ['', 'multiple'] |
|
69 | ||
70 |
def _q_traverse(self, path): |
|
71 |
if not is_payment_supported(): |
|
72 |
raise errors.TraversalError() |
|
73 |
get_response().filter['bigdiv'] = 'profile' |
|
74 |
if get_session().user: |
|
75 |
# fake breadcrumb |
|
76 |
get_response().breadcrumb.append(('myspace/', _('My Space'))) |
|
77 |
get_response().breadcrumb.append(('invoices/', _('Invoices'))) |
|
78 |
return Directory._q_traverse(self, path) |
|
79 | ||
80 |
def multiple [html] (self): |
|
81 |
invoice_ids = get_request().form.get('invoice') |
|
82 |
if type(invoice_ids) is not list: |
|
83 |
return redirect('%s' % invoice_ids) |
|
84 |
return redirect('+'.join(invoice_ids)) |
|
85 | ||
86 |
def _q_lookup [html] (self, component): |
|
87 |
if str('+') in component: |
|
88 |
invoice_ids = component.split(str('+')) |
|
89 |
else: |
|
90 |
invoice_ids = [component] |
|
91 |
for invoice_id in invoice_ids: |
|
92 |
if not Invoice.check_crc(invoice_id): |
|
93 |
raise errors.TraversalError() |
|
94 | ||
95 |
template.html_top(_('Invoices')) |
|
96 |
TextsDirectory.get_html_text('aq-invoice') |
|
97 | ||
98 |
regies_id = set() |
|
99 |
for invoice_id in invoice_ids: |
|
100 |
try: |
|
101 |
invoice = Invoice.get(invoice_id) |
|
102 |
except KeyError: |
|
103 |
raise errors.TraversalError() |
|
104 |
invoice_as_html(invoice) |
|
105 |
if not (invoice.paid or invoice.canceled): |
|
106 |
regies_id.add(invoice.regie_id) |
|
107 | ||
108 |
if len(regies_id) == 1: |
|
109 |
'<p class="command">' |
|
110 |
'<a href="%s/payment/init?invoice_ids=%s">' % (get_publisher().get_frontoffice_url(), component) |
|
111 |
if len(invoice_ids) > 1: |
|
112 |
_('Pay Selected Invoices') |
|
113 |
else: |
|
114 |
_('Pay') |
|
115 |
'</a></p>' |
|
116 |
if len(regies_id) > 1: |
|
117 |
_('You can not pay to different regies.') |
|
118 | ||
119 |
def _q_index(self): |
|
120 |
return redirect('..') |
|
121 | ||
122 | ||
123 |
class RegieDirectory(Directory): |
|
124 |
_q_exports = ['', 'edit', 'delete', 'options'] |
|
125 | ||
126 |
def __init__(self, regie): |
|
127 |
self.regie = regie |
|
128 | ||
129 |
def _q_index [html] (self): |
|
130 |
html_top('payments', title = _('Regie: %s') % self.regie.label) |
|
131 |
get_response().filter['sidebar'] = self.get_sidebar() |
|
132 |
'<h2>%s</h2>' % _('Regie: %s') % self.regie.label |
|
133 | ||
134 |
get_session().display_message() |
|
135 | ||
136 |
if self.regie.description: |
|
137 |
'<div class="bo-block">' |
|
138 |
'<p>' |
|
139 |
self.regie.description |
|
140 |
'</p>' |
|
141 |
'</div>' |
|
142 | ||
143 |
if self.regie.service: |
|
144 |
'<div class="bo-block">' |
|
145 |
url = get_publisher().get_frontoffice_url() + '/payment/back_asynchronous/' |
|
146 |
url += str(self.regie.id) |
|
147 |
'<p>' |
|
148 |
'%s %s' % (_('Banking Service:'), self.regie.service) |
|
149 |
' (<a href="options">%s</a>)' % _('options') |
|
150 |
'</p>' |
|
151 |
'<p>' |
|
152 |
'%s %s' % (_('Payment notification URL:'), url) |
|
153 |
'</div>' |
|
154 | ||
155 |
self.invoice_listing() |
|
156 | ||
157 |
def get_sidebar [html] (self): |
|
158 |
'<ul>' |
|
159 |
'<li><a href="edit">%s</a></li>' % _('Edit') |
|
160 |
'<li><a href="delete">%s</a></li>' % _('Delete') |
|
161 |
'</ul>' |
|
162 | ||
163 |
def edit [html] (self): |
|
164 |
form = self.form() |
|
165 |
if form.get_submit() == 'cancel': |
|
166 |
return redirect('.') |
|
167 | ||
168 |
if form.is_submitted() and not form.has_errors(): |
|
169 |
self.submit(form) |
|
170 |
return redirect('..') |
|
171 | ||
172 |
html_top('payments', title = _('Edit Regie: %s') % self.regie.label) |
|
173 |
'<h2>%s</h2>' % _('Edit Regie: %s') % self.regie.label |
|
174 |
form.render() |
|
175 | ||
176 | ||
177 |
def form(self): |
|
178 |
form = Form(enctype='multipart/form-data') |
|
179 |
form.add(StringWidget, 'label', title=_('Label'), required=True, |
|
180 |
value=self.regie.label) |
|
181 |
form.add(TextWidget, 'description', title=_('Description'), |
|
182 |
value=self.regie.description, rows=5, cols=60) |
|
183 |
form.add(SingleSelectWidget, 'service', title=_('Banking Service'), |
|
184 |
value=self.regie.service, required=True, |
|
185 |
options = [ |
|
186 |
('dummy', _('Dummy (for tests)')), |
|
187 |
('sips', 'SIPS'), |
|
188 |
('systempayv2', 'systempay (Banque Populaire)'), |
|
189 |
('spplus', _('SP+ (Caisse d\'epargne)'))]) |
|
190 |
form.add_submit('submit', _('Submit')) |
|
191 |
form.add_submit('cancel', _('Cancel')) |
|
192 |
return form |
|
193 | ||
194 |
def submit(self, form): |
|
195 |
for k in ('label', 'description', 'service'): |
|
196 |
widget = form.get_widget(k) |
|
197 |
if widget: |
|
198 |
setattr(self.regie, k, widget.parse()) |
|
199 |
self.regie.store() |
|
200 | ||
201 |
def delete [html] (self): |
|
202 |
form = Form(enctype='multipart/form-data') |
|
203 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
204 |
'You are about to irrevocably delete this regie.'))) |
|
205 |
form.add_submit('submit', _('Submit')) |
|
206 |
form.add_submit('cancel', _('Cancel')) |
|
207 |
if form.get_submit() == 'cancel': |
|
208 |
return redirect('..') |
|
209 |
if not form.is_submitted() or form.has_errors(): |
|
210 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
211 |
html_top('payments', title = _('Delete Regie')) |
|
212 |
'<h2>%s</h2>' % _('Deleting Regie: %s') % self.regie.label |
|
213 |
form.render() |
|
214 |
else: |
|
215 |
self.regie.remove_self() |
|
216 |
return redirect('..') |
|
217 | ||
218 |
def option_form(self): |
|
219 |
form = Form(enctype='multipart/form-data') |
|
220 |
module = eopayment.get_backend(self.regie.service) |
|
221 |
service_options = {} |
|
222 |
for infos in module.description['parameters']: |
|
223 |
if 'default' in infos: |
|
224 |
service_options[infos['name']] = infos['default'] |
|
225 |
service_options.update(self.regie.service_options or {}) |
|
226 | ||
227 |
banking_titles = { |
|
228 |
('dummy', 'direct_notification_url'): N_('Direct Notification URL'), |
|
229 |
('dummy', 'siret'): N_('Dummy SIRET'), |
|
230 |
} |
|
231 | ||
232 |
for infos in module.description['parameters']: |
|
233 |
name = infos['name'] |
|
234 |
caption = infos.get('caption', name).encode(get_publisher().site_charset) |
|
235 |
title = banking_titles.get((self.regie.service, name), caption) |
|
236 |
kwargs = {} |
|
237 |
widget = StringWidget |
|
238 |
if infos.get('help_text') is not None: |
|
239 |
kwargs['hint'] = _(infos['help_text'].encode(get_publisher().site_charset)) |
|
240 |
if infos.get('required', False): |
|
241 |
kwargs['required'] = True |
|
242 |
if infos.get('max_length') is not None: |
|
243 |
kwargs['size'] = infos['max_length'] |
|
244 |
elif infos.get('length') is not None: |
|
245 |
kwargs['size'] = infos['length'] |
|
246 |
else: |
|
247 |
kwargs['size'] = 80 |
|
248 |
if kwargs['size'] > 100: |
|
249 |
widget = TextWidget |
|
250 |
kwargs['cols'] = 80 |
|
251 |
kwargs['rows'] = 5 |
|
252 |
if 'type' not in infos or infos['type'] is str: |
|
253 |
form.add(widget, name, title=_(title), |
|
254 |
value=service_options.get(name), **kwargs) |
|
255 |
elif infos['type'] is bool: |
|
256 |
form.add(CheckboxWidget, name, title=title, |
|
257 |
value=service_options.get(name), **kwargs) |
|
258 |
form.add_submit('submit', _('Submit')) |
|
259 |
form.add_submit('cancel', _('Cancel')) |
|
260 |
return form |
|
261 | ||
262 |
def options [html] (self): |
|
263 |
form = self.option_form() |
|
264 | ||
265 |
module = eopayment.get_backend(self.regie.service) |
|
266 |
try: |
|
267 |
'<!-- Payment backend description: \n' |
|
268 |
pprint.pformat(module.description) |
|
269 |
'-->' |
|
270 |
except: |
|
271 |
return template.error_page(_('Payment backend do not list its options')) |
|
272 |
raise errors.TraversalError() |
|
273 |
'<!-- \n' |
|
274 |
'Service options\n' |
|
275 |
pprint.pformat(self.regie.service_options) |
|
276 |
'-->' |
|
277 | ||
278 |
if form.get_submit() == 'cancel': |
|
279 |
return redirect('.') |
|
280 | ||
281 |
if form.is_submitted() and not form.has_errors(): |
|
282 |
if self.submit_options(form, module): |
|
283 |
return redirect('..') |
|
284 | ||
285 |
html_top('payments', title=_('Edit Service Options')) |
|
286 |
'<h2>%s</h2>' % _('Edit Service Options') |
|
287 |
form.render() |
|
288 | ||
289 |
def submit_options(self, form, module): |
|
290 |
# extra validation |
|
291 |
error = False |
|
292 |
for infos in module.description['parameters']: |
|
293 |
widget = form.get_widget(infos['name']) |
|
294 |
value = widget.parse() |
|
295 |
if value and 'validation' in infos: |
|
296 |
try: |
|
297 |
if not infos['validation'](value): |
|
298 |
widget.set_error(_('Valeur invalide')) |
|
299 |
error = True |
|
300 |
except ValueError, e: |
|
301 |
widget.set_error(_(e.message)) |
|
302 |
error = True |
|
303 |
if error: |
|
304 |
return False |
|
305 |
if not self.regie.service_options: |
|
306 |
self.regie.service_options = {} |
|
307 |
for infos in module.description['parameters']: |
|
308 |
name = infos['name'] |
|
309 |
value = form.get_widget(name).parse() |
|
310 |
if value is None: |
|
311 |
value = '' |
|
312 |
if hasattr(value, 'strip'): |
|
313 |
value = value.strip() |
|
314 |
if infos.get('default') is not None: |
|
315 |
if value == infos['default']: |
|
316 |
self.regie.service_options.pop(name, None) |
|
317 |
else: |
|
318 |
self.regie.service_options[name] = form.get_widget(name).parse() |
|
319 |
elif not value: |
|
320 |
self.regie.service_options.pop(name, None) |
|
321 |
else: |
|
322 |
self.regie.service_options[name] = form.get_widget(name).parse() |
|
323 |
self.regie.store() |
|
324 |
return True |
|
325 | ||
326 |
PAGINATION = 50 |
|
327 | ||
328 |
def monetary_amount(self, val): |
|
329 |
if isinstance(val, basestring): |
|
330 |
val = val.replace(',', '.') |
|
331 |
return '%.2f' % decimal.Decimal(val) |
|
332 | ||
333 |
def get_sort_by(self): |
|
334 |
request = get_request() |
|
335 |
sort_by = request.form.get('sort_by') |
|
336 |
if sort_by not in ('date', 'paid_date', 'username'): |
|
337 |
sort_by = 'date' |
|
338 |
return sort_by |
|
339 | ||
340 |
def get_invoices(self): |
|
341 |
sort_by = self.get_sort_by() |
|
342 |
invoices = Invoice.get_with_indexed_value('regie_id', self.regie.id, |
|
343 |
ignore_errors=True) |
|
344 |
if 'date' in sort_by: |
|
345 |
reverse = True |
|
346 |
key = lambda i: getattr(i, sort_by) or datetime.datetime.now() |
|
347 |
else: |
|
348 |
reverse = False |
|
349 |
key = lambda i: getattr(i, sort_by) or '' |
|
350 |
invoices.sort(reverse=reverse, key=key) |
|
351 |
return invoices |
|
352 | ||
353 |
def unpay(self, request, invoice): |
|
354 |
get_logger().info(_('manually set unpaid invoice %(invoice_id)s in regie %(regie)s') |
|
355 |
% dict(invoice_id=invoice.id, regie=self.regie.id)) |
|
356 |
transaction = Transaction() |
|
357 |
transaction.invoice_ids = [ invoice.id ] |
|
358 |
transaction.order_id = 'Manual action' |
|
359 |
transaction.start = datetime.datetime.now() |
|
360 |
transaction.end = transaction.start |
|
361 |
transaction.bank_data = { |
|
362 |
'action': 'Set unpaid', |
|
363 |
'by': request.user.get_display_name() + ' (%s)' % request.user.id |
|
364 |
} |
|
365 |
transaction.store() |
|
366 |
invoice.unpay() |
|
367 | ||
368 |
def pay(self, request, invoice): |
|
369 |
get_logger().info(_('manually set paid invoice %(invoice_id)s in regie %(regie)s') |
|
370 |
% dict(invoice_id=invoice.id, regie=self.regie.id)) |
|
371 |
transaction = Transaction() |
|
372 |
transaction.invoice_ids = [ invoice.id ] |
|
373 |
transaction.order_id = 'Manual action' |
|
374 |
transaction.start = datetime.datetime.now() |
|
375 |
transaction.end = transaction.start |
|
376 |
transaction.bank_data = { |
|
377 |
'action': 'Set paid', |
|
378 |
'by': request.user.get_display_name() + ' (%s)' % request.user.id |
|
379 |
} |
|
380 |
transaction.store() |
|
381 |
invoice.pay() |
|
382 | ||
383 |
def invoice_listing [html] (self): |
|
384 |
request = get_request() |
|
385 |
get_response().add_css_include('../../themes/auquotidien/admin.css') |
|
386 |
if request.get_method() == 'POST': |
|
387 |
invoice_id = request.form.get('id') |
|
388 |
invoice = Invoice.get(invoice_id, ignore_errors=True) |
|
389 |
if invoice: |
|
390 |
if 'unpay' in request.form: |
|
391 |
self.unpay(request, invoice) |
|
392 |
elif 'pay' in request.form: |
|
393 |
self.pay(request, invoice) |
|
394 |
return redirect('') |
|
395 |
try: |
|
396 |
offset = int(request.form.get('offset', 0)) |
|
397 |
except ValueError: |
|
398 |
offset = 0 |
|
399 |
'<table id="invoice-listing" borderspacing="0">' |
|
400 |
'<thead>' |
|
401 |
'<tr>' |
|
402 |
'<td><a href="?sort_by=date&offset=%d">Creation</a></td>' % offset |
|
403 |
'<td>Amount</td>' |
|
404 |
'<td><a href="?sort_by=paid_date&offset=%d">Paid</a></td>' % offset |
|
405 |
'<td><a href="?sort_by=username&offset=%d">User</a></td>' % offset |
|
406 |
'<td>Titre</td>' |
|
407 |
'<td></td>' |
|
408 |
'</tr>' |
|
409 |
'</thead>' |
|
410 |
invoices = self.get_invoices() |
|
411 |
for invoice in invoices[offset:offset+self.PAGINATION]: |
|
412 |
'<tbody class="invoice-rows">' |
|
413 |
'<tr class="invoice-row"><td>' |
|
414 |
misc.localstrftime(invoice.date) |
|
415 |
'</td><td class="amount">' |
|
416 |
self.monetary_amount(invoice.amount) |
|
417 |
'</td><td>' |
|
418 |
if invoice.paid: |
|
419 |
misc.localstrftime(invoice.paid_date) |
|
420 |
else: |
|
421 |
'' |
|
422 |
'</td><td>' |
|
423 |
user = invoice.get_user() |
|
424 |
if user: |
|
425 |
user.name |
|
426 |
'</td><td class="subject">%s</td>' % (invoice.subject or '') |
|
427 |
'<td>' |
|
428 |
'<form method="post">' |
|
429 |
'<input type="hidden" name="id" value="%s"/> ' % invoice.id |
|
430 |
if invoice.paid: |
|
431 |
'<input type="submit" name="unpay" value="%s"/>' % _('Set unpaid') |
|
432 |
else: |
|
433 |
'<input type="submit" name="pay" value="%s"/>' % _('Set paid') |
|
434 |
'</form>' |
|
435 | ||
436 |
'</td></tr>' |
|
437 |
transactions = Transaction.get_with_indexed_value('invoice_ids', |
|
438 |
invoice.id) |
|
439 |
for transaction in sorted(transactions, key=lambda x: x.start): |
|
440 |
'<tr>' |
|
441 |
'<td></td>' |
|
442 |
'<td colspan="5">' |
|
443 |
'OrderID: %s' % transaction.order_id |
|
444 |
' Start: %s' % transaction.start |
|
445 |
if transaction.end: |
|
446 |
' End: %s' % transaction.end |
|
447 |
if transaction.bank_data: |
|
448 |
' Bank data: %r' % transaction.bank_data |
|
449 |
'</td>' |
|
450 |
'</tr>' |
|
451 |
'</tbody>' |
|
452 |
'</tbody></table>' |
|
453 |
if offset != 0: |
|
454 |
'<a href="?offset=%d>%s</a> ' % ( |
|
455 |
max(0, offset-self.PAGINATION), _('Previous')) |
|
456 |
if offset + self.PAGINATION < len(invoices): |
|
457 |
'<a href="?offset=%d>%s</a> ' % ( |
|
458 |
max(0, offset-self.PAGINATION), _('Previous')) |
|
459 | ||
460 | ||
461 |
class RegiesDirectory(Directory): |
|
462 |
_q_exports = ['', 'new'] |
|
463 | ||
464 |
def _q_traverse(self, path): |
|
465 |
get_response().breadcrumb.append(('regie/', _('Regies'))) |
|
466 |
return Directory._q_traverse(self, path) |
|
467 | ||
468 |
def _q_index [html] (self): |
|
469 |
return redirect('..') |
|
470 | ||
471 |
def new [html] (self): |
|
472 |
regie_ui = RegieDirectory(Regie()) |
|
473 | ||
474 |
form = regie_ui.form() |
|
475 |
if form.get_submit() == 'cancel': |
|
476 |
return redirect('.') |
|
477 | ||
478 |
if form.is_submitted() and not form.has_errors(): |
|
479 |
regie_ui.submit(form) |
|
480 |
return redirect('%s/' % regie_ui.regie.id) |
|
481 | ||
482 |
get_response().breadcrumb.append(('new', _('New Regie'))) |
|
483 |
html_top('payments', title = _('New Regie')) |
|
484 |
'<h2>%s</h2>' % _('New Regie') |
|
485 |
form.render() |
|
486 | ||
487 |
def _q_lookup(self, component): |
|
488 |
try: |
|
489 |
regie = Regie.get(component) |
|
490 |
except KeyError: |
|
491 |
raise errors.TraversalError() |
|
492 |
get_response().breadcrumb.append((str(regie.id), regie.label)) |
|
493 |
return RegieDirectory(regie) |
|
494 | ||
495 | ||
496 |
class PaymentsDirectory(AccessControlled, Directory): |
|
497 |
_q_exports = ['', 'regie'] |
|
498 |
label = N_('Payments') |
|
499 | ||
500 |
regie = RegiesDirectory() |
|
501 | ||
502 |
def _q_access(self): |
|
503 |
user = get_request().user |
|
504 |
if not user: |
|
505 |
raise errors.AccessUnauthorizedError() |
|
506 |
admin_role = get_cfg('aq-permissions', {}).get('payments', None) |
|
507 |
if not (user.is_admin or admin_role in (user.roles or [])): |
|
508 |
raise errors.AccessForbiddenError( |
|
509 |
public_msg = _('You are not allowed to access Payments Management'), |
|
510 |
location_hint = 'backoffice') |
|
511 | ||
512 |
get_response().breadcrumb.append(('payments/', _('Payments'))) |
|
513 | ||
514 | ||
515 |
def _q_index [html] (self): |
|
516 |
html_top('payments', _('Payments')) |
|
517 | ||
518 |
'<ul id="main-actions">' |
|
519 |
' <li><a class="new-item" href="regie/new">%s</a></li>' % _('New Regie') |
|
520 |
'</ul>' |
|
521 | ||
522 |
if not is_payment_supported: |
|
523 |
'<p class="infonotice">' |
|
524 |
_('Payment is not supported.') |
|
525 |
'</p>' |
|
526 | ||
527 |
regies = Regie.select() |
|
528 |
'<h2>%s</h2>' % _('Regies') |
|
529 |
if not regies: |
|
530 |
'<p>' |
|
531 |
_('There are no regies defined at the moment.') |
|
532 |
'</p>' |
|
533 |
'<ul class="biglist" id="regies-list">' |
|
534 |
for l in regies: |
|
535 |
regie_id = l.id |
|
536 |
'<li class="biglistitem" id="itemId_%s">' % regie_id |
|
537 |
'<strong class="label"><a href="regie/%s/">%s</a></strong>' % (regie_id, l.label) |
|
538 |
'</li>' |
|
539 |
'</ul>' |
|
540 | ||
541 | ||
542 |
TextsDirectory.register('aq-invoice', |
|
543 |
N_('Message on top of an invoice'), |
|
544 |
category = N_('Invoices')) |
|
545 | ||
546 |
EmailsDirectory.register('payment-new-invoice-email', |
|
547 |
N_('New invoice'), |
|
548 |
N_('Available variables: user, regie, invoice, invoice_url'), |
|
549 |
category = N_('Invoices'), |
|
550 |
default_subject = N_('New invoice'), |
|
551 |
default_body = N_(''' |
|
552 |
A new invoice is available at [invoice_url]. |
|
553 |
''')) |
|
554 | ||
555 |
EmailsDirectory.register('payment-invoice-paid-email', |
|
556 |
N_('Paid invoice'), |
|
557 |
N_('Available variables: user, regie, invoice, invoice_url'), |
|
558 |
category = N_('Invoices'), |
|
559 |
default_subject = N_('Paid invoice'), |
|
560 |
default_body = N_(''' |
|
561 |
The invoice [invoice_url] has been paid. |
|
562 |
''')) |
|
563 | ||
564 |
EmailsDirectory.register('payment-invoice-canceled-email', |
|
565 |
N_('Canceled invoice'), |
|
566 |
N_('Available variables: user, regie, invoice, invoice_url'), |
|
567 |
category = N_('Invoices'), |
|
568 |
default_subject = N_('Canceled invoice'), |
|
569 |
default_body = N_(''' |
|
570 |
The invoice [invoice.id] has been canceled. |
|
571 |
''')) |
extra/modules/payments_ui.py | ||
---|---|---|
1 |
import time |
|
2 |
import pprint |
|
3 |
import locale |
|
4 |
import decimal |
|
5 |
import datetime |
|
6 | ||
7 |
from quixote import get_request, get_response, get_session, redirect |
|
8 |
from quixote.directory import Directory, AccessControlled |
|
9 |
from quixote.html import TemplateIO, htmltext |
|
10 | ||
11 |
import wcs |
|
12 |
import wcs.admin.root |
|
13 |
from wcs.backoffice.menu import * |
|
14 |
from wcs.formdef import FormDef |
|
15 | ||
16 |
from qommon import errors, misc, template, get_logger |
|
17 |
from qommon.form import * |
|
18 |
from qommon.strftime import strftime |
|
19 |
from qommon.admin.emails import EmailsDirectory |
|
20 | ||
21 |
from payments import (eopayment, Regie, is_payment_supported, Invoice, |
|
22 |
Transaction, notify_paid_invoice) |
|
23 | ||
24 |
from qommon.admin.texts import TextsDirectory |
|
25 | ||
26 |
if not set: |
|
27 |
from sets import Set as set |
|
28 | ||
29 |
def invoice_as_html(invoice): |
|
30 |
r = TemplateIO(html=True) |
|
31 |
r += htmltext('<div id="invoice">') |
|
32 |
r += htmltext('<h2>%s</h2>') % _('Invoice: %s') % invoice.subject |
|
33 |
r += htmltext('<h3>%s') % _('Amount: %s') % invoice.amount |
|
34 |
r += htmltext(' €</h3>') |
|
35 |
r += htmltext('<!-- DEBUG \n') |
|
36 |
r += 'Invoice:\n' |
|
37 |
r += pprint.pformat(invoice.__dict__) |
|
38 |
for transaction in Transaction.get_with_indexed_value('invoice_ids', invoice.id): |
|
39 |
r += '\nTransaction:\n' |
|
40 |
r += pprint.pformat(transaction.__dict__) |
|
41 |
r += '\n-->' |
|
42 |
if invoice.formdef_id and invoice.formdata_id and \ |
|
43 |
get_session().user == invoice.user_id: |
|
44 |
formdef = FormDef.get(invoice.formdef_id) |
|
45 |
if formdef: |
|
46 |
formdata = formdef.data_class().get(invoice.formdata_id, ignore_errors=True) |
|
47 |
if formdata: |
|
48 |
name = _('%(form_name)s #%(formdata_id)s') % { |
|
49 |
'form_name': formdata.formdef.name, |
|
50 |
'formdata_id': formdata.id } |
|
51 |
r += htmltext('<p class="from">%s <a href="%s">%s</a></p>') % (_('From:'), formdata.get_url(), name) |
|
52 |
r += htmltext('<p class="regie">%s</p>') % _('Regie: %s') % Regie.get(invoice.regie_id).label |
|
53 |
r += htmltext('<p class="date">%s</p>') % _('Created on: %s') % misc.localstrftime(invoice.date) |
|
54 |
if invoice.details: |
|
55 |
r += htmltext('<p class="details">%s</p>') % _('Details:') |
|
56 |
r += htmltext('<div class="details">') |
|
57 |
r += htmltext(invoice.details) |
|
58 |
r += htmltext('</div>') |
|
59 |
if invoice.canceled: |
|
60 |
r += htmltext('<p class="canceled">') |
|
61 |
r += '%s' % _('canceled on %s') % misc.localstrftime(invoice.canceled_date) |
|
62 |
if invoice.canceled_reason: |
|
63 |
r += ' (%s)' % invoice.canceled_reason |
|
64 |
r += htmltext('</p>') |
|
65 |
if invoice.paid: |
|
66 |
r += htmltext('<p class="paid">%s</p>') % _('paid on %s') % misc.localstrftime(invoice.paid_date) |
|
67 |
r += htmltext('</div>') |
|
68 |
return r.getvalue() |
|
69 | ||
70 | ||
71 |
class InvoicesDirectory(Directory): |
|
72 |
_q_exports = ['', 'multiple'] |
|
73 | ||
74 |
def _q_traverse(self, path): |
|
75 |
if not is_payment_supported(): |
|
76 |
raise errors.TraversalError() |
|
77 |
get_response().filter['bigdiv'] = 'profile' |
|
78 |
if get_session().user: |
|
79 |
# fake breadcrumb |
|
80 |
get_response().breadcrumb.append(('myspace/', _('My Space'))) |
|
81 |
get_response().breadcrumb.append(('invoices/', _('Invoices'))) |
|
82 |
return Directory._q_traverse(self, path) |
|
83 | ||
84 |
def multiple(self): |
|
85 |
invoice_ids = get_request().form.get('invoice') |
|
86 |
if type(invoice_ids) is not list: |
|
87 |
return redirect('%s' % invoice_ids) |
|
88 |
return redirect('+'.join(invoice_ids)) |
|
89 | ||
90 |
def _q_lookup(self, component): |
|
91 |
if str('+') in component: |
|
92 |
invoice_ids = component.split(str('+')) |
|
93 |
else: |
|
94 |
invoice_ids = [component] |
|
95 |
for invoice_id in invoice_ids: |
|
96 |
if not Invoice.check_crc(invoice_id): |
|
97 |
raise errors.TraversalError() |
|
98 | ||
99 |
template.html_top(_('Invoices')) |
|
100 |
r = TemplateIO(html=True) |
|
101 |
r += TextsDirectory.get_html_text('aq-invoice') |
|
102 | ||
103 |
regies_id = set() |
|
104 |
for invoice_id in invoice_ids: |
|
105 |
try: |
|
106 |
invoice = Invoice.get(invoice_id) |
|
107 |
except KeyError: |
|
108 |
raise errors.TraversalError() |
|
109 |
r += invoice_as_html(invoice) |
|
110 |
if not (invoice.paid or invoice.canceled): |
|
111 |
regies_id.add(invoice.regie_id) |
|
112 | ||
113 |
if len(regies_id) == 1: |
|
114 |
r += htmltext('<p class="command">') |
|
115 |
r += htmltext('<a href="%s/payment/init?invoice_ids=%s">') % ( |
|
116 |
get_publisher().get_frontoffice_url(), component) |
|
117 |
if len(invoice_ids) > 1: |
|
118 |
r += _('Pay Selected Invoices') |
|
119 |
else: |
|
120 |
r += _('Pay') |
|
121 |
r += htmltext('</a></p>') |
|
122 |
if len(regies_id) > 1: |
|
123 |
r += _('You can not pay to different regies.') |
|
124 | ||
125 |
return r.getvalue() |
|
126 | ||
127 |
def _q_index(self): |
|
128 |
return redirect('..') |
|
129 | ||
130 | ||
131 |
class RegieDirectory(Directory): |
|
132 |
_q_exports = ['', 'edit', 'delete', 'options'] |
|
133 | ||
134 |
def __init__(self, regie): |
|
135 |
self.regie = regie |
|
136 | ||
137 |
def _q_index(self): |
|
138 |
html_top('payments', title = _('Regie: %s') % self.regie.label) |
|
139 |
r = TemplateIO(html=True) |
|
140 |
get_response().filter['sidebar'] = self.get_sidebar() |
|
141 |
r += htmltext('<h2>%s</h2>') % _('Regie: %s') % self.regie.label |
|
142 | ||
143 |
r += get_session().display_message() |
|
144 | ||
145 |
if self.regie.description: |
|
146 |
r += htmltext('<div class="bo-block">') |
|
147 |
r += htmltext('<p>') |
|
148 |
r += self.regie.description |
|
149 |
r += htmltext('</p>') |
|
150 |
r += htmltext('</div>') |
|
151 | ||
152 |
if self.regie.service: |
|
153 |
r += htmltext('<div class="bo-block">') |
|
154 |
url = get_publisher().get_frontoffice_url() + '/payment/back_asynchronous/' |
|
155 |
url += str(self.regie.id) |
|
156 |
r += htmltext('<p>') |
|
157 |
r += '%s %s' % (_('Banking Service:'), self.regie.service) |
|
158 |
r += htmltext(' (<a href="options">%s</a>)') % _('options') |
|
159 |
r += htmltext('</p>') |
|
160 |
r += htmltext('<p>') |
|
161 |
r += '%s %s' % (_('Payment notification URL:'), url) |
|
162 |
r += htmltext('</div>') |
|
163 | ||
164 |
r += self.invoice_listing() |
|
165 |
return r.getvalue() |
|
166 | ||
167 |
def get_sidebar(self): |
|
168 |
r = TemplateIO(html=True) |
|
169 |
r += htmltext('<ul>') |
|
170 |
r += htmltext('<li><a href="edit">%s</a></li>') % _('Edit') |
|
171 |
r += htmltext('<li><a href="delete">%s</a></li>') % _('Delete') |
|
172 |
r += htmltext('</ul>') |
|
173 |
return r.getvalue() |
|
174 | ||
175 |
def edit(self): |
|
176 |
form = self.form() |
|
177 |
if form.get_submit() == 'cancel': |
|
178 |
return redirect('.') |
|
179 | ||
180 |
if form.is_submitted() and not form.has_errors(): |
|
181 |
self.submit(form) |
|
182 |
return redirect('..') |
|
183 | ||
184 |
html_top('payments', title = _('Edit Regie: %s') % self.regie.label) |
|
185 |
r = TemplateIO(html=True) |
|
186 |
r += htmltext('<h2>%s</h2>') % _('Edit Regie: %s') % self.regie.label |
|
187 |
r += form.render() |
|
188 |
return r.getvalue() |
|
189 | ||
190 | ||
191 |
def form(self): |
|
192 |
form = Form(enctype='multipart/form-data') |
|
193 |
form.add(StringWidget, 'label', title=_('Label'), required=True, |
|
194 |
value=self.regie.label) |
|
195 |
form.add(TextWidget, 'description', title=_('Description'), |
|
196 |
value=self.regie.description, rows=5, cols=60) |
|
197 |
form.add(SingleSelectWidget, 'service', title=_('Banking Service'), |
|
198 |
value=self.regie.service, required=True, |
|
199 |
options = [ |
|
200 |
('dummy', _('Dummy (for tests)')), |
|
201 |
('sips', 'SIPS'), |
|
202 |
('systempayv2', 'systempay (Banque Populaire)'), |
|
203 |
('spplus', _('SP+ (Caisse d\'epargne)'))]) |
|
204 |
form.add_submit('submit', _('Submit')) |
|
205 |
form.add_submit('cancel', _('Cancel')) |
|
206 |
return form |
|
207 | ||
208 |
def submit(self, form): |
|
209 |
for k in ('label', 'description', 'service'): |
|
210 |
widget = form.get_widget(k) |
|
211 |
if widget: |
|
212 |
setattr(self.regie, k, widget.parse()) |
|
213 |
self.regie.store() |
|
214 | ||
215 |
def delete(self): |
|
216 |
form = Form(enctype='multipart/form-data') |
|
217 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
218 |
'You are about to irrevocably delete this regie.'))) |
|
219 |
form.add_submit('submit', _('Submit')) |
|
220 |
form.add_submit('cancel', _('Cancel')) |
|
221 |
if form.get_submit() == 'cancel': |
|
222 |
return redirect('..') |
|
223 |
if not form.is_submitted() or form.has_errors(): |
|
224 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
225 |
r = TemplateIO(html=True) |
|
226 |
html_top('payments', title = _('Delete Regie')) |
|
227 |
r += htmltext('<h2>%s</h2>') % _('Deleting Regie: %s') % self.regie.label |
|
228 |
r += form.render() |
|
229 |
return r.getvalue() |
|
230 |
else: |
|
231 |
self.regie.remove_self() |
|
232 |
return redirect('..') |
|
233 | ||
234 |
def option_form(self): |
|
235 |
form = Form(enctype='multipart/form-data') |
|
236 |
module = eopayment.get_backend(self.regie.service) |
|
237 |
service_options = {} |
|
238 |
for infos in module.description['parameters']: |
|
239 |
if 'default' in infos: |
|
240 |
service_options[infos['name']] = infos['default'] |
|
241 |
service_options.update(self.regie.service_options or {}) |
|
242 | ||
243 |
banking_titles = { |
|
244 |
('dummy', 'direct_notification_url'): N_('Direct Notification URL'), |
|
245 |
('dummy', 'siret'): N_('Dummy SIRET'), |
|
246 |
} |
|
247 | ||
248 |
for infos in module.description['parameters']: |
|
249 |
name = infos['name'] |
|
250 |
caption = infos.get('caption', name).encode(get_publisher().site_charset) |
|
251 |
title = banking_titles.get((self.regie.service, name), caption) |
|
252 |
kwargs = {} |
|
253 |
widget = StringWidget |
|
254 |
if infos.get('help_text') is not None: |
|
255 |
kwargs['hint'] = _(infos['help_text'].encode(get_publisher().site_charset)) |
|
256 |
if infos.get('required', False): |
|
257 |
kwargs['required'] = True |
|
258 |
if infos.get('max_length') is not None: |
|
259 |
kwargs['size'] = infos['max_length'] |
|
260 |
elif infos.get('length') is not None: |
|
261 |
kwargs['size'] = infos['length'] |
|
262 |
else: |
|
263 |
kwargs['size'] = 80 |
|
264 |
if kwargs['size'] > 100: |
|
265 |
widget = TextWidget |
|
266 |
kwargs['cols'] = 80 |
|
267 |
kwargs['rows'] = 5 |
|
268 |
if 'type' not in infos or infos['type'] is str: |
|
269 |
form.add(widget, name, title=_(title), |
|
270 |
value=service_options.get(name), **kwargs) |
|
271 |
elif infos['type'] is bool: |
|
272 |
form.add(CheckboxWidget, name, title=title, |
|
273 |
value=service_options.get(name), **kwargs) |
|
274 |
form.add_submit('submit', _('Submit')) |
|
275 |
form.add_submit('cancel', _('Cancel')) |
|
276 |
return form |
|
277 | ||
278 |
def options(self): |
|
279 |
r = TemplateIO(html=True) |
|
280 |
form = self.option_form() |
|
281 | ||
282 |
module = eopayment.get_backend(self.regie.service) |
|
283 |
try: |
|
284 |
r += htmltext('<!-- Payment backend description: \n') |
|
285 |
r += pprint.pformat(module.description) |
|
286 |
r += htmltext('-->') |
|
287 |
except: |
|
288 |
return template.error_page(_('Payment backend do not list its options')) |
|
289 |
raise errors.TraversalError() |
|
290 |
r += htmltext('<!-- \n') |
|
291 |
r += 'Service options\n' |
|
292 |
r += pprint.pformat(self.regie.service_options) |
|
293 |
r += htmltext('-->') |
|
294 | ||
295 |
if form.get_submit() == 'cancel': |
|
296 |
return redirect('.') |
|
297 | ||
298 |
if form.is_submitted() and not form.has_errors(): |
|
299 |
if self.submit_options(form, module): |
|
300 |
return redirect('..') |
|
301 | ||
302 |
html_top('payments', title=_('Edit Service Options')) |
|
303 |
r += htmltext('<h2>%s</h2>') % _('Edit Service Options') |
|
304 |
r += form.render() |
|
305 |
return r.getvalue() |
|
306 | ||
307 |
def submit_options(self, form, module): |
|
308 |
# extra validation |
|
309 |
error = False |
|
310 |
for infos in module.description['parameters']: |
|
311 |
widget = form.get_widget(infos['name']) |
|
312 |
value = widget.parse() |
|
313 |
if value and 'validation' in infos: |
|
314 |
try: |
|
315 |
if not infos['validation'](value): |
|
316 |
widget.set_error(_('Valeur invalide')) |
|
317 |
error = True |
|
318 |
except ValueError, e: |
|
319 |
widget.set_error(_(e.message)) |
|
320 |
error = True |
|
321 |
if error: |
|
322 |
return False |
|
323 |
if not self.regie.service_options: |
|
324 |
self.regie.service_options = {} |
|
325 |
for infos in module.description['parameters']: |
|
326 |
name = infos['name'] |
|
327 |
value = form.get_widget(name).parse() |
|
328 |
if value is None: |
|
329 |
value = '' |
|
330 |
if hasattr(value, 'strip'): |
|
331 |
value = value.strip() |
|
332 |
if infos.get('default') is not None: |
|
333 |
if value == infos['default']: |
|
334 |
self.regie.service_options.pop(name, None) |
|
335 |
else: |
|
336 |
self.regie.service_options[name] = form.get_widget(name).parse() |
|
337 |
elif not value: |
|
338 |
self.regie.service_options.pop(name, None) |
|
339 |
else: |
|
340 |
self.regie.service_options[name] = form.get_widget(name).parse() |
|
341 |
self.regie.store() |
|
342 |
return True |
|
343 | ||
344 |
PAGINATION = 50 |
|
345 | ||
346 |
def monetary_amount(self, val): |
|
347 |
if isinstance(val, basestring): |
|
348 |
val = val.replace(',', '.') |
|
349 |
return '%.2f' % decimal.Decimal(val) |
|
350 | ||
351 |
def get_sort_by(self): |
|
352 |
request = get_request() |
|
353 |
sort_by = request.form.get('sort_by') |
|
354 |
if sort_by not in ('date', 'paid_date', 'username'): |
|
355 |
sort_by = 'date' |
|
356 |
return sort_by |
|
357 | ||
358 |
def get_invoices(self): |
|
359 |
sort_by = self.get_sort_by() |
|
360 |
invoices = Invoice.get_with_indexed_value('regie_id', self.regie.id, |
|
361 |
ignore_errors=True) |
|
362 |
if 'date' in sort_by: |
|
363 |
reverse = True |
|
364 |
key = lambda i: getattr(i, sort_by) or datetime.datetime.now() |
|
365 |
else: |
|
366 |
reverse = False |
|
367 |
key = lambda i: getattr(i, sort_by) or '' |
|
368 |
invoices.sort(reverse=reverse, key=key) |
|
369 |
return invoices |
|
370 | ||
371 |
def unpay(self, request, invoice): |
|
372 |
get_logger().info(_('manually set unpaid invoice %(invoice_id)s in regie %(regie)s') |
|
373 |
% dict(invoice_id=invoice.id, regie=self.regie.id)) |
|
374 |
transaction = Transaction() |
|
375 |
transaction.invoice_ids = [ invoice.id ] |
|
376 |
transaction.order_id = 'Manual action' |
|
377 |
transaction.start = datetime.datetime.now() |
|
378 |
transaction.end = transaction.start |
|
379 |
transaction.bank_data = { |
|
380 |
'action': 'Set unpaid', |
|
381 |
'by': request.user.get_display_name() + ' (%s)' % request.user.id |
|
382 |
} |
|
383 |
transaction.store() |
|
384 |
invoice.unpay() |
|
385 | ||
386 |
def pay(self, request, invoice): |
|
387 |
get_logger().info(_('manually set paid invoice %(invoice_id)s in regie %(regie)s') |
|
388 |
% dict(invoice_id=invoice.id, regie=self.regie.id)) |
|
389 |
transaction = Transaction() |
|
390 |
transaction.invoice_ids = [ invoice.id ] |
|
391 |
transaction.order_id = 'Manual action' |
|
392 |
transaction.start = datetime.datetime.now() |
|
393 |
transaction.end = transaction.start |
|
394 |
transaction.bank_data = { |
|
395 |
'action': 'Set paid', |
|
396 |
'by': request.user.get_display_name() + ' (%s)' % request.user.id |
|
397 |
} |
|
398 |
transaction.store() |
|
399 |
invoice.pay() |
|
400 | ||
401 |
def invoice_listing(self): |
|
402 |
request = get_request() |
|
403 |
get_response().add_css_include('../../themes/auquotidien/admin.css') |
|
404 |
if request.get_method() == 'POST': |
|
405 |
invoice_id = request.form.get('id') |
|
406 |
invoice = Invoice.get(invoice_id, ignore_errors=True) |
|
407 |
if invoice: |
|
408 |
if 'unpay' in request.form: |
|
409 |
self.unpay(request, invoice) |
|
410 |
elif 'pay' in request.form: |
|
411 |
self.pay(request, invoice) |
|
412 |
return redirect('') |
|
413 |
try: |
|
414 |
offset = int(request.form.get('offset', 0)) |
|
415 |
except ValueError: |
|
416 |
offset = 0 |
|
417 |
r = TemplateIO(html=True) |
|
418 |
r += htmltext('<table id="invoice-listing" borderspacing="0">') |
|
419 |
r += htmltext('<thead>') |
|
420 |
r += htmltext('<tr>') |
|
421 |
r += htmltext('<td><a href="?sort_by=date&offset=%d">Creation</a></td>') % offset |
|
422 |
r += htmltext('<td>Amount</td>') |
|
423 |
r += htmltext('<td><a href="?sort_by=paid_date&offset=%d">Paid</a></td>') % offset |
|
424 |
r += htmltext('<td><a href="?sort_by=username&offset=%d">User</a></td>') % offset |
|
425 |
r += htmltext('<td>Titre</td>') |
|
426 |
r += htmltext('<td></td>') |
|
427 |
r += htmltext('</tr>') |
|
428 |
r += htmltext('</thead>') |
|
429 |
invoices = self.get_invoices() |
|
430 |
for invoice in invoices[offset:offset+self.PAGINATION]: |
|
431 |
r += htmltext('<tbody class="invoice-rows">') |
|
432 |
r += htmltext('<tr class="invoice-row"><td>') |
|
433 |
r += misc.localstrftime(invoice.date) |
|
434 |
r += htmltext('</td><td class="amount">') |
|
435 |
r += self.monetary_amount(invoice.amount) |
|
436 |
r += htmltext('</td><td>') |
|
437 |
if invoice.paid: |
|
438 |
r += misc.localstrftime(invoice.paid_date) |
|
439 |
else: |
|
440 |
r += '' |
|
441 |
r += htmltext('</td><td>') |
|
442 |
user = invoice.get_user() |
|
443 |
if user: |
|
444 |
r += user.name |
|
445 |
r += htmltext('</td><td class="subject">%s</td>') % (invoice.subject or '') |
|
446 |
r += htmltext('<td>') |
|
447 |
r += htmltext('<form method="post">') |
|
448 |
r += htmltext('<input type="hidden" name="id" value="%s"/> ') % invoice.id |
|
449 |
if invoice.paid: |
|
450 |
r += htmltext('<input type="submit" name="unpay" value="%s"/>') % _('Set unpaid') |
|
451 |
else: |
|
452 |
r += htmltext('<input type="submit" name="pay" value="%s"/>') % _('Set paid') |
|
453 |
r += htmltext('</form>') |
|
454 | ||
455 |
r += htmltext('</td></tr>') |
|
456 |
transactions = Transaction.get_with_indexed_value('invoice_ids', |
|
457 |
invoice.id) |
|
458 |
for transaction in sorted(transactions, key=lambda x: x.start): |
|
459 |
r += htmltext('<tr>') |
|
460 |
r += htmltext('<td></td>') |
|
461 |
r += htmltext('<td colspan="5">') |
|
462 |
r += 'OrderID: %s' % transaction.order_id |
|
463 |
r += ' Start: %s' % transaction.start |
|
464 |
if transaction.end: |
|
465 |
r += ' End: %s' % transaction.end |
|
466 |
if transaction.bank_data: |
|
467 |
r += ' Bank data: %r' % transaction.bank_data |
|
468 |
r += htmltext('</td>') |
|
469 |
r += htmltext('</tr>') |
|
470 |
r += htmltext('</tbody>') |
|
471 |
r += htmltext('</tbody></table>') |
|
472 |
if offset != 0: |
|
473 |
r += htmltext('<a href="?offset=%d>%s</a> ') % ( |
|
474 |
max(0, offset-self.PAGINATION), _('Previous')) |
|
475 |
if offset + self.PAGINATION < len(invoices): |
|
476 |
r += htmltext('<a href="?offset=%d>%s</a> ') % ( |
|
477 |
max(0, offset-self.PAGINATION), _('Previous')) |
|
478 |
return r.getvalue() |
|
479 | ||
480 | ||
481 |
class RegiesDirectory(Directory): |
|
482 |
_q_exports = ['', 'new'] |
|
483 | ||
484 |
def _q_traverse(self, path): |
|
485 |
get_response().breadcrumb.append(('regie/', _('Regies'))) |
|
486 |
return Directory._q_traverse(self, path) |
|
487 | ||
488 |
def _q_index(self): |
|
489 |
return redirect('..') |
|
490 | ||
491 |
def new(self): |
|
492 |
regie_ui = RegieDirectory(Regie()) |
|
493 | ||
494 |
form = regie_ui.form() |
|
495 |
if form.get_submit() == 'cancel': |
|
496 |
return redirect('.') |
|
497 | ||
498 |
if form.is_submitted() and not form.has_errors(): |
|
499 |
regie_ui.submit(form) |
|
500 |
return redirect('%s/' % regie_ui.regie.id) |
|
501 | ||
502 |
get_response().breadcrumb.append(('new', _('New Regie'))) |
|
503 |
html_top('payments', title = _('New Regie')) |
|
504 |
r = TemplateIO(html=True) |
|
505 |
r += htmltext('<h2>%s</h2>') % _('New Regie') |
|
506 |
r += form.render() |
|
507 |
return r.getvalue() |
|
508 | ||
509 |
def _q_lookup(self, component): |
|
510 |
try: |
|
511 |
regie = Regie.get(component) |
|
512 |
except KeyError: |
|
513 |
raise errors.TraversalError() |
|
514 |
get_response().breadcrumb.append((str(regie.id), regie.label)) |
|
515 |
return RegieDirectory(regie) |
|
516 | ||
517 | ||
518 |
class PaymentsDirectory(AccessControlled, Directory): |
|
519 |
_q_exports = ['', 'regie'] |
|
520 |
label = N_('Payments') |
|
521 | ||
522 |
regie = RegiesDirectory() |
|
523 | ||
524 |
def _q_access(self): |
|
525 |
user = get_request().user |
|
526 |
if not user: |
|
527 |
raise errors.AccessUnauthorizedError() |
|
528 |
admin_role = get_cfg('aq-permissions', {}).get('payments', None) |
|
529 |
if not (user.is_admin or admin_role in (user.roles or [])): |
|
530 |
raise errors.AccessForbiddenError( |
|
531 |
public_msg = _('You are not allowed to access Payments Management'), |
|
532 |
location_hint = 'backoffice') |
|
533 | ||
534 |
get_response().breadcrumb.append(('payments/', _('Payments'))) |
|
535 | ||
536 | ||
537 |
def _q_index(self): |
|
538 |
html_top('payments', _('Payments')) |
|
539 |
r = TemplateIO(html=True) |
|
540 | ||
541 |
r += htmltext('<ul id="main-actions">') |
|
542 |
r += htmltext(' <li><a class="new-item" href="regie/new">%s</a></li>') % _('New Regie') |
|
543 |
r += htmltext('</ul>') |
|
544 | ||
545 |
if not is_payment_supported: |
|
546 |
r += htmltext('<p class="infonotice">') |
|
547 |
r += _('Payment is not supported.') |
|
548 |
r += htmltext('</p>') |
|
549 | ||
550 |
regies = Regie.select() |
|
551 |
r += htmltext('<h2>%s</h2>') % _('Regies') |
|
552 |
if not regies: |
|
553 |
r += htmltext('<p>') |
|
554 |
r += _('There are no regies defined at the moment.') |
|
555 |
r += htmltext('</p>') |
|
556 |
r += htmltext('<ul class="biglist" id="regies-list">') |
|
557 |
for l in regies: |
|
558 |
regie_id = l.id |
|
559 |
r += htmltext('<li class="biglistitem" id="itemId_%s">') % regie_id |
|
560 |
r += htmltext('<strong class="label"><a href="regie/%s/">%s</a></strong>') % (regie_id, l.label) |
|
561 |
r += htmltext('</li>') |
|
562 |
r += htmltext('</ul>') |
|
563 |
return r.getvalue() |
|
564 | ||
565 | ||
566 |
TextsDirectory.register('aq-invoice', |
|
567 |
N_('Message on top of an invoice'), |
|
568 |
category = N_('Invoices')) |
|
569 | ||
570 |
EmailsDirectory.register('payment-new-invoice-email', |
|
571 |
N_('New invoice'), |
|
572 |
N_('Available variables: user, regie, invoice, invoice_url'), |
|
573 |
category = N_('Invoices'), |
|
574 |
default_subject = N_('New invoice'), |
|
575 |
default_body = N_(''' |
|
576 |
A new invoice is available at [invoice_url]. |
|
577 |
''')) |
|
578 | ||
579 |
EmailsDirectory.register('payment-invoice-paid-email', |
|
580 |
N_('Paid invoice'), |
|
581 |
N_('Available variables: user, regie, invoice, invoice_url'), |
|
582 |
category = N_('Invoices'), |
|
583 |
default_subject = N_('Paid invoice'), |
|
584 |
default_body = N_(''' |
|
585 |
The invoice [invoice_url] has been paid. |
|
586 |
''')) |
|
587 | ||
588 |
EmailsDirectory.register('payment-invoice-canceled-email', |
|
589 |
N_('Canceled invoice'), |
|
590 |
N_('Available variables: user, regie, invoice, invoice_url'), |
|
591 |
category = N_('Invoices'), |
|
592 |
default_subject = N_('Canceled invoice'), |
|
593 |
default_body = N_(''' |
|
594 |
The invoice [invoice.id] has been canceled. |
|
595 |
''')) |
extra/modules/root.ptl | ||
---|---|---|
1 |
from quixote import get_publisher, get_response, get_request, redirect, get_session |
|
2 |
from quixote.directory import Directory |
|
3 |
from quixote.html import htmltext |
|
4 |
from quixote.util import StaticDirectory |
|
5 | ||
6 |
from wcs.qommon.misc import get_variadic_url |
|
7 | ||
8 |
import os |
|
9 |
import re |
|
10 |
import string |
|
11 |
import urlparse |
|
12 | ||
13 |
try: |
|
14 |
import lasso |
|
15 |
except ImportError: |
|
16 |
pass |
|
17 | ||
18 |
import wcs |
|
19 |
import wcs.root |
|
20 |
import qommon |
|
21 |
from qommon import get_cfg, get_logger |
|
22 |
from qommon import template |
|
23 |
from qommon import errors |
|
24 |
from qommon.form import * |
|
25 |
from qommon import logger |
|
26 |
from wcs.roles import logged_users_role |
|
27 | ||
28 |
from qommon import emails |
|
29 |
from qommon.sms import SMS |
|
30 |
from wcs.categories import Category |
|
31 |
from wcs.formdef import FormDef |
|
32 |
from qommon.tokens import Token |
|
33 |
from qommon.admin.emails import EmailsDirectory |
|
34 |
from qommon.admin.texts import TextsDirectory |
|
35 | ||
36 |
from links import Link |
|
37 |
from announces import Announce, AnnounceSubscription |
|
38 |
from myspace import MyspaceDirectory |
|
39 |
from agenda import AgendaDirectory |
|
40 |
from events import Event, get_default_event_tags |
|
41 |
from payments import PublicPaymentDirectory |
|
42 |
from payments_ui import InvoicesDirectory |
|
43 | ||
44 |
import admin |
|
45 | ||
46 |
import wcs.forms.root |
|
47 |
from wcs.workflows import Workflow |
|
48 | ||
49 |
from saml2 import Saml2Directory |
|
50 | ||
51 |
OldRootDirectory = wcs.root.RootDirectory |
|
52 | ||
53 |
import qommon.ident.password |
|
54 |
import qommon.ident.idp |
|
55 | ||
56 |
import drupal |
|
57 |
import ezldap_ui |
|
58 |
import msp_ui |
|
59 | ||
60 |
def category_get_homepage_position(self): |
|
61 |
if hasattr(self, 'homepage_position') and self.homepage_position: |
|
62 |
return self.homepage_position |
|
63 |
if self.url_name == 'consultations': |
|
64 |
return '2nd' |
|
65 |
return '1st' |
|
66 |
Category.get_homepage_position = category_get_homepage_position |
|
67 | ||
68 |
def category_get_limit(self): |
|
69 |
if hasattr(self, 'limit') and self.limit is not None: |
|
70 |
return self.limit |
|
71 |
return 3 |
|
72 |
Category.get_limit = category_get_limit |
|
73 | ||
74 | ||
75 |
class FormsRootDirectory(wcs.forms.root.RootDirectory): |
|
76 | ||
77 |
def _q_index(self, *args): |
|
78 |
get_response().filter['is_index'] = True |
|
79 |
return wcs.forms.root.RootDirectory._q_index(self, *args) |
|
80 | ||
81 |
def user_forms [html] (self, user_forms): |
|
82 |
base_url = get_publisher().get_root_url() |
|
83 | ||
84 |
draft = [x for x in user_forms if x.is_draft()] |
|
85 |
if draft: |
|
86 |
'<h4 id="drafts">%s</h4>' % _('My Current Drafts') |
|
87 |
'<ul>' |
|
88 |
for f in draft: |
|
89 |
'<li><a href="%s%s/%s/%s">%s</a>, %s</li>' % (base_url, |
|
90 |
f.formdef.category.url_name, |
|
91 |
f.formdef.url_name, f.id, f.formdef.name, |
|
92 |
misc.localstrftime(f.receipt_time)) |
|
93 |
'</ul>' |
|
94 | ||
95 |
forms_by_status_name = {} |
|
96 |
for f in user_forms: |
|
97 |
if f.is_draft(): |
|
98 |
continue |
|
99 |
status = f.get_visible_status() |
|
100 |
if status: |
|
101 |
status_name = status.name |
|
102 |
else: |
|
103 |
status_name = None |
|
104 |
if status_name in forms_by_status_name: |
|
105 |
forms_by_status_name[status_name].append(f) |
|
106 |
else: |
|
107 |
forms_by_status_name[status_name] = [f] |
|
108 |
for status_name in forms_by_status_name: |
|
109 |
if status_name: |
|
110 |
'<h4>%s</h4>' % _('My forms with status "%s"') % status_name |
|
111 |
else: |
|
112 |
'<h4>%s</h4>' % _('My forms with an unknown status') % status_name |
|
113 |
'<ul>' |
|
114 |
forms_by_status_name[status_name].sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) |
|
115 |
for f in forms_by_status_name[status_name]: |
|
116 |
if f.formdef.category_id: |
|
117 |
category_url = f.formdef.category.url_name |
|
118 |
else: |
|
119 |
category_url = '.' |
|
120 |
'<li><a href="%s%s/%s/%s/">%s</a>, %s</li>' % ( |
|
121 |
base_url, |
|
122 |
category_url, |
|
123 |
f.formdef.url_name, f.id, f.formdef.name, |
|
124 |
misc.localstrftime(f.receipt_time)) |
|
125 |
'</ul>' |
|
126 | ||
127 | ||
128 |
class AnnounceDirectory(Directory): |
|
129 |
_q_exports = ['', 'edit', 'delete', 'email'] |
|
130 | ||
131 |
def __init__(self, announce): |
|
132 |
self.announce = announce |
|
133 | ||
134 |
def _q_index [html] (self): |
|
135 |
template.html_top(_('Announces to citizens')) |
|
136 | ||
137 |
if self.announce.publication_time: |
|
138 |
date_heading = '%s - ' % time.strftime(misc.date_format(), self.announce.publication_time) |
|
139 |
else: |
|
140 |
date_heading = '' |
|
141 | ||
142 |
'<h3>%s%s</h3>' % (date_heading, self.announce.title) |
|
143 | ||
144 |
'<p>' |
|
145 |
self.announce.text |
|
146 |
'</p>' |
|
147 | ||
148 |
'<p>' |
|
149 |
'<a href="../">%s</a>' % _('Back') |
|
150 |
'</p>' |
|
151 | ||
152 | ||
153 |
class AnnouncesDirectory(Directory): |
|
154 |
_q_exports = ['', 'subscribe', 'email', 'atom', 'sms', 'emailconfirm', |
|
155 |
'email_unsubscribe', 'sms_unsubscribe', 'smsconfirm', 'rawlist'] |
|
156 | ||
157 |
|
|
158 |
def _q_traverse(self, path): |
|
159 |
get_response().breadcrumb.append(('announces/', _('Announces'))) |
|
160 |
return Directory._q_traverse(self, path) |
|
161 | ||
162 |
def _q_index [html] (self): |
|
163 |
template.html_top(_('Announces to citizens')) |
|
164 |
self.announces_list() |
|
165 |
'<ul id="announces-links">' |
|
166 |
'<li><a href="subscribe">%s</a></li>' % _('Receiving those Announces') |
|
167 |
'</ul>' |
|
168 | ||
169 |
def _get_announce_subscription(self): |
|
170 |
""" """ |
|
171 |
sub = None |
|
172 |
if get_request().user: |
|
173 |
subs = AnnounceSubscription.select(lambda x: x.user_id == get_request().user.id) |
|
174 |
if subs: |
|
175 |
sub = subs[0] |
|
176 |
return sub |
|
177 | ||
178 |
def rawlist [html] (self): |
|
179 |
self.announces_list() |
|
180 |
get_response().filter = None |
|
181 | ||
182 |
def announces_list [html] (self): |
|
183 |
announces = Announce.get_published_announces() |
|
184 |
if not announces: |
|
185 |
raise errors.TraversalError() |
|
186 | ||
187 |
# XXX: will need pagination someday |
|
188 |
for item in announces: |
|
189 |
'<div class="announce-item">\n' |
|
190 |
'<h4>' |
|
191 |
if item.publication_time: |
|
192 |
time.strftime(misc.date_format(), item.publication_time) |
|
193 |
' - ' |
|
194 |
item.title |
|
195 |
'</h4>\n' |
|
196 |
'<p>\n' |
|
197 |
item.text |
|
198 |
'\n</p>\n' |
|
199 |
'</div>\n' |
|
200 | ||
201 | ||
202 |
def sms [html] (self): |
|
203 |
sms_mode = get_cfg('sms', {}).get('mode', 'none') |
|
204 | ||
205 |
if sms_mode == 'none': |
|
206 |
raise errors.TraversalError() |
|
207 | ||
208 |
get_response().breadcrumb.append(('sms', _('SMS'))) |
|
209 |
template.html_top(_('Receiving announces by SMS')) |
|
210 | ||
211 |
if sms_mode == 'demo': |
|
212 |
TextsDirectory.get_html_text('aq-sms-demo') |
|
213 |
else: |
|
214 |
announces_cfg = get_cfg('announces',{}) |
|
215 |
mobile_mask = announces_cfg.get('mobile_mask') |
|
216 |
if mobile_mask: |
|
217 |
mobile_mask = ' (' + mobile_mask + ')' |
|
218 |
else: |
|
219 |
mobile_mask = '' |
|
220 |
form = Form(enctype='multipart/form-data') |
|
221 |
form.add(StringWidget, 'mobile', title = _('Mobile number %s') % mobile_mask, size=12, required=True) |
|
222 |
form.add_submit('submit', _('Subscribe')) |
|
223 |
form.add_submit('cancel', _('Cancel')) |
|
224 | ||
225 |
if form.get_submit() == 'cancel': |
|
226 |
return redirect('subscribe') |
|
227 | ||
228 |
if form.is_submitted() and not form.has_errors(): |
|
229 |
s = self.sms_submit(form) |
|
230 |
if s == False: |
|
231 |
form.render() |
|
232 |
else: |
|
233 |
return redirect("smsconfirm") |
|
234 |
else: |
|
235 |
form.render() |
|
236 | ||
237 |
def sms_submit(self, form): |
|
238 |
mobile = form.get_widget("mobile").parse() |
|
239 |
# clean the string, remove any extra character |
|
240 |
mobile = re.sub('[^0-9+]','',mobile) |
|
241 |
# if a mask was set, validate |
|
242 |
announces_cfg = get_cfg('announces',{}) |
|
243 |
mobile_mask = announces_cfg.get('mobile_mask') |
|
244 |
if mobile_mask: |
|
245 |
mobile_regexp = re.sub('X','[0-9]', mobile_mask) + '$' |
|
246 |
if not re.match(mobile_regexp, mobile): |
|
247 |
form.set_error("mobile", _("Phone number invalid ! It must match ") + mobile_mask) |
|
248 |
return False |
|
249 |
if mobile.startswith('00'): |
|
250 |
mobile = '+' + mobile[2:] |
|
251 |
else: |
|
252 |
# Default to france international prefix |
|
253 |
if not mobile.startswith('+'): |
|
254 |
mobile = re.sub("^0", "+33", mobile) |
|
255 |
sub = self._get_announce_subscription() |
|
256 |
if not sub: |
|
257 |
sub = AnnounceSubscription() |
|
258 |
if get_request().user: |
|
259 |
sub.user_id = get_request().user.id |
|
260 | ||
261 |
if mobile: |
|
262 |
sub.sms = mobile |
|
263 | ||
264 |
if not get_request().user: |
|
265 |
sub.enabled = False |
|
266 | ||
267 |
sub.store() |
|
268 | ||
269 |
# Asking sms confirmation |
|
270 |
token = Token(3 * 86400, 4, string.digits) |
|
271 |
token.type = 'announces-subscription-confirmation' |
|
272 |
token.subscription_id = sub.id |
|
273 |
token.store() |
|
274 | ||
275 |
message = _("Confirmation code : %s") % str(token.id) |
|
276 |
sms_cfg = get_cfg('sms', {}) |
|
277 |
sender = sms_cfg.get('sender', 'AuQuotidien')[:11] |
|
278 |
mode = sms_cfg.get('mode', 'none') |
|
279 |
sms = SMS.get_sms_class(mode) |
|
280 |
try: |
|
281 |
sms.send(sender, [mobile], message) |
|
282 |
except errors.SMSError, e: |
|
283 |
get_logger().error(e) |
|
284 |
form.set_error("mobile", _("Send SMS confirmation failed")) |
|
285 |
sub.remove("sms") |
|
286 |
return False |
|
287 | ||
288 |
def smsconfirm [html] (self): |
|
289 |
template.html_top(_('Receiving announces by SMS confirmation')) |
|
290 |
"<p>%s</p>" % _("You will receive a confirmation code by SMS.") |
|
291 |
form = Form(enctype='multipart/form-data') |
|
292 |
form.add(StringWidget, 'code', title = _('Confirmation code (4 characters)'), size=12, required=True) |
|
293 |
form.add_submit('submit', _('Subscribe')) |
|
294 |
form.add_submit('cancel', _('Cancel')) |
|
295 | ||
296 |
if form.get_submit() == 'cancel': |
|
297 |
return redirect('..') |
|
298 | ||
299 |
if form.is_submitted() and not form.has_errors(): |
|
300 |
token = None |
|
301 |
id = form.get_widget("code").parse() |
|
302 |
try: |
|
303 |
token = Token.get(id) |
|
304 |
except KeyError: |
|
305 |
form.set_error("code", _('Invalid confirmation code.')) |
|
306 |
else: |
|
307 |
if token.type != 'announces-subscription-confirmation': |
|
308 |
form.set_error("code", _('Invalid confirmation code.')) |
|
309 |
else: |
|
310 |
sub = AnnounceSubscription.get(token.subscription_id) |
|
311 |
token.remove_self() |
|
312 |
sub.enabled_sms = True |
|
313 |
sub.store() |
|
314 |
return redirect('.') |
|
315 |
form.render() |
|
316 |
else: |
|
317 |
form.render() |
|
318 | ||
319 |
def sms_unsubscribe [html] (self): |
|
320 |
sub = self._get_announce_subscription() |
|
321 | ||
322 |
form = Form(enctype='multipart/form-data') |
|
323 |
if not sub: |
|
324 |
return redirect('..') |
|
325 | ||
326 |
form.add_submit('submit', _('Unsubscribe')) |
|
327 |
form.add_submit('cancel', _('Cancel')) |
|
328 | ||
329 |
if form.get_submit() == 'cancel': |
|
330 |
return redirect('..') |
|
331 | ||
332 |
get_response().breadcrumb.append(('sms', _('SMS Unsubscription'))) |
|
333 |
template.html_top() |
|
334 | ||
335 |
if form.is_submitted() and not form.has_errors(): |
|
336 |
if sub: |
|
337 |
sub.remove("sms") |
|
338 | ||
339 |
def sms_unsub_ok [html] (): |
|
340 |
root_url = get_publisher().get_root_url() |
|
341 |
'<p>' |
|
342 |
_('You have been unsubscribed from announces') |
|
343 |
'</p>' |
|
344 |
if not get_response().iframe_mode: |
|
345 |
'<a href="%s">%s</a>' % (root_url, _('Back Home')) |
|
346 | ||
347 |
return sms_unsub_ok() |
|
348 | ||
349 |
else: |
|
350 |
'<p>' |
|
351 |
_('Do you want to stop receiving announces by sms ?') |
|
352 |
'</p>' |
|
353 |
form.render() |
|
354 | ||
355 | ||
356 |
def subscribe [html] (self): |
|
357 |
get_response().breadcrumb.append(('subscribe', _('Subscription'))) |
|
358 |
template.html_top(_('Receiving Announces')) |
|
359 | ||
360 |
TextsDirectory.get_html_text('aq-announces-subscription') |
|
361 | ||
362 |
sub = self._get_announce_subscription() |
|
363 | ||
364 |
'<ul id="announce-modes">' |
|
365 |
if sub and sub.email: |
|
366 |
' <li>' |
|
367 |
'<span id="par_mail">%s</span>' % _('Email (currently subscribed)') |
|
368 |
' <a href="email_unsubscribe" rel="popup">%s</a></li>' % _('Unsubscribe') |
|
369 |
else: |
|
370 |
' <li><a href="email" id="par_mail" rel="popup">%s</a></li>' % _('Email') |
|
371 |
if sub and sub.sms: |
|
372 |
' <li>' |
|
373 |
if sub.enabled_sms: |
|
374 |
'<span id="par_sms">%s</span>' % _('SMS %s (currently subscribed)') % sub.sms |
|
375 |
else: |
|
376 |
'<span id="par_sms">%s</span>' % _('SMS %s (currently not confirmed)') % sub.sms |
|
377 |
' <a href="smsconfirm" rel="popup">%s</a> ' % _('Confirmation') |
|
378 |
' <a href="sms_unsubscribe" rel="popup">%s</a></li>' % _('Unsubscribe') |
|
379 |
elif get_cfg('sms', {}).get('mode', 'none') != 'none': |
|
380 |
' <li><a href="sms" id="par_sms">%s</a></li>' % _('SMS') |
|
381 |
' <li><a class="feed-link" href="atom" id="par_rss">%s</a>' % _('Feed') |
|
382 |
'</ul>' |
|
383 | ||
384 | ||
385 |
def email [html] (self): |
|
386 |
get_response().breadcrumb.append(('email', _('Email Subscription'))) |
|
387 |
template.html_top(_('Receiving Announces by email')) |
|
388 | ||
389 |
form = Form(enctype='multipart/form-data') |
|
390 |
if get_request().user: |
|
391 |
if get_request().user.email: |
|
392 |
'<p>' |
|
393 |
_('You are logged in and your email is %s, ok to subscribe ?') % \ |
|
394 |
get_request().user.email |
|
395 |
'</p>' |
|
396 |
form.add_submit('submit', _('Subscribe')) |
|
397 |
else: |
|
398 |
'<p>' |
|
399 |
_("You are logged in but there is no email address in your profile.") |
|
400 |
'</p>' |
|
401 |
form.add(EmailWidget, 'email', title = _('Email'), required = True) |
|
402 |
form.add_submit('submit', _('Subscribe')) |
|
403 |
form.add_submit('submit-remember', _('Subscribe and add this email to my profile')) |
|
404 |
else: |
|
405 |
'<p>' |
|
406 |
_('FIXME will only be used for this purpose etc.') |
|
407 |
'</p>' |
|
408 |
form.add(EmailWidget, 'email', title = _('Email'), required = True) |
|
409 |
form.add_submit('submit', _('Subscribe')) |
|
410 |
|
|
411 |
form.add_submit('cancel', _('Cancel')) |
|
412 | ||
413 |
if form.get_submit() == 'cancel': |
|
414 |
return redirect('subscribe') |
|
415 | ||
416 |
if form.is_submitted() and not form.has_errors(): |
|
417 |
s = self.email_submit(form) |
|
418 |
if s is not False: |
|
419 |
return s |
|
420 |
else: |
|
421 |
form.render() |
|
422 | ||
423 |
def email_submit(self, form): |
|
424 |
sub = self._get_announce_subscription() |
|
425 |
if not sub: |
|
426 |
sub = AnnounceSubscription() |
|
427 | ||
428 |
if get_request().user: |
|
429 |
sub.user_id = get_request().user.id |
|
430 | ||
431 |
if form.get_widget('email'): |
|
432 |
sub.email = form.get_widget('email').parse() |
|
433 |
elif get_request().user.email: |
|
434 |
sub.email = get_request().user.email |
|
435 | ||
436 |
if not get_request().user: |
|
437 |
sub.enabled = False |
|
438 | ||
439 |
sub.store() |
|
440 | ||
441 |
if get_request().user: |
|
442 |
def email_submit_ok [html] (): |
|
443 |
root_url = get_publisher().get_root_url() |
|
444 |
'<p>' |
|
445 |
_('You have been subscribed to the announces.') |
|
446 |
'</p>' |
|
447 |
if not get_response().iframe_mode: |
|
448 |
'<a href="%s">%s</a>' % (root_url, _('Back Home')) |
|
449 | ||
450 |
return email_submit_ok() |
|
451 | ||
452 |
# asking email confirmation before subscribing someone |
|
453 |
token = Token(3 * 86400) |
|
454 |
token.type = 'announces-subscription-confirmation' |
|
455 |
token.subscription_id = sub.id |
|
456 |
token.store() |
|
457 |
data = { |
|
458 |
'confirm_url': get_request().get_url() + 'confirm?t=%s&a=cfm' % token.id, |
|
459 |
'cancel_url': get_request().get_url() + 'confirm?t=%s&a=cxl' % token.id, |
|
460 |
'time': misc.localstrftime(time.localtime(token.expiration)), |
|
461 |
} |
|
462 | ||
463 |
emails.custom_ezt_email('announces-subscription-confirmation', |
|
464 |
data, sub.email, exclude_current_user = False) |
|
465 | ||
466 |
def email_submit_ok [html] (): |
|
467 |
root_url = get_publisher().get_root_url() |
|
468 |
'<p>' |
|
469 |
_('You have been sent an email for confirmation') |
|
470 |
'</p>' |
|
471 |
if not get_response().iframe_mode: |
|
472 |
'<a href="%s">%s</a>' % (root_url, _('Back Home')) |
|
473 | ||
474 |
return email_submit_ok() |
|
475 | ||
476 |
def emailconfirm(self): |
|
477 |
tokenv = get_request().form.get('t') |
|
478 |
action = get_request().form.get('a') |
|
479 | ||
480 |
root_url = get_publisher().get_root_url() |
|
481 | ||
482 |
try: |
|
483 |
token = Token.get(tokenv) |
|
484 |
except KeyError: |
|
485 |
return template.error_page( |
|
486 |
_('The token you submitted does not exist, has expired, or has been cancelled.'), |
|
487 |
continue_to = (root_url, _('home page'))) |
|
488 | ||
489 |
if token.type != 'announces-subscription-confirmation': |
|
490 |
return template.error_page( |
|
491 |
_('The token you submitted is not appropriate for the requested task.'), |
|
492 |
continue_to = (root_url, _('home page'))) |
|
493 | ||
494 |
sub = AnnounceSubscription.get(token.subscription_id) |
|
495 | ||
496 |
if action == 'cxl': |
|
497 |
def cancel [html](): |
|
498 |
root_url = get_publisher().get_root_url() |
|
499 |
template.html_top(_('Email Subscription')) |
|
500 |
'<h1>%s</h1>' % _('Request Cancelled') |
|
501 |
'<p>%s</p>' % _('The request for subscription has been cancelled.') |
|
502 |
'<p>' |
|
503 |
htmltext(_('Continue to <a href="%s">home page</a>') % root_url) |
|
504 |
'</p>' |
|
505 |
token.remove_self() |
|
506 |
sub.remove_self() |
|
507 |
return cancel() |
|
508 | ||
509 |
if action == 'cfm': |
|
510 |
token.remove_self() |
|
511 |
sub.enabled = True |
|
512 |
sub.store() |
|
513 |
def sub [html] (): |
|
514 |
root_url = get_publisher().get_root_url() |
|
515 |
template.html_top(_('Email Subscription')) |
|
516 |
'<h1>%s</h1>' % _('Subscription Confirmation') |
|
517 |
'<p>%s</p>' % _('Your subscription to announces is now effective.') |
|
518 |
'<p>' |
|
519 |
htmltext(_('Continue to <a href="%s">home page</a>') % root_url) |
|
520 |
'</p>' |
|
521 |
return sub() |
|
522 | ||
523 | ||
524 |
def atom [plain] (self): |
|
525 |
response = get_response() |
|
526 |
response.set_content_type('application/atom+xml') |
|
527 | ||
528 |
from pyatom import pyatom |
|
529 |
xmldoc = pyatom.XMLDoc() |
|
530 |
feed = pyatom.Feed() |
|
531 |
xmldoc.root_element = feed |
|
532 |
feed.title = get_cfg('misc', {}).get('sitename') or 'Au Quotidien' |
|
533 |
feed.id = get_request().get_url() |
|
534 | ||
535 |
author_email = get_cfg('emails', {}).get('reply_to') |
|
536 |
if not author_email: |
|
537 |
author_email = get_cfg('emails', {}).get('from') |
|
538 |
if author_email: |
|
539 |
feed.authors.append(pyatom.Author(author_email)) |
|
540 | ||
541 |
announces = Announce.get_published_announces() |
|
542 | ||
543 |
if announces and announces[0].modification_time: |
|
544 |
feed.updated = misc.format_time(announces[0].modification_time, |
|
545 |
'%(year)s-%(month)02d-%(day)02dT%(hour)02d:%(minute)02d:%(second)02dZ', |
|
546 |
gmtime = True) |
|
547 |
feed.links.append(pyatom.Link(get_request().get_url(1) + '/')) |
|
548 | ||
549 |
for item in announces: |
|
550 |
entry = item.get_atom_entry() |
|
551 |
if entry: |
|
552 |
feed.entries.append(entry) |
|
553 | ||
554 |
str(feed) |
|
555 | ||
556 |
def email_unsubscribe [html] (self): |
|
557 |
sub = self._get_announce_subscription() |
|
558 | ||
559 |
form = Form(enctype='multipart/form-data') |
|
560 |
if not sub: |
|
561 |
form.add(EmailWidget, 'email', title = _('Email'), required = True) |
|
562 | ||
563 |
form.add_submit('submit', _('Unsubscribe')) |
|
564 |
form.add_submit('cancel', _('Cancel')) |
|
565 | ||
566 |
if form.get_submit() == 'cancel': |
|
567 |
return redirect('..') |
|
568 | ||
569 |
get_response().breadcrumb.append(('email', _('Email Unsubscription'))) |
|
570 |
template.html_top() |
|
571 | ||
572 |
if form.is_submitted() and not form.has_errors(): |
|
573 |
if sub: |
|
574 |
sub.remove("email") |
|
575 |
else: |
|
576 |
email = form.get_widget('email').parse() |
|
577 |
for s in AnnounceSubscription.select(): |
|
578 |
if s.email == email: |
|
579 |
s.remove("email") |
|
580 | ||
581 |
def email_unsub_ok [html] (): |
|
582 |
root_url = get_publisher().get_root_url() |
|
583 |
'<p>' |
|
584 |
_('You have been unsubscribed from announces') |
|
585 |
'</p>' |
|
586 |
if not get_response().iframe_mode: |
|
587 |
'<a href="%s">%s</a>' % (root_url, _('Back Home')) |
|
588 | ||
589 |
return email_unsub_ok() |
|
590 | ||
591 |
else: |
|
592 |
'<p>' |
|
593 |
_('Do you want to stop receiving announces by email?') |
|
594 |
'</p>' |
|
595 |
form.render() |
|
596 | ||
597 |
def _q_lookup(self, component): |
|
598 |
try: |
|
599 |
announce = Announce.get(component) |
|
600 |
except KeyError: |
|
601 |
raise errors.TraversalError() |
|
602 | ||
603 |
if announce.hidden: |
|
604 |
raise errors.TraversalError() |
|
605 | ||
606 |
get_response().breadcrumb.append((str(announce.id), announce.title)) |
|
607 |
return AnnounceDirectory(announce) |
|
608 | ||
609 |
OldRegisterDirectory = wcs.root.RegisterDirectory |
|
610 | ||
611 |
class AlternateRegisterDirectory(OldRegisterDirectory): |
|
612 |
def _q_traverse(self, path): |
|
613 |
get_response().filter['bigdiv'] = 'new_member' |
|
614 |
return OldRegisterDirectory._q_traverse(self, path) |
|
615 | ||
616 |
def _q_index [html] (self): |
|
617 |
get_logger().info('register') |
|
618 |
ident_methods = get_cfg('identification', {}).get('methods', []) |
|
619 | ||
620 |
if len(ident_methods) == 0: |
|
621 |
idps = get_cfg('idp', {}) |
|
622 |
if len(idps) == 0: |
|
623 |
return template.error_page(_('Authentication subsystem is not yet configured.')) |
|
624 |
ident_methods = ['idp'] # fallback to old behaviour; liberty. |
|
625 | ||
626 |
if len(ident_methods) == 1: |
|
627 |
method = ident_methods[0] |
|
628 |
else: |
|
629 |
method = 'password' |
|
630 | ||
631 |
return qommon.ident.register(method) |
|
632 | ||
633 |
OldLoginDirectory = wcs.root.LoginDirectory |
|
634 | ||
635 |
class AlternateLoginDirectory(OldLoginDirectory): |
|
636 |
def _q_traverse(self, path): |
|
637 |
get_response().filter['bigdiv'] = 'member' |
|
638 |
return OldLoginDirectory._q_traverse(self, path) |
|
639 | ||
640 |
def _q_index [html] (self): |
|
641 |
get_logger().info('login') |
|
642 |
ident_methods = get_cfg('identification', {}).get('methods', []) |
|
643 | ||
644 |
if len(ident_methods) > 1 and 'idp' in ident_methods: |
|
645 |
# if there is more than one identification method, and there is a |
|
646 |
# possibility of SSO, if we got there as a consequence of an access |
|
647 |
# unauthorized url on admin/ or backoffice/, then idp auth method |
|
648 |
# is chosen forcefully. |
|
649 |
after_url = get_session().after_url |
|
650 |
if after_url: |
|
651 |
root_url = get_publisher().get_root_url() |
|
652 |
after_path = urlparse.urlparse(after_url)[2] |
|
653 |
after_path = after_path[len(root_url):] |
|
654 |
if after_path.startswith(str('admin')) or \ |
|
655 |
after_path.startswith(str('backoffice')): |
|
656 |
ident_methods = ['idp'] |
|
657 | ||
658 |
# don't display authentication system choice |
|
659 |
if len(ident_methods) == 1: |
|
660 |
method = ident_methods[0] |
|
661 |
try: |
|
662 |
return qommon.ident.login(method) |
|
663 |
except KeyError: |
|
664 |
get_logger().error('failed to login with method %s' % method) |
|
665 |
return errors.TraversalError() |
|
666 | ||
667 |
if sorted(ident_methods) == ['idp', 'password']: |
|
668 |
get_response().breadcrumb.append(('login', _('Login'))) |
|
669 |
identities_cfg = get_cfg('identities', {}) |
|
670 |
form = Form(enctype = 'multipart/form-data', id = 'login-form', use_tokens = False) |
|
671 |
if identities_cfg.get('email-as-username', False): |
|
672 |
form.add(StringWidget, 'username', title = _('Email'), size=25, required=True) |
|
673 |
else: |
|
674 |
form.add(StringWidget, 'username', title = _('Username'), size=25, required=True) |
|
675 |
form.add(PasswordWidget, 'password', title = _('Password'), size=25, required=True) |
|
676 |
form.add_submit('submit', _('Connect')) |
|
677 |
if form.is_submitted() and not form.has_errors(): |
|
678 |
tmp = qommon.ident.password.MethodDirectory().login_submit(form) |
|
679 |
if not form.has_errors(): |
|
680 |
return tmp |
|
681 | ||
682 |
'<div id="login-password">' |
|
683 |
get_session().display_message() |
|
684 |
form.render() |
|
685 | ||
686 |
base_url = get_publisher().get_root_url() |
|
687 |
'<p><a href="%sident/password/forgotten">%s</a></p>' % ( |
|
688 |
base_url, _('Forgotten password ?')) |
|
689 | ||
690 |
'</div>' |
|
691 | ||
692 |
# XXX: this part only supports a single IdP |
|
693 |
'<div id="login-sso">' |
|
694 |
TextsDirectory.get_html_text('aq-sso-text') |
|
695 |
form = Form(enctype='multipart/form-data', |
|
696 |
action = '%sident/idp/login' % base_url) |
|
697 |
form.add_hidden('method', 'idp') |
|
698 |
for kidp, idp in get_cfg('idp', {}).items(): |
|
699 |
p = lasso.Provider(lasso.PROVIDER_ROLE_IDP, |
|
700 |
misc.get_abs_path(idp['metadata']), |
|
701 |
misc.get_abs_path(idp.get('publickey')), None) |
|
702 |
form.add_hidden('idp', p.providerId) |
|
703 |
break |
|
704 |
form.add_submit('submit', _('Connect')) |
|
705 |
|
|
706 |
form.render() |
|
707 |
'</div>' |
|
708 | ||
709 |
get_request().environ['REQUEST_METHOD'] = 'GET' |
|
710 | ||
711 |
"""<script type="text/javascript"> |
|
712 |
document.getElementById('login-form')['username'].focus(); |
|
713 |
</script>""" |
|
714 | ||
715 |
else: |
|
716 |
return OldLoginDirectory._q_index(self) |
|
717 | ||
718 | ||
719 |
OldIdentDirectory = wcs.root.IdentDirectory |
|
720 |
class AlternateIdentDirectory(OldIdentDirectory): |
|
721 |
def _q_traverse(self, path): |
|
722 |
get_response().filter['bigdiv'] = 'member' |
|
723 |
return OldIdentDirectory._q_traverse(self, path) |
|
724 | ||
725 | ||
726 |
class AlternateRootDirectory(OldRootDirectory): |
|
727 |
_q_exports = ['', 'admin', 'backoffice', 'forms', 'login', 'logout', |
|
728 |
'liberty', 'token', 'saml', 'register', 'ident', 'afterjobs', |
|
729 |
('informations-editeur', 'informations_editeur'), 'index2', |
|
730 |
('announces', 'announces_dir'), |
|
731 |
'accessibility', 'contact', 'help', |
|
732 |
'myspace', 'services', 'agenda', 'categories', 'user', |
|
733 |
('tmp-upload', 'tmp_upload'), 'json', '__version__', |
|
734 |
'themes', 'pages', 'payment', 'invoices', 'accesscode', 'roles', |
|
735 |
'msp'] |
|
736 | ||
737 |
admin = admin.AdminRootDirectory() |
|
738 |
announces_dir = AnnouncesDirectory() |
|
739 |
register = AlternateRegisterDirectory() |
|
740 |
login = AlternateLoginDirectory() |
|
741 |
ident = AlternateIdentDirectory() |
|
742 |
myspace = MyspaceDirectory() |
|
743 |
agenda = AgendaDirectory() |
|
744 |
saml = Saml2Directory() |
|
745 |
payment = PublicPaymentDirectory() |
|
746 |
invoices = InvoicesDirectory() |
|
747 |
msp = msp_ui.MSPDirectory() |
|
748 | ||
749 |
def get_substitution_variables(self): |
|
750 |
d = {} |
|
751 |
def print_links(fd): |
|
752 |
fd.write(str(self.links())) |
|
753 |
d['links'] = print_links |
|
754 |
return d |
|
755 | ||
756 |
def _q_traverse(self, path): |
|
757 |
if get_publisher().has_site_option('drupal'): |
|
758 |
drupal.try_auth() |
|
759 |
if get_publisher().has_site_option('ezldap'): |
|
760 |
ezldap_ui.try_auth(self) |
|
761 | ||
762 |
session = get_session() |
|
763 |
if session: |
|
764 |
get_request().user = session.get_user() |
|
765 |
else: |
|
766 |
get_request().user = None |
|
767 | ||
768 |
get_publisher().substitutions.feed(get_request().user) |
|
769 | ||
770 |
response = get_response() |
|
771 |
if not hasattr(response, 'filter'): |
|
772 |
response.filter = {} |
|
773 | ||
774 |
response.filter['gauche'] = self.box_side(path) |
|
775 |
response.filter['keywords'] = template.get_current_theme().get('keywords') |
|
776 |
get_publisher().substitutions.feed(self) |
|
777 | ||
778 |
response.breadcrumb = [ ('', _('Home')) ] |
|
779 | ||
780 |
if not self.admin: |
|
781 |
self.admin = get_publisher().admin_directory_class() |
|
782 | ||
783 |
if not self.backoffice: |
|
784 |
self.backoffice = get_publisher().backoffice_directory_class() |
|
785 | ||
786 |
try: |
|
787 |
return Directory._q_traverse(self, path) |
|
788 |
except errors.TraversalError, e: |
|
789 |
try: |
|
790 |
f = FormDef.get_by_urlname(path[0]) |
|
791 |
except KeyError: |
|
792 |
pass |
|
793 |
else: |
|
794 |
base_url = get_publisher().get_root_url() |
|
795 | ||
796 |
uri_rest = get_request().environ.get('REQUEST_URI') |
|
797 |
if not uri_rest: |
|
798 |
uri_rest = get_request().get_path() |
|
799 |
if uri_rest.startswith(base_url): |
|
800 |
uri_rest = uri_rest[len(base_url):] |
|
801 |
elif uri_rest.startswith('/'): |
|
802 |
# dirty hack, ezldap reverseproxy uses a fake base_url |
|
803 |
uri_rest = uri_rest[1:] |
|
804 |
if f.category_id: |
|
805 |
return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest)) |
|
806 | ||
807 |
raise e |
|
808 | ||
809 | ||
810 |
def _q_lookup(self, component): |
|
811 |
if component == 'qo': |
|
812 |
dirname = os.path.join(get_publisher().data_dir, 'qommon') |
|
813 |
return StaticDirectory(dirname, follow_symlinks = True) |
|
814 | ||
815 |
if component == 'aq': |
|
816 |
dirname = os.path.join(get_publisher().data_dir, 'qommon', 'auquotidien') |
|
817 |
return StaticDirectory(dirname, follow_symlinks = True) |
|
818 | ||
819 |
if component in ('css','images'): |
|
820 |
return OldRootDirectory._q_lookup(self, component) |
|
821 | ||
822 |
# is this a category ? |
|
823 |
try: |
|
824 |
category = Category.get_by_urlname(component) |
|
825 |
except KeyError: |
|
826 |
pass |
|
827 |
else: |
|
828 |
return FormsRootDirectory(category) |
|
829 | ||
830 |
# is this a formdef ? |
|
831 |
try: |
|
832 |
formdef = FormDef.get_by_urlname(component) |
|
833 |
except KeyError: |
|
834 |
pass |
|
835 |
else: |
|
836 |
if formdef.category_id is None: |
|
837 |
get_response().filter['bigdiv'] = 'rub_service' |
|
838 |
return FormsRootDirectory()._q_lookup(component) |
|
839 |
# if there is category, let it fall back to raise TraversalError, |
|
840 |
# it will get caught in _q_traverse that will redirect it to an |
|
841 |
# URL embedding the category |
|
842 | ||
843 |
return None |
|
844 | ||
845 |
def json(self): |
|
846 |
return FormsRootDirectory().json() |
|
847 | ||
848 |
def categories(self): |
|
849 |
return FormsRootDirectory().categories() |
|
850 | ||
851 |
def _q_index [html] (self): |
|
852 |
if get_request().is_json(): |
|
853 |
return FormsRootDirectory().json() |
|
854 | ||
855 |
root_url = get_publisher().get_root_url() |
|
856 |
if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump: |
|
857 |
return redirect('%smyspace/new' % root_url) |
|
858 | ||
859 |
if get_response().iframe_mode: |
|
860 |
# never display home page in an iframe |
|
861 |
return redirect('%sservices' % root_url) |
|
862 | ||
863 |
template.html_top() |
|
864 |
get_response().filter['is_index'] = True |
|
865 | ||
866 |
if not 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []): |
|
867 |
t = TextsDirectory.get_html_text('aq-home-page') |
|
868 |
if not t: |
|
869 |
if get_request().user: |
|
870 |
t = TextsDirectory.get_html_text('welcome-logged') |
|
871 |
else: |
|
872 |
t = TextsDirectory.get_html_text('welcome-unlogged') |
|
873 |
if t: |
|
874 |
'<div id="home-page-intro">' |
|
875 |
t |
|
876 |
'</div>' |
|
877 | ||
878 |
'<div id="centre">' |
|
879 |
self.box_services(position='1st') |
|
880 |
'</div>' |
|
881 |
'<div id="droite">' |
|
882 |
self.myspace_snippet() |
|
883 |
self.box_services(position='2nd') |
|
884 |
self.consultations() |
|
885 |
self.announces() |
|
886 |
'</div>' |
|
887 | ||
888 |
user = get_request().user |
|
889 |
if user and user.can_go_in_backoffice(): |
|
890 |
get_response().filter['backoffice'] = True |
|
891 | ||
892 |
def services [html] (self): |
|
893 |
template.html_top() |
|
894 |
get_response().filter['bigdiv'] = 'rub_service' |
|
895 |
self.box_services(level = 2) |
|
896 | ||
897 |
def box_services [html] (self, level=3, position=None): |
|
898 |
## Services |
|
899 |
if get_request().user and get_request().user.roles: |
|
900 |
accepted_roles = get_request().user.roles |
|
901 |
else: |
|
902 |
accepted_roles = [] |
|
903 | ||
904 |
cats = Category.select(order_by = 'name') |
|
905 |
cats = [x for x in cats if x.url_name != 'consultations'] |
|
906 |
Category.sort_by_position(cats) |
|
907 | ||
908 |
all_formdefs = FormDef.select(lambda x: not x.is_disabled() or x.disabled_redirection, |
|
909 |
order_by = 'name') |
|
910 | ||
911 |
if position: |
|
912 |
t = self.display_list_of_formdefs( |
|
913 |
[x for x in cats if x.get_homepage_position() == position], |
|
914 |
all_formdefs, accepted_roles) |
|
915 |
else: |
|
916 |
t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles) |
|
917 | ||
918 |
if not t: |
|
919 |
return |
|
920 | ||
921 |
if position == '2nd': |
|
922 |
'<div id="services-2nd">' |
|
923 |
else: |
|
924 |
'<div id="services">' |
|
925 |
if level == 2: |
|
926 |
'<h2>%s</h2>' % _('Services') |
|
927 |
else: |
|
928 |
'<h3>%s</h3>' % _('Services') |
|
929 | ||
930 |
if get_response().iframe_mode: |
|
931 |
if get_request().user: |
|
932 |
message = TextsDirectory.get_html_text('welcome-logged') |
|
933 |
else: |
|
934 |
message = TextsDirectory.get_html_text('welcome-unlogged') |
|
935 | ||
936 |
if message: |
|
937 |
'<div id="welcome-message">' |
|
938 |
message |
|
939 |
'</div>' |
|
940 |
elif 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []): |
|
941 |
homepage_text = TextsDirectory.get_html_text('aq-home-page') |
|
942 |
if homepage_text: |
|
943 |
'<div id="home-page-intro">' |
|
944 |
homepage_text |
|
945 |
'</div>' |
|
946 | ||
947 |
'<ul>' |
|
948 |
t |
|
949 |
'</ul>' |
|
950 | ||
951 |
'</div>' |
|
952 | ||
953 |
def display_list_of_formdefs [html] (self, cats, all_formdefs, accepted_roles): |
|
954 |
for category in cats: |
|
955 |
if category.url_name == 'consultations': |
|
956 |
self.consultations_category = category |
|
957 |
continue |
|
958 |
formdefs = [x for x in all_formdefs if x.category_id == category.id] |
|
959 |
formdefs_advertise = [] |
|
960 | ||
961 |
for formdef in formdefs[:]: |
|
962 |
if formdef.is_disabled(): # is a redirection |
|
963 |
continue |
|
964 |
if not formdef.roles: |
|
965 |
continue |
|
966 |
if not get_request().user: |
|
967 |
if formdef.always_advertise: |
|
968 |
formdefs_advertise.append(formdef) |
|
969 |
formdefs.remove(formdef) |
|
970 |
continue |
|
971 |
if logged_users_role().id in formdef.roles: |
|
972 |
continue |
|
973 |
for q in accepted_roles: |
|
974 |
if q in formdef.roles: |
|
975 |
break |
|
976 |
else: |
|
977 |
if formdef.always_advertise: |
|
978 |
formdefs_advertise.append(formdef) |
|
979 |
formdefs.remove(formdef) |
|
980 | ||
981 |
if not formdefs and not formdefs_advertise: |
|
982 |
continue |
|
983 | ||
984 |
'<li>' |
|
985 |
'<strong>' |
|
986 |
'<a href="%s/">' % category.url_name |
|
987 |
category.name |
|
988 |
'</a></strong>\n' |
|
989 |
if category.description: |
|
990 |
if category.description[0] == '<': |
|
991 |
htmltext(category.description) |
|
992 |
else: |
|
993 |
'<p>' |
|
994 |
category.description |
|
995 |
'</p>' |
|
996 |
'<ul>' |
|
997 |
limit = category.get_limit() |
|
998 |
for formdef in formdefs[:limit]: |
|
999 |
'<li>' |
|
1000 |
'<a href="%s/%s/">%s</a>' % (category.url_name, formdef.url_name, formdef.name) |
|
1001 |
'</li>\n' |
|
1002 |
if len(formdefs) < limit: |
|
1003 |
for formdef in formdefs_advertise[:limit-len(formdefs)]: |
|
1004 |
'<li>' |
|
1005 |
'<a href="%s/%s/">%s</a>' % (category.url_name, formdef.url_name, formdef.name) |
|
1006 |
' (%s)' % _('authentication required') |
|
1007 |
'</li>\n' |
|
1008 |
if (len(formdefs)+len(formdefs_advertise)) > limit: |
|
1009 |
'<li class="all-forms"><a href="%s/" title="%s">%s</a></li>' % (category.url_name, |
|
1010 |
_('Access to all forms of the "%s" category') % category.name, |
|
1011 |
_('Access to all forms in this category')) |
|
1012 |
'</ul>' |
|
1013 |
'</li>\n' |
|
1014 | ||
1015 |
def consultations [html] (self): |
|
1016 |
cats = [x for x in Category.select() if x.url_name == 'consultations'] |
|
1017 |
if not cats: |
|
1018 |
return |
|
1019 |
consultations_category = cats[0] |
|
1020 |
formdefs = FormDef.select(lambda x: ( |
|
1021 |
x.category_id == consultations_category.id and |
|
1022 |
(not x.is_disabled() or x.disabled_redirection)), |
|
1023 |
order_by = 'name') |
|
1024 |
if not formdefs: |
|
1025 |
return |
|
1026 |
## Consultations |
|
1027 |
'<div id="consultations">' |
|
1028 |
'<h3>%s</h3>' % _('Consultations') |
|
1029 |
if consultations_category.description: |
|
1030 |
if consultations_category.description[0] == '<': |
|
1031 |
htmltext(consultations_category.description) |
|
1032 |
else: |
|
1033 |
'<p>' |
|
1034 |
consultations_category.description |
|
1035 |
'</p>' |
|
1036 |
'<ul>' |
|
1037 |
for formdef in formdefs: |
|
1038 |
'<li>' |
|
1039 |
'<a href="%s/%s/">%s</a>' % (consultations_category.url_name, |
|
1040 |
formdef.url_name, formdef.name) |
|
1041 |
'</li>' |
|
1042 |
'</ul>' |
|
1043 |
'</div>' |
|
1044 | ||
1045 |
def box_side [html] (self, path): |
|
1046 |
'<div id="sidebox">' |
|
1047 |
root_url = get_publisher().get_root_url() |
|
1048 | ||
1049 |
if self.has_anonymous_access_codes(): |
|
1050 |
'<form id="follow-form" action="%saccesscode">' % root_url |
|
1051 |
'<h3>%s</h3>' % _('Tracking') |
|
1052 |
'<label>%s</label> ' % _('Code:') |
|
1053 |
'<input name="code" size="10"/>' |
|
1054 |
'</form>' |
|
1055 | ||
1056 |
self.links() |
|
1057 | ||
1058 |
cats = Category.select(order_by = 'name') |
|
1059 |
cats = [x for x in cats if x.url_name != 'consultations' and x.get_homepage_position() == 'side'] |
|
1060 |
Category.sort_by_position(cats) |
|
1061 |
if cats: |
|
1062 |
'<div id="side-services">' |
|
1063 |
'<h3>%s</h3>' % _('Services') |
|
1064 |
'<ul>' |
|
1065 |
for cat in cats: |
|
1066 |
'<li><a href="%s/">%s</a></li>' % (cat.url_name, cat.name) |
|
1067 |
'</ul>' |
|
1068 |
'</div>' |
|
1069 | ||
1070 |
if Event.keys(): # if there are events, add a link to the agenda |
|
1071 |
tags = get_cfg('misc', {}).get('event_tags') |
|
1072 |
if not tags: |
|
1073 |
tags = get_default_event_tags() |
|
1074 |
'<h3 id="agenda-link"><a href="%sagenda/">%s</a></h3>' % (root_url, _('Agenda')) |
|
1075 | ||
1076 |
if path and path[0] == 'agenda': |
|
1077 |
'<p class="tags">' |
|
1078 |
for tag in tags: |
|
1079 |
'<a href="%sagenda/tag/%s">%s</a> ' % (root_url, tag, tag) |
|
1080 |
'</p>' |
|
1081 |
self.agenda.display_remote_calendars() |
|
1082 | ||
1083 |
'<p>' |
|
1084 |
' <a href="%sagenda/filter">%s</a>' % (root_url, _('Advanced Filter')) |
|
1085 |
'</p>' |
|
1086 | ||
1087 |
'</div>' |
|
1088 | ||
1089 |
def has_anonymous_access_codes(self): |
|
1090 |
for workflow in Workflow.select(): |
|
1091 |
for wfstatus in workflow.possible_status: |
|
1092 |
for wfitem in wfstatus.items: |
|
1093 |
if wfitem.key == 'create-anonymous-access-code': |
|
1094 |
return True |
|
1095 |
return False |
|
1096 | ||
1097 |
def accesscode(self): |
|
1098 |
code = get_request().form.get('code') |
|
1099 |
if not code: |
|
1100 |
return redirect(get_publisher().get_root_url()) |
|
1101 |
try: |
|
1102 |
token = Token.get(code) |
|
1103 |
except KeyError: |
|
1104 |
return redirect(get_publisher().get_root_url()) |
|
1105 |
if token.type != 'anonymous-access-code': |
|
1106 |
return redirect(get_publisher().get_root_url()) |
|
1107 |
formdef_urlname, formdata_id = token.formdata_reference |
|
1108 |
try: |
|
1109 |
formdata = FormDef.get_by_urlname(formdef_urlname).data_class().get(formdata_id) |
|
1110 |
except KeyError: |
|
1111 |
return redirect(get_publisher().get_root_url()) |
|
1112 |
session = get_session() |
|
1113 |
if not hasattr(session, '_wf_anonymous_access_authorized'): |
|
1114 |
session._wf_anonymous_access_authorized = [] |
|
1115 |
session._wf_anonymous_access_authorized.append(formdata.get_url()) |
|
1116 |
return redirect(formdata.get_url() + 'access/') |
|
1117 | ||
1118 |
def links [html] (self): |
|
1119 |
links = Link.select() |
|
1120 |
if not links: |
|
1121 |
return |
|
1122 | ||
1123 |
Link.sort_by_position(links) |
|
1124 | ||
1125 |
'<div id="links">' |
|
1126 |
if links[0].url: |
|
1127 |
# first link has an URL, so it's not a title, so we display a |
|
1128 |
# generic title |
|
1129 |
'<h3>%s</h3>' % _('Useful links') |
|
1130 |
has_ul = False |
|
1131 |
vars = get_publisher().substitutions.get_context_variables() |
|
1132 |
for link in links: |
|
1133 |
if not link.url: |
|
1134 |
# acting title |
|
1135 |
if has_ul: |
|
1136 |
'</ul>' |
|
1137 |
'<h3>%s</h3>' % link.title |
|
1138 |
'<ul>' |
|
1139 |
has_ul = True |
|
1140 |
else: |
|
1141 |
if not has_ul: |
|
1142 |
'<ul>' |
|
1143 |
has_ul = True |
|
1144 |
'<li><a href="%s">%s</a></li>' % (get_variadic_url(link.url, vars), link.title) |
|
1145 |
if has_ul: |
|
1146 |
'</ul>' |
|
1147 |
'</div>' |
|
1148 | ||
1149 | ||
1150 |
def announces [html] (self): |
|
1151 |
announces = Announce.get_published_announces() |
|
1152 |
if not announces: |
|
1153 |
return |
|
1154 | ||
1155 |
'<div id="announces">' |
|
1156 |
'<h3>%s</h3>' % _('Announces to citizens') |
|
1157 |
for item in announces[:3]: |
|
1158 |
'<div class="announce-item">' |
|
1159 |
'<h4>' |
|
1160 |
if item.publication_time: |
|
1161 |
time.strftime(misc.date_format(), item.publication_time) |
|
1162 |
' - ' |
|
1163 |
item.title |
|
1164 |
'</h4>' |
|
1165 |
'<p>' |
|
1166 |
item.text |
|
1167 |
'</p>' |
|
1168 |
'</div>' |
|
1169 | ||
1170 |
'<ul id="announces-links">' |
|
1171 |
'<li><a href="announces/subscribe">%s</a></li>' % _('Receiving those Announces') |
|
1172 |
'<li><a href="announces/">%s</a></li>' % _('Previous Announces') |
|
1173 |
'</ul>' |
|
1174 |
'</div>' |
|
1175 | ||
1176 |
def myspace_snippet [html] (self): |
|
1177 |
'<div id="myspace">' |
|
1178 |
'<h3>%s</h3>' % _('My Space') |
|
1179 |
'<ul>' |
|
1180 |
if get_request().user and not get_request().user.anonymous: |
|
1181 |
' <li><a href="myspace/" id="member">%s</a></li>' % _('Access to your personal space') |
|
1182 |
' <li><a href="logout" id="logout">%s</a></li>' % _('Logout') |
|
1183 |
else: |
|
1184 |
' <li><a href="register/" id="inscr">%s</a></li>' % _('Registration') |
|
1185 |
' <li><a href="login/" id="login">%s</a></li>' % _('Login') |
|
1186 |
'</ul>' |
|
1187 |
'</div>' |
|
1188 | ||
1189 | ||
1190 |
def page_view [html] (self, key, title, urlname = None): |
|
1191 |
if not urlname: |
|
1192 |
urlname = key[3:].replace(str('_'), str('-')) |
|
1193 |
get_response().breadcrumb.append((urlname, title)) |
|
1194 |
template.html_top(title) |
|
1195 |
'<div class="article">' |
|
1196 |
htmltext(TextsDirectory.get_html_text(key)) |
|
1197 |
'</div>' |
|
1198 | ||
1199 |
def informations_editeur [html] (self): |
|
1200 |
get_response().filter['bigdiv'] = 'info' |
|
1201 |
return self.page_view('aq-editor-info', _('Editor Informations'), |
|
1202 |
urlname = 'informations_editeur') |
|
1203 | ||
1204 |
def accessibility(self): |
|
1205 |
get_response().filter['bigdiv'] = 'accessibility' |
|
1206 |
return self.page_view('aq-accessibility', _('Accessibility Statement')) |
|
1207 | ||
1208 |
def contact(self): |
|
1209 |
get_response().filter['bigdiv'] = 'contact' |
|
1210 |
return self.page_view('aq-contact', _('Contact')) |
|
1211 | ||
1212 |
def help(self): |
|
1213 |
get_response().filter['bigdiv'] = 'help' |
|
1214 |
return self.page_view('aq-help', _('Help')) |
|
1215 | ||
1216 | ||
1217 |
from qommon.publisher import get_publisher_class |
|
1218 |
get_publisher_class().root_directory_class = AlternateRootDirectory |
|
1219 |
get_publisher_class().after_login_url = 'myspace/' |
|
1220 |
get_publisher_class().use_sms_feature = True |
|
1221 | ||
1222 |
# help links |
|
1223 |
get_publisher_class().backoffice_help_url = { |
|
1224 |
'fr': 'https://doc.entrouvert.org/au-quotidien/stable/guide-gestionnaire.html', |
|
1225 |
} |
|
1226 |
get_publisher_class().admin_help_url = { |
|
1227 |
'fr': 'https://doc.entrouvert.org/auquotidien/dev/', |
|
1228 |
} |
|
1229 | ||
1230 | ||
1231 |
EmailsDirectory.register('announces-subscription-confirmation', |
|
1232 |
N_('Confirmation of Announces Subscription'), |
|
1233 |
N_('Available variables: change_url, cancel_url, time, sitename'), |
|
1234 |
default_subject = N_('Announce Subscription Request'), |
|
1235 |
default_body = N_("""\ |
|
1236 |
You have (or someone impersonating you has) requested to subscribe to |
|
1237 |
announces from [sitename]. To confirm this request, visit the |
|
1238 |
following link: |
|
1239 | ||
1240 |
[confirm_url] |
|
1241 | ||
1242 |
If you are not the person who made this request, or you wish to cancel |
|
1243 |
this request, visit the following link: |
|
1244 | ||
1245 |
[cancel_url] |
|
1246 | ||
1247 |
If you do nothing, the request will lapse after 3 days (precisely on |
|
1248 |
[time]). |
|
1249 |
""")) |
|
1250 | ||
1251 | ||
1252 |
TextsDirectory.register('aq-announces-subscription', |
|
1253 |
N_('Text on announces subscription page'), |
|
1254 |
default = N_('''\ |
|
1255 |
<p> |
|
1256 |
FIXME |
|
1257 |
'</p>''')) |
|
1258 | ||
1259 |
TextsDirectory.register('aq-sms-demo', |
|
1260 |
N_('Text when subscribing to announces SMS and configured as demo'), |
|
1261 |
default = N_(''' |
|
1262 |
<p> |
|
1263 |
Receiving announces by SMS is not possible in this demo |
|
1264 |
</p>''')) |
|
1265 | ||
1266 |
TextsDirectory.register('aq-editor-info', N_('Editor Informations')) |
|
1267 |
TextsDirectory.register('aq-accessibility', N_('Accessibility Statement')) |
|
1268 |
TextsDirectory.register('aq-contact', N_('Contact Information')) |
|
1269 |
TextsDirectory.register('aq-help', N_('Help')) |
|
1270 |
TextsDirectory.register('aq-sso-text', N_('Connecting with Identity Provider'), |
|
1271 |
default = N_('''<h3>Connecting with Identity Provider</h3> |
|
1272 |
<p>You can also use your identity provider to connect. |
|
1273 |
</p>''')) |
|
1274 | ||
1275 |
TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg = True) |
extra/modules/root.py | ||
---|---|---|
1 |
from quixote import get_publisher, get_response, get_request, redirect, get_session |
|
2 |
from quixote.directory import Directory |
|
3 |
from quixote.html import TemplateIO, htmltext |
|
4 |
from quixote.util import StaticDirectory |
|
5 | ||
6 |
from wcs.qommon.misc import get_variadic_url |
|
7 | ||
8 |
import os |
|
9 |
import re |
|
10 |
import string |
|
11 |
import urlparse |
|
12 | ||
13 |
try: |
|
14 |
import lasso |
|
15 |
except ImportError: |
|
16 |
pass |
|
17 | ||
18 |
import wcs |
|
19 |
import wcs.root |
|
20 |
import qommon |
|
21 |
from qommon import get_cfg, get_logger |
|
22 |
from qommon import template |
|
23 |
from qommon import errors |
|
24 |
from qommon.form import * |
|
25 |
from qommon import logger |
|
26 |
from wcs.roles import logged_users_role |
|
27 | ||
28 |
from qommon import emails |
|
29 |
from qommon.sms import SMS |
|
30 |
from wcs.categories import Category |
|
31 |
from wcs.formdef import FormDef |
|
32 |
from qommon.tokens import Token |
|
33 |
from qommon.admin.emails import EmailsDirectory |
|
34 |
from qommon.admin.texts import TextsDirectory |
|
35 | ||
36 |
from links import Link |
|
37 |
from announces import Announce, AnnounceSubscription |
|
38 |
from myspace import MyspaceDirectory |
|
39 |
from agenda import AgendaDirectory |
|
40 |
from events import Event, get_default_event_tags |
|
41 |
from payments import PublicPaymentDirectory |
|
42 |
from payments_ui import InvoicesDirectory |
|
43 | ||
44 |
import admin |
|
45 | ||
46 |
import wcs.forms.root |
|
47 |
from wcs.workflows import Workflow |
|
48 | ||
49 |
from saml2 import Saml2Directory |
|
50 | ||
51 |
OldRootDirectory = wcs.root.RootDirectory |
|
52 | ||
53 |
import qommon.ident.password |
|
54 |
import qommon.ident.idp |
|
55 | ||
56 |
import drupal |
|
57 |
import ezldap_ui |
|
58 |
import msp_ui |
|
59 | ||
60 |
def category_get_homepage_position(self): |
|
61 |
if hasattr(self, 'homepage_position') and self.homepage_position: |
|
62 |
return self.homepage_position |
|
63 |
if self.url_name == 'consultations': |
|
64 |
return '2nd' |
|
65 |
return '1st' |
|
66 |
Category.get_homepage_position = category_get_homepage_position |
|
67 | ||
68 |
def category_get_limit(self): |
|
69 |
if hasattr(self, 'limit') and self.limit is not None: |
|
70 |
return self.limit |
|
71 |
return 3 |
|
72 |
Category.get_limit = category_get_limit |
|
73 | ||
74 | ||
75 |
class FormsRootDirectory(wcs.forms.root.RootDirectory): |
|
76 | ||
77 |
def _q_index(self, *args): |
|
78 |
get_response().filter['is_index'] = True |
|
79 |
return wcs.forms.root.RootDirectory._q_index(self, *args) |
|
80 | ||
81 |
def user_forms(self, user_forms): |
|
82 |
r = TemplateIO(html=True) |
|
83 |
base_url = get_publisher().get_root_url() |
|
84 | ||
85 |
draft = [x for x in user_forms if x.is_draft()] |
|
86 |
if draft: |
|
87 |
r += htmltext('<h4 id="drafts">%s</h4>') % _('My Current Drafts') |
|
88 |
r += htmltext('<ul>') |
|
89 |
for f in draft: |
|
90 |
r += htmltext('<li><a href="%s%s/%s/%s">%s</a>, %s</li>') % (base_url, |
|
91 |
f.formdef.category.url_name, |
|
92 |
f.formdef.url_name, f.id, f.formdef.name, |
|
93 |
misc.localstrftime(f.receipt_time)) |
|
94 |
r += htmltext('</ul>') |
|
95 | ||
96 |
forms_by_status_name = {} |
|
97 |
for f in user_forms: |
|
98 |
if f.is_draft(): |
|
99 |
continue |
|
100 |
status = f.get_visible_status() |
|
101 |
if status: |
|
102 |
status_name = status.name |
|
103 |
else: |
|
104 |
status_name = None |
|
105 |
if status_name in forms_by_status_name: |
|
106 |
forms_by_status_name[status_name].append(f) |
|
107 |
else: |
|
108 |
forms_by_status_name[status_name] = [f] |
|
109 |
for status_name in forms_by_status_name: |
|
110 |
if status_name: |
|
111 |
r += htmltext('<h4>%s</h4>') % _('My forms with status "%s"') % status_name |
|
112 |
else: |
|
113 |
r += htmltext('<h4>%s</h4>') % _('My forms with an unknown status') % status_name |
|
114 |
r += htmltext('<ul>') |
|
115 |
forms_by_status_name[status_name].sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) |
|
116 |
for f in forms_by_status_name[status_name]: |
|
117 |
if f.formdef.category_id: |
|
118 |
category_url = f.formdef.category.url_name |
|
119 |
else: |
|
120 |
category_url = '.' |
|
121 |
r += htmltext('<li><a href="%s%s/%s/%s/">%s</a>, %s</li>') % ( |
|
122 |
base_url, |
|
123 |
category_url, |
|
124 |
f.formdef.url_name, f.id, f.formdef.name, |
|
125 |
misc.localstrftime(f.receipt_time)) |
|
126 |
r += htmltext('</ul>') |
|
127 |
return r.getvalue() |
|
128 | ||
129 | ||
130 |
class AnnounceDirectory(Directory): |
|
131 |
_q_exports = ['', 'edit', 'delete', 'email'] |
|
132 | ||
133 |
def __init__(self, announce): |
|
134 |
self.announce = announce |
|
135 | ||
136 |
def _q_index(self): |
|
137 |
template.html_top(_('Announces to citizens')) |
|
138 |
r = TemplateIO(html=True) |
|
139 | ||
140 |
if self.announce.publication_time: |
|
141 |
date_heading = '%s - ' % time.strftime(misc.date_format(), self.announce.publication_time) |
|
142 |
else: |
|
143 |
date_heading = '' |
|
144 | ||
145 |
r += htmltext('<h3>%s%s</h3>') % (date_heading, self.announce.title) |
|
146 | ||
147 |
r += htmltext('<p>') |
|
148 |
r += self.announce.text |
|
149 |
r += htmltext('</p>') |
|
150 | ||
151 |
r += htmltext('<p>') |
|
152 |
r += htmltext('<a href="../">%s</a>') % _('Back') |
|
153 |
r += htmltext('</p>') |
|
154 |
return r.getvalue() |
|
155 | ||
156 | ||
157 |
class AnnouncesDirectory(Directory): |
|
158 |
_q_exports = ['', 'subscribe', 'email', 'atom', 'sms', 'emailconfirm', |
|
159 |
'email_unsubscribe', 'sms_unsubscribe', 'smsconfirm', 'rawlist'] |
|
160 | ||
161 |
def _q_traverse(self, path): |
|
162 |
get_response().breadcrumb.append(('announces/', _('Announces'))) |
|
163 |
return Directory._q_traverse(self, path) |
|
164 | ||
165 |
def _q_index(self): |
|
166 |
template.html_top(_('Announces to citizens')) |
|
167 |
r = TemplateIO(html=True) |
|
168 |
r += self.announces_list() |
|
169 |
r += htmltext('<ul id="announces-links">') |
|
170 |
r += htmltext('<li><a href="subscribe">%s</a></li>') % _('Receiving those Announces') |
|
171 |
r += htmltext('</ul>') |
|
172 |
return r.getvalue() |
|
173 | ||
174 |
def _get_announce_subscription(self): |
|
175 |
""" """ |
|
176 |
sub = None |
|
177 |
if get_request().user: |
|
178 |
subs = AnnounceSubscription.select(lambda x: x.user_id == get_request().user.id) |
|
179 |
if subs: |
|
180 |
sub = subs[0] |
|
181 |
return sub |
|
182 | ||
183 |
def rawlist(self): |
|
184 |
get_response().filter = None |
|
185 |
return self.announces_list() |
|
186 | ||
187 |
def announces_list(self): |
|
188 |
announces = Announce.get_published_announces() |
|
189 |
if not announces: |
|
190 |
raise errors.TraversalError() |
|
191 | ||
192 |
# XXX: will need pagination someday |
|
193 |
r = TemplateIO(html=True) |
|
194 |
for item in announces: |
|
195 |
r += htmltext('<div class="announce-item">\n') |
|
196 |
r += htmltext('<h4>') |
|
197 |
if item.publication_time: |
|
198 |
r += time.strftime(misc.date_format(), item.publication_time) |
|
199 |
r += ' - ' |
|
200 |
r += item.title |
|
201 |
r += htmltext('</h4>\n') |
|
202 |
r += htmltext('<p>\n') |
|
203 |
r += item.text |
|
204 |
r += htmltext('\n</p>\n') |
|
205 |
r += htmltext('</div>\n') |
|
206 |
return r.getvalue() |
|
207 | ||
208 | ||
209 |
def sms(self): |
|
210 |
sms_mode = get_cfg('sms', {}).get('mode', 'none') |
|
211 | ||
212 |
if sms_mode == 'none': |
|
213 |
raise errors.TraversalError() |
|
214 | ||
215 |
get_response().breadcrumb.append(('sms', _('SMS'))) |
|
216 |
template.html_top(_('Receiving announces by SMS')) |
|
217 |
r = TemplateIO(html=True) |
|
218 | ||
219 |
if sms_mode == 'demo': |
|
220 |
r += TextsDirectory.get_html_text('aq-sms-demo') |
|
221 |
else: |
|
222 |
announces_cfg = get_cfg('announces',{}) |
|
223 |
mobile_mask = announces_cfg.get('mobile_mask') |
|
224 |
if mobile_mask: |
|
225 |
mobile_mask = ' (' + mobile_mask + ')' |
|
226 |
else: |
|
227 |
mobile_mask = '' |
|
228 |
form = Form(enctype='multipart/form-data') |
|
229 |
form.add(StringWidget, 'mobile', title = _('Mobile number %s') % mobile_mask, size=12, required=True) |
|
230 |
form.add_submit('submit', _('Subscribe')) |
|
231 |
form.add_submit('cancel', _('Cancel')) |
|
232 | ||
233 |
if form.get_submit() == 'cancel': |
|
234 |
return redirect('subscribe') |
|
235 | ||
236 |
if form.is_submitted() and not form.has_errors(): |
|
237 |
s = self.sms_submit(form) |
|
238 |
if s == False: |
|
239 |
r += form.render() |
|
240 |
else: |
|
241 |
return redirect("smsconfirm") |
|
242 |
else: |
|
243 |
r += form.render() |
|
244 |
return r.getvalue() |
|
245 | ||
246 |
def sms_submit(self, form): |
|
247 |
mobile = form.get_widget("mobile").parse() |
|
248 |
# clean the string, remove any extra character |
|
249 |
mobile = re.sub('[^0-9+]','',mobile) |
|
250 |
# if a mask was set, validate |
|
251 |
announces_cfg = get_cfg('announces',{}) |
|
252 |
mobile_mask = announces_cfg.get('mobile_mask') |
|
253 |
if mobile_mask: |
|
254 |
mobile_regexp = re.sub('X','[0-9]', mobile_mask) + '$' |
|
255 |
if not re.match(mobile_regexp, mobile): |
|
256 |
form.set_error("mobile", _("Phone number invalid ! It must match ") + mobile_mask) |
|
257 |
return False |
|
258 |
if mobile.startswith('00'): |
|
259 |
mobile = '+' + mobile[2:] |
|
260 |
else: |
|
261 |
# Default to france international prefix |
|
262 |
if not mobile.startswith('+'): |
|
263 |
mobile = re.sub("^0", "+33", mobile) |
|
264 |
sub = self._get_announce_subscription() |
|
265 |
if not sub: |
|
266 |
sub = AnnounceSubscription() |
|
267 |
if get_request().user: |
|
268 |
sub.user_id = get_request().user.id |
|
269 | ||
270 |
if mobile: |
|
271 |
sub.sms = mobile |
|
272 | ||
273 |
if not get_request().user: |
|
274 |
sub.enabled = False |
|
275 | ||
276 |
sub.store() |
|
277 | ||
278 |
# Asking sms confirmation |
|
279 |
token = Token(3 * 86400, 4, string.digits) |
|
280 |
token.type = 'announces-subscription-confirmation' |
|
281 |
token.subscription_id = sub.id |
|
282 |
token.store() |
|
283 | ||
284 |
message = _("Confirmation code : %s") % str(token.id) |
|
285 |
sms_cfg = get_cfg('sms', {}) |
|
286 |
sender = sms_cfg.get('sender', 'AuQuotidien')[:11] |
|
287 |
mode = sms_cfg.get('mode', 'none') |
|
288 |
sms = SMS.get_sms_class(mode) |
|
289 |
try: |
|
290 |
sms.send(sender, [mobile], message) |
|
291 |
except errors.SMSError, e: |
|
292 |
get_logger().error(e) |
|
293 |
form.set_error("mobile", _("Send SMS confirmation failed")) |
|
294 |
sub.remove("sms") |
|
295 |
return False |
|
296 | ||
297 |
def smsconfirm(self): |
|
298 |
template.html_top(_('Receiving announces by SMS confirmation')) |
|
299 |
r = TemplateIO(html=True) |
|
300 |
r += htmltext("<p>%s</p>") % _("You will receive a confirmation code by SMS.") |
|
301 |
form = Form(enctype='multipart/form-data') |
|
302 |
form.add(StringWidget, 'code', title = _('Confirmation code (4 characters)'), size=12, required=True) |
|
303 |
form.add_submit('submit', _('Subscribe')) |
|
304 |
form.add_submit('cancel', _('Cancel')) |
|
305 | ||
306 |
if form.get_submit() == 'cancel': |
|
307 |
return redirect('..') |
|
308 | ||
309 |
if form.is_submitted() and not form.has_errors(): |
|
310 |
token = None |
|
311 |
id = form.get_widget("code").parse() |
|
312 |
try: |
|
313 |
token = Token.get(id) |
|
314 |
except KeyError: |
|
315 |
form.set_error("code", _('Invalid confirmation code.')) |
|
316 |
else: |
|
317 |
if token.type != 'announces-subscription-confirmation': |
|
318 |
form.set_error("code", _('Invalid confirmation code.')) |
|
319 |
else: |
|
320 |
sub = AnnounceSubscription.get(token.subscription_id) |
|
321 |
token.remove_self() |
|
322 |
sub.enabled_sms = True |
|
323 |
sub.store() |
|
324 |
return redirect('.') |
|
325 |
r += form.render() |
|
326 |
else: |
|
327 |
r += form.render() |
|
328 | ||
329 |
return r.getvalue() |
|
330 | ||
331 |
def sms_unsubscribe(self): |
|
332 |
sub = self._get_announce_subscription() |
|
333 | ||
334 |
form = Form(enctype='multipart/form-data') |
|
335 |
if not sub: |
|
336 |
return redirect('..') |
|
337 | ||
338 |
form.add_submit('submit', _('Unsubscribe')) |
|
339 |
form.add_submit('cancel', _('Cancel')) |
|
340 | ||
341 |
if form.get_submit() == 'cancel': |
|
342 |
return redirect('..') |
|
343 | ||
344 |
get_response().breadcrumb.append(('sms', _('SMS Unsubscription'))) |
|
345 |
template.html_top() |
|
346 |
r = TemplateIO(html=True) |
|
347 | ||
348 |
if form.is_submitted() and not form.has_errors(): |
|
349 |
if sub: |
|
350 |
sub.remove("sms") |
|
351 | ||
352 |
root_url = get_publisher().get_root_url() |
|
353 |
r += htmltext('<p>') |
|
354 |
r += _('You have been unsubscribed from announces') |
|
355 |
r += htmltext('</p>') |
|
356 |
if not get_response().iframe_mode: |
|
357 |
r += htmltext('<a href="%s">%s</a>') % (root_url, _('Back Home')) |
|
358 |
else: |
|
359 |
r += htmltext('<p>') |
|
360 |
r += _('Do you want to stop receiving announces by sms ?') |
|
361 |
r += htmltext('</p>') |
|
362 |
r += form.render() |
|
363 | ||
364 |
return r.getvalue() |
|
365 | ||
366 | ||
367 |
def subscribe(self): |
|
368 |
get_response().breadcrumb.append(('subscribe', _('Subscription'))) |
|
369 |
template.html_top(_('Receiving Announces')) |
|
370 |
r = TemplateIO(html=True) |
|
371 | ||
372 |
r += TextsDirectory.get_html_text('aq-announces-subscription') |
|
373 | ||
374 |
sub = self._get_announce_subscription() |
|
375 | ||
376 |
r += htmltext('<ul id="announce-modes">') |
|
377 |
if sub and sub.email: |
|
378 |
r += htmltext(' <li>') |
|
379 |
r += htmltext('<span id="par_mail">%s</span>') % _('Email (currently subscribed)') |
|
380 |
r += htmltext(' <a href="email_unsubscribe" rel="popup">%s</a></li>') % _('Unsubscribe') |
|
381 |
else: |
|
382 |
r += htmltext(' <li><a href="email" id="par_mail" rel="popup">%s</a></li>') % _('Email') |
|
383 |
if sub and sub.sms: |
|
384 |
r += htmltext(' <li>') |
|
385 |
if sub.enabled_sms: |
|
386 |
r += htmltext('<span id="par_sms">%s</span>') % _('SMS %s (currently subscribed)') % sub.sms |
|
387 |
else: |
|
388 |
r += htmltext('<span id="par_sms">%s</span>') % _('SMS %s (currently not confirmed)') % sub.sms |
|
389 |
r += htmltext(' <a href="smsconfirm" rel="popup">%s</a> ') % _('Confirmation') |
|
390 |
r += htmltext(' <a href="sms_unsubscribe" rel="popup">%s</a></li>') % _('Unsubscribe') |
|
391 |
elif get_cfg('sms', {}).get('mode', 'none') != 'none': |
|
392 |
r += htmltext(' <li><a href="sms" id="par_sms">%s</a></li>') % _('SMS') |
|
393 |
r += htmltext(' <li><a class="feed-link" href="atom" id="par_rss">%s</a>') % _('Feed') |
|
394 |
r += htmltext('</ul>') |
|
395 |
return r.getvalue() |
|
396 | ||
397 |
def email(self): |
|
398 |
get_response().breadcrumb.append(('email', _('Email Subscription'))) |
|
399 |
template.html_top(_('Receiving Announces by email')) |
|
400 |
r = TemplateIO(html=True) |
|
401 | ||
402 |
form = Form(enctype='multipart/form-data') |
|
403 |
if get_request().user: |
|
404 |
if get_request().user.email: |
|
405 |
r += htmltext('<p>') |
|
406 |
r += _('You are logged in and your email is %s, ok to subscribe ?') % \ |
|
407 |
get_request().user.email |
|
408 |
r += htmltext('</p>') |
|
409 |
form.add_submit('submit', _('Subscribe')) |
|
410 |
else: |
|
411 |
r += htmltext('<p>') |
|
412 |
r += _("You are logged in but there is no email address in your profile.") |
|
413 |
r += htmltext('</p>') |
|
414 |
form.add(EmailWidget, 'email', title = _('Email'), required = True) |
|
415 |
form.add_submit('submit', _('Subscribe')) |
|
416 |
form.add_submit('submit-remember', _('Subscribe and add this email to my profile')) |
|
417 |
else: |
|
418 |
r += htmltext('<p>') |
|
419 |
r += _('FIXME will only be used for this purpose etc.') |
|
420 |
r += htmltext('</p>') |
|
421 |
form.add(EmailWidget, 'email', title = _('Email'), required = True) |
|
422 |
form.add_submit('submit', _('Subscribe')) |
|
423 | ||
424 |
form.add_submit('cancel', _('Cancel')) |
|
425 | ||
426 |
if form.get_submit() == 'cancel': |
|
427 |
return redirect('subscribe') |
|
428 | ||
429 |
if form.is_submitted() and not form.has_errors(): |
|
430 |
s = self.email_submit(form) |
|
431 |
if s is not False: |
|
432 |
return s |
|
433 |
else: |
|
434 |
r += form.render() |
|
435 | ||
436 |
return r.getvalue() |
|
437 | ||
438 |
def email_submit(self, form): |
|
439 |
sub = self._get_announce_subscription() |
|
440 |
if not sub: |
|
441 |
sub = AnnounceSubscription() |
|
442 | ||
443 |
if get_request().user: |
|
444 |
sub.user_id = get_request().user.id |
|
445 | ||
446 |
if form.get_widget('email'): |
|
447 |
sub.email = form.get_widget('email').parse() |
|
448 |
elif get_request().user.email: |
|
449 |
sub.email = get_request().user.email |
|
450 | ||
451 |
if not get_request().user: |
|
452 |
sub.enabled = False |
|
453 | ||
454 |
sub.store() |
|
455 | ||
456 |
if get_request().user: |
|
457 |
r = TemplateIO(html=True) |
|
458 |
root_url = get_publisher().get_root_url() |
|
459 |
r += htmltext('<p>') |
|
460 |
r += _('You have been subscribed to the announces.') |
|
461 |
r += htmltext('</p>') |
|
462 |
if not get_response().iframe_mode: |
|
463 |
r += htmltext('<a href="%s">%s</a>') % (root_url, _('Back Home')) |
|
464 |
return r.getvalue() |
|
465 | ||
466 |
# asking email confirmation before subscribing someone |
|
467 |
token = Token(3 * 86400) |
|
468 |
token.type = 'announces-subscription-confirmation' |
|
469 |
token.subscription_id = sub.id |
|
470 |
token.store() |
|
471 |
data = { |
|
472 |
'confirm_url': get_request().get_url() + 'confirm?t=%s&a=cfm' % token.id, |
|
473 |
'cancel_url': get_request().get_url() + 'confirm?t=%s&a=cxl' % token.id, |
|
474 |
'time': misc.localstrftime(time.localtime(token.expiration)), |
|
475 |
} |
|
476 | ||
477 |
emails.custom_ezt_email('announces-subscription-confirmation', |
|
478 |
data, sub.email, exclude_current_user = False) |
|
479 | ||
480 |
r = TemplateIO(html=True) |
|
481 |
root_url = get_publisher().get_root_url() |
|
482 |
r += htmltext('<p>') |
|
483 |
r += _('You have been sent an email for confirmation') |
|
484 |
r += htmltext('</p>') |
|
485 |
if not get_response().iframe_mode: |
|
486 |
r += htmltext('<a href="%s">%s</a>') % (root_url, _('Back Home')) |
|
487 |
return r.getvalue() |
|
488 | ||
489 |
def emailconfirm(self): |
|
490 |
tokenv = get_request().form.get('t') |
|
491 |
action = get_request().form.get('a') |
|
492 | ||
493 |
root_url = get_publisher().get_root_url() |
|
494 | ||
495 |
try: |
|
496 |
token = Token.get(tokenv) |
|
497 |
except KeyError: |
|
498 |
return template.error_page( |
|
499 |
_('The token you submitted does not exist, has expired, or has been cancelled.'), |
|
500 |
continue_to = (root_url, _('home page'))) |
|
501 | ||
502 |
if token.type != 'announces-subscription-confirmation': |
|
503 |
return template.error_page( |
|
504 |
_('The token you submitted is not appropriate for the requested task.'), |
|
505 |
continue_to = (root_url, _('home page'))) |
|
506 | ||
507 |
sub = AnnounceSubscription.get(token.subscription_id) |
|
508 | ||
509 |
if action == 'cxl': |
|
510 |
r = TemplateIO(html=True) |
|
511 |
root_url = get_publisher().get_root_url() |
|
512 |
template.html_top(_('Email Subscription')) |
|
513 |
r += htmltext('<h1>%s</h1>') % _('Request Cancelled') |
|
514 |
r += htmltext('<p>%s</p>') % _('The request for subscription has been cancelled.') |
|
515 |
r += htmltext('<p>') |
|
516 |
r += htmltext(_('Continue to <a href="%s">home page</a>') % root_url) |
|
517 |
r += htmltext('</p>') |
|
518 |
token.remove_self() |
|
519 |
sub.remove_self() |
|
520 |
return r.getvalue() |
|
521 | ||
522 |
if action == 'cfm': |
|
523 |
token.remove_self() |
|
524 |
sub.enabled = True |
|
525 |
sub.store() |
|
526 |
r = TemplateIO(html=True) |
|
527 |
root_url = get_publisher().get_root_url() |
|
528 |
template.html_top(_('Email Subscription')) |
|
529 |
r += htmltext('<h1>%s</h1>') % _('Subscription Confirmation') |
|
530 |
r += htmltext('<p>%s</p>') % _('Your subscription to announces is now effective.') |
|
531 |
r += htmltext('<p>') |
|
532 |
r += htmltext(_('Continue to <a href="%s">home page</a>') % root_url) |
|
533 |
r += htmltext('</p>') |
|
534 |
return r.getvalue() |
|
535 | ||
536 |
def atom(self): |
|
537 |
response = get_response() |
|
538 |
response.set_content_type('application/atom+xml') |
|
539 | ||
540 |
from pyatom import pyatom |
|
541 |
xmldoc = pyatom.XMLDoc() |
|
542 |
feed = pyatom.Feed() |
|
543 |
xmldoc.root_element = feed |
|
544 |
feed.title = get_cfg('misc', {}).get('sitename') or 'Au Quotidien' |
|
545 |
feed.id = get_request().get_url() |
|
546 | ||
547 |
author_email = get_cfg('emails', {}).get('reply_to') |
|
548 |
if not author_email: |
|
549 |
author_email = get_cfg('emails', {}).get('from') |
|
550 |
if author_email: |
|
551 |
feed.authors.append(pyatom.Author(author_email)) |
|
552 | ||
553 |
announces = Announce.get_published_announces() |
|
554 | ||
555 |
if announces and announces[0].modification_time: |
|
556 |
feed.updated = misc.format_time(announces[0].modification_time, |
|
557 |
'%(year)s-%(month)02d-%(day)02dT%(hour)02d:%(minute)02d:%(second)02dZ', |
|
558 |
gmtime = True) |
|
559 |
feed.links.append(pyatom.Link(get_request().get_url(1) + '/')) |
|
560 | ||
561 |
for item in announces: |
|
562 |
entry = item.get_atom_entry() |
|
563 |
if entry: |
|
564 |
feed.entries.append(entry) |
|
565 | ||
566 |
return str(feed) |
|
567 | ||
568 |
def email_unsubscribe(self): |
|
569 |
sub = self._get_announce_subscription() |
|
570 | ||
571 |
form = Form(enctype='multipart/form-data') |
|
572 |
if not sub: |
|
573 |
form.add(EmailWidget, 'email', title = _('Email'), required = True) |
|
574 | ||
575 |
form.add_submit('submit', _('Unsubscribe')) |
|
576 |
form.add_submit('cancel', _('Cancel')) |
|
577 | ||
578 |
if form.get_submit() == 'cancel': |
|
579 |
return redirect('..') |
|
580 | ||
581 |
get_response().breadcrumb.append(('email', _('Email Unsubscription'))) |
|
582 |
template.html_top() |
|
583 |
r = TemplateIO(html=True) |
|
584 | ||
585 |
if form.is_submitted() and not form.has_errors(): |
|
586 |
if sub: |
|
587 |
sub.remove("email") |
|
588 |
else: |
|
589 |
email = form.get_widget('email').parse() |
|
590 |
for s in AnnounceSubscription.select(): |
|
591 |
if s.email == email: |
|
592 |
s.remove("email") |
|
593 | ||
594 |
root_url = get_publisher().get_root_url() |
|
595 |
r += htmltext('<p>') |
|
596 |
r += _('You have been unsubscribed from announces') |
|
597 |
r += htmltext('</p>') |
|
598 |
if not get_response().iframe_mode: |
|
599 |
r += htmltext('<a href="%s">%s</a>') % (root_url, _('Back Home')) |
|
600 | ||
601 |
else: |
|
602 |
r += htmltext('<p>') |
|
603 |
r += _('Do you want to stop receiving announces by email?') |
|
604 |
r += htmltext('</p>') |
|
605 |
r += form.render() |
|
606 | ||
607 |
return r.getvalue() |
|
608 | ||
609 |
def _q_lookup(self, component): |
|
610 |
try: |
|
611 |
announce = Announce.get(component) |
|
612 |
except KeyError: |
|
613 |
raise errors.TraversalError() |
|
614 | ||
615 |
if announce.hidden: |
|
616 |
raise errors.TraversalError() |
|
617 | ||
618 |
get_response().breadcrumb.append((str(announce.id), announce.title)) |
|
619 |
return AnnounceDirectory(announce) |
|
620 | ||
621 |
OldRegisterDirectory = wcs.root.RegisterDirectory |
|
622 | ||
623 |
class AlternateRegisterDirectory(OldRegisterDirectory): |
|
624 |
def _q_traverse(self, path): |
|
625 |
get_response().filter['bigdiv'] = 'new_member' |
|
626 |
return OldRegisterDirectory._q_traverse(self, path) |
|
627 | ||
628 |
def _q_index(self): |
|
629 |
get_logger().info('register') |
|
630 |
ident_methods = get_cfg('identification', {}).get('methods', []) |
|
631 | ||
632 |
if len(ident_methods) == 0: |
|
633 |
idps = get_cfg('idp', {}) |
|
634 |
if len(idps) == 0: |
|
635 |
return template.error_page(_('Authentication subsystem is not yet configured.')) |
|
636 |
ident_methods = ['idp'] # fallback to old behaviour; liberty. |
|
637 | ||
638 |
if len(ident_methods) == 1: |
|
639 |
method = ident_methods[0] |
|
640 |
else: |
|
641 |
method = 'password' |
|
642 | ||
643 |
return qommon.ident.register(method) |
|
644 | ||
645 |
OldLoginDirectory = wcs.root.LoginDirectory |
|
646 | ||
647 |
class AlternateLoginDirectory(OldLoginDirectory): |
|
648 |
def _q_traverse(self, path): |
|
649 |
get_response().filter['bigdiv'] = 'member' |
|
650 |
return OldLoginDirectory._q_traverse(self, path) |
|
651 | ||
652 |
def _q_index(self): |
|
653 |
get_logger().info('login') |
|
654 |
ident_methods = get_cfg('identification', {}).get('methods', []) |
|
655 | ||
656 |
if len(ident_methods) > 1 and 'idp' in ident_methods: |
|
657 |
# if there is more than one identification method, and there is a |
|
658 |
# possibility of SSO, if we got there as a consequence of an access |
|
659 |
# unauthorized url on admin/ or backoffice/, then idp auth method |
|
660 |
# is chosen forcefully. |
|
661 |
after_url = get_session().after_url |
|
662 |
if after_url: |
|
663 |
root_url = get_publisher().get_root_url() |
|
664 |
after_path = urlparse.urlparse(after_url)[2] |
|
665 |
after_path = after_path[len(root_url):] |
|
666 |
if after_path.startswith(str('admin')) or \ |
|
667 |
after_path.startswith(str('backoffice')): |
|
668 |
ident_methods = ['idp'] |
|
669 | ||
670 |
# don't display authentication system choice |
|
671 |
if len(ident_methods) == 1: |
|
672 |
method = ident_methods[0] |
|
673 |
try: |
|
674 |
return qommon.ident.login(method) |
|
675 |
except KeyError: |
|
676 |
get_logger().error('failed to login with method %s' % method) |
|
677 |
return errors.TraversalError() |
|
678 | ||
679 |
if sorted(ident_methods) == ['idp', 'password']: |
|
680 |
r = TemplateIO(html=True) |
|
681 |
get_response().breadcrumb.append(('login', _('Login'))) |
|
682 |
identities_cfg = get_cfg('identities', {}) |
|
683 |
form = Form(enctype = 'multipart/form-data', id = 'login-form', use_tokens = False) |
|
684 |
if identities_cfg.get('email-as-username', False): |
|
685 |
form.add(StringWidget, 'username', title = _('Email'), size=25, required=True) |
|
686 |
else: |
|
687 |
form.add(StringWidget, 'username', title = _('Username'), size=25, required=True) |
|
688 |
form.add(PasswordWidget, 'password', title = _('Password'), size=25, required=True) |
|
689 |
form.add_submit('submit', _('Connect')) |
|
690 |
if form.is_submitted() and not form.has_errors(): |
|
691 |
tmp = qommon.ident.password.MethodDirectory().login_submit(form) |
|
692 |
if not form.has_errors(): |
|
693 |
return tmp |
|
694 | ||
695 |
r += htmltext('<div id="login-password">') |
|
696 |
r += get_session().display_message() |
|
697 |
r += form.render() |
|
698 | ||
699 |
base_url = get_publisher().get_root_url() |
|
700 |
r += htmltext('<p><a href="%sident/password/forgotten">%s</a></p>') % ( |
|
701 |
base_url, _('Forgotten password ?')) |
|
702 | ||
703 |
r += htmltext('</div>') |
|
704 | ||
705 |
# XXX: this part only supports a single IdP |
|
706 |
r += htmltext('<div id="login-sso">') |
|
707 |
r += TextsDirectory.get_html_text('aq-sso-text') |
|
708 |
form = Form(enctype='multipart/form-data', |
|
709 |
action = '%sident/idp/login' % base_url) |
|
710 |
form.add_hidden('method', 'idp') |
|
711 |
for kidp, idp in get_cfg('idp', {}).items(): |
|
712 |
p = lasso.Provider(lasso.PROVIDER_ROLE_IDP, |
|
713 |
misc.get_abs_path(idp['metadata']), |
|
714 |
misc.get_abs_path(idp.get('publickey')), None) |
|
715 |
form.add_hidden('idp', p.providerId) |
|
716 |
break |
|
717 |
form.add_submit('submit', _('Connect')) |
|
718 | ||
719 |
r += form.render() |
|
720 |
r += htmltext('</div>') |
|
721 | ||
722 |
get_request().environ['REQUEST_METHOD'] = 'GET' |
|
723 | ||
724 |
r += htmltext("""<script type="text/javascript"> |
|
725 |
document.getElementById('login-form')['username'].focus(); |
|
726 |
</script>""") |
|
727 |
return r.getvalue() |
|
728 |
else: |
|
729 |
return OldLoginDirectory._q_index(self) |
|
730 | ||
731 | ||
732 |
OldIdentDirectory = wcs.root.IdentDirectory |
|
733 |
class AlternateIdentDirectory(OldIdentDirectory): |
|
734 |
def _q_traverse(self, path): |
|
735 |
get_response().filter['bigdiv'] = 'member' |
|
736 |
return OldIdentDirectory._q_traverse(self, path) |
|
737 | ||
738 | ||
739 |
class AlternateRootDirectory(OldRootDirectory): |
|
740 |
_q_exports = ['', 'admin', 'backoffice', 'forms', 'login', 'logout', |
|
741 |
'liberty', 'token', 'saml', 'register', 'ident', 'afterjobs', |
|
742 |
('informations-editeur', 'informations_editeur'), 'index2', |
|
743 |
('announces', 'announces_dir'), |
|
744 |
'accessibility', 'contact', 'help', |
|
745 |
'myspace', 'services', 'agenda', 'categories', 'user', |
|
746 |
('tmp-upload', 'tmp_upload'), 'json', '__version__', |
|
747 |
'themes', 'pages', 'payment', 'invoices', 'accesscode', 'roles', |
|
748 |
'msp'] |
|
749 | ||
750 |
admin = admin.AdminRootDirectory() |
|
751 |
announces_dir = AnnouncesDirectory() |
|
752 |
register = AlternateRegisterDirectory() |
|
753 |
login = AlternateLoginDirectory() |
|
754 |
ident = AlternateIdentDirectory() |
|
755 |
myspace = MyspaceDirectory() |
|
756 |
agenda = AgendaDirectory() |
|
757 |
saml = Saml2Directory() |
|
758 |
payment = PublicPaymentDirectory() |
|
759 |
invoices = InvoicesDirectory() |
|
760 |
msp = msp_ui.MSPDirectory() |
|
761 | ||
762 |
def get_substitution_variables(self): |
|
763 |
d = {} |
|
764 |
def print_links(fd): |
|
765 |
fd.write(str(self.links())) |
|
766 |
d['links'] = print_links |
|
767 |
return d |
|
768 | ||
769 |
def _q_traverse(self, path): |
|
770 |
if get_publisher().has_site_option('drupal'): |
|
771 |
drupal.try_auth() |
|
772 |
if get_publisher().has_site_option('ezldap'): |
|
773 |
ezldap_ui.try_auth(self) |
|
774 | ||
775 |
session = get_session() |
|
776 |
if session: |
|
777 |
get_request().user = session.get_user() |
|
778 |
else: |
|
779 |
get_request().user = None |
|
780 | ||
781 |
get_publisher().substitutions.feed(get_request().user) |
|
782 | ||
783 |
response = get_response() |
|
784 |
if not hasattr(response, 'filter'): |
|
785 |
response.filter = {} |
|
786 | ||
787 |
response.filter['gauche'] = self.box_side(path) |
|
788 |
response.filter['keywords'] = template.get_current_theme().get('keywords') |
|
789 |
get_publisher().substitutions.feed(self) |
|
790 | ||
791 |
response.breadcrumb = [ ('', _('Home')) ] |
|
792 | ||
793 |
if not self.admin: |
|
794 |
self.admin = get_publisher().admin_directory_class() |
|
795 | ||
796 |
if not self.backoffice: |
|
797 |
self.backoffice = get_publisher().backoffice_directory_class() |
|
798 | ||
799 |
try: |
|
800 |
return Directory._q_traverse(self, path) |
|
801 |
except errors.TraversalError, e: |
|
802 |
try: |
|
803 |
f = FormDef.get_by_urlname(path[0]) |
|
804 |
except KeyError: |
|
805 |
pass |
|
806 |
else: |
|
807 |
base_url = get_publisher().get_root_url() |
|
808 | ||
809 |
uri_rest = get_request().environ.get('REQUEST_URI') |
|
810 |
if not uri_rest: |
|
811 |
uri_rest = get_request().get_path() |
|
812 |
if uri_rest.startswith(base_url): |
|
813 |
uri_rest = uri_rest[len(base_url):] |
|
814 |
elif uri_rest.startswith('/'): |
|
815 |
# dirty hack, ezldap reverseproxy uses a fake base_url |
|
816 |
uri_rest = uri_rest[1:] |
|
817 |
if f.category_id: |
|
818 |
return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest)) |
|
819 | ||
820 |
raise e |
|
821 | ||
822 | ||
823 |
def _q_lookup(self, component): |
|
824 |
if component == 'qo': |
|
825 |
dirname = os.path.join(get_publisher().data_dir, 'qommon') |
|
826 |
return StaticDirectory(dirname, follow_symlinks = True) |
|
827 | ||
828 |
if component == 'aq': |
|
829 |
dirname = os.path.join(get_publisher().data_dir, 'qommon', 'auquotidien') |
|
830 |
return StaticDirectory(dirname, follow_symlinks = True) |
|
831 | ||
832 |
if component in ('css','images'): |
|
833 |
return OldRootDirectory._q_lookup(self, component) |
|
834 | ||
835 |
# is this a category ? |
|
836 |
try: |
|
837 |
category = Category.get_by_urlname(component) |
|
838 |
except KeyError: |
|
839 |
pass |
|
840 |
else: |
|
841 |
return FormsRootDirectory(category) |
|
842 | ||
843 |
# is this a formdef ? |
|
844 |
try: |
|
845 |
formdef = FormDef.get_by_urlname(component) |
|
846 |
except KeyError: |
|
847 |
pass |
|
848 |
else: |
|
849 |
if formdef.category_id is None: |
|
850 |
get_response().filter['bigdiv'] = 'rub_service' |
|
851 |
return FormsRootDirectory()._q_lookup(component) |
|
852 |
# if there is category, let it fall back to raise TraversalError, |
|
853 |
# it will get caught in _q_traverse that will redirect it to an |
|
854 |
# URL embedding the category |
|
855 | ||
856 |
return None |
|
857 | ||
858 |
def json(self): |
|
859 |
return FormsRootDirectory().json() |
|
860 | ||
861 |
def categories(self): |
|
862 |
return FormsRootDirectory().categories() |
|
863 | ||
864 |
def _q_index(self): |
|
865 |
if get_request().is_json(): |
|
866 |
return FormsRootDirectory().json() |
|
867 | ||
868 |
root_url = get_publisher().get_root_url() |
|
869 |
if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump: |
|
870 |
return redirect('%smyspace/new' % root_url) |
|
871 | ||
872 |
if get_response().iframe_mode: |
|
873 |
# never display home page in an iframe |
|
874 |
return redirect('%sservices' % root_url) |
|
875 | ||
876 |
template.html_top() |
|
877 |
r = TemplateIO(html=True) |
|
878 |
get_response().filter['is_index'] = True |
|
879 | ||
880 |
if not 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []): |
|
881 |
t = TextsDirectory.get_html_text('aq-home-page') |
|
882 |
if not t: |
|
883 |
if get_request().user: |
|
884 |
t = TextsDirectory.get_html_text('welcome-logged') |
|
885 |
else: |
|
886 |
t = TextsDirectory.get_html_text('welcome-unlogged') |
|
887 |
if t: |
|
888 |
r += htmltext('<div id="home-page-intro">') |
|
889 |
r += t |
|
890 |
r += htmltext('</div>') |
|
891 | ||
892 |
r += htmltext('<div id="centre">') |
|
893 |
r += self.box_services(position='1st') |
|
894 |
r += htmltext('</div>') |
|
895 |
r += htmltext('<div id="droite">') |
|
896 |
r += self.myspace_snippet() |
|
897 |
r += self.box_services(position='2nd') |
|
898 |
r += self.consultations() |
|
899 |
r += self.announces() |
|
900 |
r += htmltext('</div>') |
|
901 | ||
902 |
user = get_request().user |
|
903 |
if user and user.can_go_in_backoffice(): |
|
904 |
get_response().filter['backoffice'] = True |
|
905 | ||
906 |
return r.getvalue() |
|
907 | ||
908 |
def services(self): |
|
909 |
template.html_top() |
|
910 |
get_response().filter['bigdiv'] = 'rub_service' |
|
911 |
return self.box_services(level = 2) |
|
912 | ||
913 |
def box_services(self, level=3, position=None): |
|
914 |
## Services |
|
915 |
if get_request().user and get_request().user.roles: |
|
916 |
accepted_roles = get_request().user.roles |
|
917 |
else: |
|
918 |
accepted_roles = [] |
|
919 | ||
920 |
cats = Category.select(order_by = 'name') |
|
921 |
cats = [x for x in cats if x.url_name != 'consultations'] |
|
922 |
Category.sort_by_position(cats) |
|
923 | ||
924 |
all_formdefs = FormDef.select(lambda x: not x.is_disabled() or x.disabled_redirection, |
|
925 |
order_by = 'name') |
|
926 | ||
927 |
if position: |
|
928 |
t = self.display_list_of_formdefs( |
|
929 |
[x for x in cats if x.get_homepage_position() == position], |
|
930 |
all_formdefs, accepted_roles) |
|
931 |
else: |
|
932 |
t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles) |
|
933 | ||
934 |
if not t: |
|
935 |
return |
|
936 | ||
937 |
r = TemplateIO(html=True) |
|
938 | ||
939 |
if position == '2nd': |
|
940 |
r += htmltext('<div id="services-2nd">') |
|
941 |
else: |
|
942 |
r += htmltext('<div id="services">') |
|
943 |
if level == 2: |
|
944 |
r += htmltext('<h2>%s</h2>') % _('Services') |
|
945 |
else: |
|
946 |
r += htmltext('<h3>%s</h3>') % _('Services') |
|
947 | ||
948 |
if get_response().iframe_mode: |
|
949 |
if get_request().user: |
|
950 |
message = TextsDirectory.get_html_text('welcome-logged') |
|
951 |
else: |
|
952 |
message = TextsDirectory.get_html_text('welcome-unlogged') |
|
953 | ||
954 |
if message: |
|
955 |
r += htmltext('<div id="welcome-message">') |
|
956 |
r += message |
|
957 |
r += htmltext('</div>') |
|
958 |
elif 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []): |
|
959 |
homepage_text = TextsDirectory.get_html_text('aq-home-page') |
|
960 |
if homepage_text: |
|
961 |
r += htmltext('<div id="home-page-intro">') |
|
962 |
r += homepage_text |
|
963 |
r += htmltext('</div>') |
|
964 | ||
965 |
r += htmltext('<ul>') |
|
966 |
r += t |
|
967 |
r += htmltext('</ul>') |
|
968 | ||
969 |
r += htmltext('</div>') |
|
970 |
return r.getvalue() |
|
971 | ||
972 |
def display_list_of_formdefs(self, cats, all_formdefs, accepted_roles): |
|
973 |
r = TemplateIO(html=True) |
|
974 |
for category in cats: |
|
975 |
if category.url_name == 'consultations': |
|
976 |
self.consultations_category = category |
|
977 |
continue |
|
978 |
formdefs = [x for x in all_formdefs if x.category_id == category.id] |
|
979 |
formdefs_advertise = [] |
|
980 | ||
981 |
for formdef in formdefs[:]: |
|
982 |
if formdef.is_disabled(): # is a redirection |
|
983 |
continue |
|
984 |
if not formdef.roles: |
|
985 |
continue |
|
986 |
if not get_request().user: |
|
987 |
if formdef.always_advertise: |
|
988 |
formdefs_advertise.append(formdef) |
|
989 |
formdefs.remove(formdef) |
|
990 |
continue |
|
991 |
if logged_users_role().id in formdef.roles: |
|
992 |
continue |
|
993 |
for q in accepted_roles: |
|
994 |
if q in formdef.roles: |
|
995 |
break |
|
996 |
else: |
|
997 |
if formdef.always_advertise: |
|
998 |
formdefs_advertise.append(formdef) |
|
999 |
formdefs.remove(formdef) |
|
1000 | ||
1001 |
if not formdefs and not formdefs_advertise: |
|
1002 |
continue |
|
1003 | ||
1004 |
r += htmltext('<li>') |
|
1005 |
r += htmltext('<strong>') |
|
1006 |
r += htmltext('<a href="%s/">') % category.url_name |
|
1007 |
r += category.name |
|
1008 |
r += htmltext('</a></strong>\n') |
|
1009 |
if category.description: |
|
1010 |
if category.description[0] == '<': |
|
1011 |
r += htmltext(category.description) |
|
1012 |
else: |
|
1013 |
r += htmltext('<p>') |
|
1014 |
r += category.description |
|
1015 |
r += htmltext('</p>') |
|
1016 |
r += htmltext('<ul>') |
|
1017 |
limit = category.get_limit() |
|
1018 |
for formdef in formdefs[:limit]: |
|
1019 |
r += htmltext('<li>') |
|
1020 |
r += htmltext('<a href="%s/%s/">%s</a>') % (category.url_name, formdef.url_name, formdef.name) |
|
1021 |
r += htmltext('</li>\n') |
|
1022 |
if len(formdefs) < limit: |
|
1023 |
for formdef in formdefs_advertise[:limit-len(formdefs)]: |
|
1024 |
r += htmltext('<li>') |
|
1025 |
r += htmltext('<a href="%s/%s/">%s</a>') % (category.url_name, formdef.url_name, formdef.name) |
|
1026 |
r += ' (%s)' % _('authentication required') |
|
1027 |
r += htmltext('</li>\n') |
|
1028 |
if (len(formdefs)+len(formdefs_advertise)) > limit: |
|
1029 |
r += htmltext('<li class="all-forms"><a href="%s/" title="%s">%s</a></li>') % (category.url_name, |
|
1030 |
_('Access to all forms of the "%s" category') % category.name, |
|
1031 |
_('Access to all forms in this category')) |
|
1032 |
r += htmltext('</ul>') |
|
1033 |
r += htmltext('</li>\n') |
|
1034 | ||
1035 |
return r.getvalue() |
|
1036 | ||
1037 |
def consultations(self): |
|
1038 |
cats = [x for x in Category.select() if x.url_name == 'consultations'] |
|
1039 |
if not cats: |
|
1040 |
return |
|
1041 |
consultations_category = cats[0] |
|
1042 |
formdefs = FormDef.select(lambda x: ( |
|
1043 |
x.category_id == consultations_category.id and |
|
1044 |
(not x.is_disabled() or x.disabled_redirection)), |
|
1045 |
order_by = 'name') |
|
1046 |
if not formdefs: |
|
1047 |
return |
|
1048 |
## Consultations |
|
1049 |
r = TemplateIO(html=True) |
|
1050 |
r += htmltext('<div id="consultations">') |
|
1051 |
r += htmltext('<h3>%s</h3>') % _('Consultations') |
|
1052 |
if consultations_category.description: |
|
1053 |
if consultations_category.description[0] == '<': |
|
1054 |
r += htmltext(consultations_category.description) |
|
1055 |
else: |
|
1056 |
r += htmltext('<p>') |
|
1057 |
r += consultations_category.description |
|
1058 |
r += htmltext('</p>') |
|
1059 |
r += htmltext('<ul>') |
|
1060 |
for formdef in formdefs: |
|
1061 |
r += htmltext('<li>') |
|
1062 |
r += htmltext('<a href="%s/%s/">%s</a>') % (consultations_category.url_name, |
|
1063 |
formdef.url_name, formdef.name) |
|
1064 |
r += htmltext('</li>') |
|
1065 |
r += htmltext('</ul>') |
|
1066 |
r += htmltext('</div>') |
|
1067 |
return r.getvalue() |
|
1068 | ||
1069 |
def box_side(self, path): |
|
1070 |
r = TemplateIO(html=True) |
|
1071 |
r += htmltext('<div id="sidebox">') |
|
1072 |
root_url = get_publisher().get_root_url() |
|
1073 | ||
1074 |
if self.has_anonymous_access_codes(): |
|
1075 |
r += htmltext('<form id="follow-form" action="%saccesscode">') % root_url |
|
1076 |
r += htmltext('<h3>%s</h3>') % _('Tracking') |
|
1077 |
r += htmltext('<label>%s</label> ') % _('Code:') |
|
1078 |
r += htmltext('<input name="code" size="10"/>') |
|
1079 |
r += htmltext('</form>') |
|
1080 | ||
1081 |
r += self.links() |
|
1082 | ||
1083 |
cats = Category.select(order_by = 'name') |
|
1084 |
cats = [x for x in cats if x.url_name != 'consultations' and x.get_homepage_position() == 'side'] |
|
1085 |
Category.sort_by_position(cats) |
|
1086 |
if cats: |
|
1087 |
r += htmltext('<div id="side-services">') |
|
1088 |
r += htmltext('<h3>%s</h3>') % _('Services') |
|
1089 |
r += htmltext('<ul>') |
|
1090 |
for cat in cats: |
|
1091 |
r += htmltext('<li><a href="%s/">%s</a></li>') % (cat.url_name, cat.name) |
|
1092 |
r += htmltext('</ul>') |
|
1093 |
r += htmltext('</div>') |
|
1094 | ||
1095 |
if Event.keys(): # if there are events, add a link to the agenda |
|
1096 |
tags = get_cfg('misc', {}).get('event_tags') |
|
1097 |
if not tags: |
|
1098 |
tags = get_default_event_tags() |
|
1099 |
r += htmltext('<h3 id="agenda-link"><a href="%sagenda/">%s</a></h3>') % (root_url, _('Agenda')) |
|
1100 | ||
1101 |
if path and path[0] == 'agenda': |
|
1102 |
r += htmltext('<p class="tags">') |
|
1103 |
for tag in tags: |
|
1104 |
r += htmltext('<a href="%sagenda/tag/%s">%s</a> ') % (root_url, tag, tag) |
|
1105 |
r += htmltext('</p>') |
|
1106 |
r += self.agenda.display_remote_calendars() |
|
1107 | ||
1108 |
r += htmltext('<p>') |
|
1109 |
r += htmltext(' <a href="%sagenda/filter">%s</a>') % (root_url, _('Advanced Filter')) |
|
1110 |
r += htmltext('</p>') |
|
1111 | ||
1112 |
r += htmltext('</div>') |
|
1113 |
return r.getvalue() |
|
1114 | ||
1115 |
def has_anonymous_access_codes(self): |
|
1116 |
for workflow in Workflow.select(): |
|
1117 |
for wfstatus in workflow.possible_status: |
|
1118 |
for wfitem in wfstatus.items: |
|
1119 |
if wfitem.key == 'create-anonymous-access-code': |
|
1120 |
return True |
|
1121 |
return False |
|
1122 | ||
1123 |
def accesscode(self): |
|
1124 |
code = get_request().form.get('code') |
|
1125 |
if not code: |
|
1126 |
return redirect(get_publisher().get_root_url()) |
|
1127 |
try: |
|
1128 |
token = Token.get(code) |
|
1129 |
except KeyError: |
|
1130 |
return redirect(get_publisher().get_root_url()) |
|
1131 |
if token.type != 'anonymous-access-code': |
|
1132 |
return redirect(get_publisher().get_root_url()) |
|
1133 |
formdef_urlname, formdata_id = token.formdata_reference |
|
1134 |
try: |
|
1135 |
formdata = FormDef.get_by_urlname(formdef_urlname).data_class().get(formdata_id) |
|
1136 |
except KeyError: |
|
1137 |
return redirect(get_publisher().get_root_url()) |
|
1138 |
session = get_session() |
|
1139 |
if not hasattr(session, '_wf_anonymous_access_authorized'): |
|
1140 |
session._wf_anonymous_access_authorized = [] |
|
1141 |
session._wf_anonymous_access_authorized.append(formdata.get_url()) |
|
1142 |
return redirect(formdata.get_url() + 'access/') |
|
1143 | ||
1144 |
def links(self): |
|
1145 |
links = Link.select() |
|
1146 |
if not links: |
|
1147 |
return |
|
1148 | ||
1149 |
Link.sort_by_position(links) |
|
1150 | ||
1151 |
r = TemplateIO(html=True) |
|
1152 | ||
1153 |
r += htmltext('<div id="links">') |
|
1154 |
if links[0].url: |
|
1155 |
# first link has an URL, so it's not a title, so we display a |
|
1156 |
# generic title |
|
1157 |
r += htmltext('<h3>%s</h3>') % _('Useful links') |
|
1158 |
has_ul = False |
|
1159 |
vars = get_publisher().substitutions.get_context_variables() |
|
1160 |
for link in links: |
|
1161 |
if not link.url: |
|
1162 |
# acting title |
|
1163 |
if has_ul: |
|
1164 |
r += htmltext('</ul>') |
|
1165 |
r += htmltext('<h3>%s</h3>') % link.title |
|
1166 |
r += htmltext('<ul>') |
|
1167 |
has_ul = True |
|
1168 |
else: |
|
1169 |
if not has_ul: |
|
1170 |
r += htmltext('<ul>') |
|
1171 |
has_ul = True |
|
1172 |
r += htmltext('<li><a href="%s">%s</a></li>') % (get_variadic_url(link.url, vars), link.title) |
|
1173 |
if has_ul: |
|
1174 |
r += htmltext('</ul>') |
|
1175 |
r += htmltext('</div>') |
|
1176 |
return r.getvalue() |
|
1177 | ||
1178 |
def announces(self): |
|
1179 |
announces = Announce.get_published_announces() |
|
1180 |
if not announces: |
|
1181 |
return |
|
1182 | ||
1183 |
r = TemplateIO(html=True) |
|
1184 |
r += htmltext('<div id="announces">') |
|
1185 |
r += htmltext('<h3>%s</h3>') % _('Announces to citizens') |
|
1186 |
for item in announces[:3]: |
|
1187 |
r += htmltext('<div class="announce-item">') |
|
1188 |
r += htmltext('<h4>') |
|
1189 |
if item.publication_time: |
|
1190 |
r += time.strftime(misc.date_format(), item.publication_time) |
|
1191 |
r += ' - ' |
|
1192 |
r += item.title |
|
1193 |
r += htmltext('</h4>') |
|
1194 |
r += htmltext('<p>') |
|
1195 |
r += item.text |
|
1196 |
r += htmltext('</p>') |
|
1197 |
r += htmltext('</div>') |
|
1198 | ||
1199 |
r += htmltext('<ul id="announces-links">') |
|
1200 |
r += htmltext('<li><a href="announces/subscribe">%s</a></li>') % _('Receiving those Announces') |
|
1201 |
r += htmltext('<li><a href="announces/">%s</a></li>') % _('Previous Announces') |
|
1202 |
r += htmltext('</ul>') |
|
1203 |
r += htmltext('</div>') |
|
1204 |
return r.getvalue() |
|
1205 | ||
1206 |
def myspace_snippet(self): |
|
1207 |
r = TemplateIO(html=True) |
|
1208 |
r += htmltext('<div id="myspace">') |
|
1209 |
r += htmltext('<h3>%s</h3>') % _('My Space') |
|
1210 |
r += htmltext('<ul>') |
|
1211 |
if get_request().user and not get_request().user.anonymous: |
|
1212 |
r += htmltext(' <li><a href="myspace/" id="member">%s</a></li>') % _('Access to your personal space') |
|
1213 |
r += htmltext(' <li><a href="logout" id="logout">%s</a></li>') % _('Logout') |
|
1214 |
else: |
|
1215 |
r += htmltext(' <li><a href="register/" id="inscr">%s</a></li>') % _('Registration') |
|
1216 |
r += htmltext(' <li><a href="login/" id="login">%s</a></li>') % _('Login') |
|
1217 |
r += htmltext('</ul>') |
|
1218 |
r += htmltext('</div>') |
|
1219 |
return r.getvalue() |
|
1220 | ||
1221 |
def page_view(self, key, title, urlname = None): |
|
1222 |
if not urlname: |
|
1223 |
urlname = key[3:].replace(str('_'), str('-')) |
|
1224 |
get_response().breadcrumb.append((urlname, title)) |
|
1225 |
template.html_top(title) |
|
1226 |
r = TemplateIO(html=True) |
|
1227 |
r += htmltext('<div class="article">') |
|
1228 |
r += htmltext(TextsDirectory.get_html_text(key)) |
|
1229 |
r += htmltext('</div>') |
|
1230 |
return r.getvalue() |
|
1231 | ||
1232 |
def informations_editeur(self): |
|
1233 |
get_response().filter['bigdiv'] = 'info' |
|
1234 |
return self.page_view('aq-editor-info', _('Editor Informations'), |
|
1235 |
urlname = 'informations_editeur') |
|
1236 | ||
1237 |
def accessibility(self): |
|
1238 |
get_response().filter['bigdiv'] = 'accessibility' |
|
1239 |
return self.page_view('aq-accessibility', _('Accessibility Statement')) |
|
1240 | ||
1241 |
def contact(self): |
|
1242 |
get_response().filter['bigdiv'] = 'contact' |
|
1243 |
return self.page_view('aq-contact', _('Contact')) |
|
1244 | ||
1245 |
def help(self): |
|
1246 |
get_response().filter['bigdiv'] = 'help' |
|
1247 |
return self.page_view('aq-help', _('Help')) |
|
1248 | ||
1249 | ||
1250 |
from qommon.publisher import get_publisher_class |
|
1251 |
get_publisher_class().root_directory_class = AlternateRootDirectory |
|
1252 |
get_publisher_class().after_login_url = 'myspace/' |
|
1253 |
get_publisher_class().use_sms_feature = True |
|
1254 | ||
1255 |
# help links |
|
1256 |
get_publisher_class().backoffice_help_url = { |
|
1257 |
'fr': 'https://doc.entrouvert.org/au-quotidien/stable/guide-gestionnaire.html', |
|
1258 |
} |
|
1259 |
get_publisher_class().admin_help_url = { |
|
1260 |
'fr': 'https://doc.entrouvert.org/auquotidien/dev/', |
|
1261 |
} |
|
1262 | ||
1263 | ||
1264 |
EmailsDirectory.register('announces-subscription-confirmation', |
|
1265 |
N_('Confirmation of Announces Subscription'), |
|
1266 |
N_('Available variables: change_url, cancel_url, time, sitename'), |
|
1267 |
default_subject = N_('Announce Subscription Request'), |
|
1268 |
default_body = N_("""\ |
|
1269 |
You have (or someone impersonating you has) requested to subscribe to |
|
1270 |
announces from [sitename]. To confirm this request, visit the |
|
1271 |
following link: |
|
1272 | ||
1273 |
[confirm_url] |
|
1274 | ||
1275 |
If you are not the person who made this request, or you wish to cancel |
|
1276 |
this request, visit the following link: |
|
1277 | ||
1278 |
[cancel_url] |
|
1279 | ||
1280 |
If you do nothing, the request will lapse after 3 days (precisely on |
|
1281 |
[time]). |
|
1282 |
""")) |
|
1283 | ||
1284 | ||
1285 |
TextsDirectory.register('aq-announces-subscription', |
|
1286 |
N_('Text on announces subscription page'), |
|
1287 |
default = N_('''\ |
|
1288 |
<p> |
|
1289 |
FIXME |
|
1290 |
'</p>''')) |
|
1291 | ||
1292 |
TextsDirectory.register('aq-sms-demo', |
|
1293 |
N_('Text when subscribing to announces SMS and configured as demo'), |
|
1294 |
default = N_(''' |
|
1295 |
<p> |
|
1296 |
Receiving announces by SMS is not possible in this demo |
|
1297 |
</p>''')) |
|
1298 | ||
1299 |
TextsDirectory.register('aq-editor-info', N_('Editor Informations')) |
|
1300 |
TextsDirectory.register('aq-accessibility', N_('Accessibility Statement')) |
|
1301 |
TextsDirectory.register('aq-contact', N_('Contact Information')) |
|
1302 |
TextsDirectory.register('aq-help', N_('Help')) |
|
1303 |
TextsDirectory.register('aq-sso-text', N_('Connecting with Identity Provider'), |
|
1304 |
default = N_('''<h3>Connecting with Identity Provider</h3> |
|
1305 |
<p>You can also use your identity provider to connect. |
|
1306 |
</p>''')) |
|
1307 | ||
1308 |
TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg = True) |
extra/modules/strongbox_ui.ptl | ||
---|---|---|
1 |
import time |
|
2 | ||
3 |
from quixote import get_request, get_response, get_session, redirect |
|
4 |
from quixote.directory import Directory, AccessControlled |
|
5 | ||
6 |
import wcs |
|
7 |
import wcs.admin.root |
|
8 |
from wcs.backoffice.menu import * |
|
9 | ||
10 |
from qommon import errors, misc |
|
11 |
from qommon.form import * |
|
12 |
from qommon.strftime import strftime |
|
13 | ||
14 |
from strongbox import StrongboxType, StrongboxItem |
|
15 | ||
16 | ||
17 | ||
18 |
class StrongboxTypeDirectory(Directory): |
|
19 |
_q_exports = ['', 'edit', 'delete'] |
|
20 | ||
21 |
def __init__(self, strongboxtype): |
|
22 |
self.strongboxtype = strongboxtype |
|
23 | ||
24 |
def _q_index [html] (self): |
|
25 |
html_top('strongbox', title = _('Item Type: %s') % self.strongboxtype.label) |
|
26 |
'<h2>%s</h2>' % _('Item Type: %s') % self.strongboxtype.label |
|
27 |
get_response().filter['sidebar'] = self.get_sidebar() |
|
28 |
get_session().display_message() |
|
29 | ||
30 |
if self.strongboxtype.validation_months: |
|
31 |
'<div class="bo-block">' |
|
32 |
'<ul>' |
|
33 |
'<li>' |
|
34 |
_('Number of months of validity:') |
|
35 |
' ' |
|
36 |
self.strongboxtype.validation_months |
|
37 |
'</li>' |
|
38 |
'</ul>' |
|
39 |
'</div>' |
|
40 | ||
41 |
def get_sidebar [html] (self): |
|
42 |
'<ul>' |
|
43 |
'<li><a href="edit">%s</a></li>' % _('Edit') |
|
44 |
'<li><a href="delete">%s</a></li>' % _('Delete') |
|
45 |
'</ul>' |
|
46 | ||
47 |
def edit [html] (self): |
|
48 |
form = self.form() |
|
49 |
if form.get_submit() == 'cancel': |
|
50 |
return redirect('.') |
|
51 | ||
52 |
if form.is_submitted() and not form.has_errors(): |
|
53 |
self.submit(form) |
|
54 |
return redirect('..') |
|
55 | ||
56 |
html_top('strongbox', title = _('Edit Item Type: %s') % self.strongboxtype.label) |
|
57 |
'<h2>%s</h2>' % _('Edit Item Type: %s') % self.strongboxtype.label |
|
58 |
form.render() |
|
59 | ||
60 | ||
61 |
def form(self): |
|
62 |
form = Form(enctype='multipart/form-data') |
|
63 |
form.add(StringWidget, 'label', title = _('Label'), required = True, |
|
64 |
value = self.strongboxtype.label) |
|
65 |
form.add(IntWidget, 'validation_months', title=_('Number of months of validity'), |
|
66 |
value=self.strongboxtype.validation_months, |
|
67 |
hint=_('Use 0 if there is no expiration')) |
|
68 |
form.add_submit('submit', _('Submit')) |
|
69 |
form.add_submit('cancel', _('Cancel')) |
|
70 |
return form |
|
71 | ||
72 |
def submit(self, form): |
|
73 |
for k in ('label', 'validation_months'): |
|
74 |
widget = form.get_widget(k) |
|
75 |
if widget: |
|
76 |
setattr(self.strongboxtype, k, widget.parse()) |
|
77 |
self.strongboxtype.store() |
|
78 | ||
79 |
def delete [html] (self): |
|
80 |
form = Form(enctype='multipart/form-data') |
|
81 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
82 |
'You are about to irrevocably delete this item type.'))) |
|
83 |
form.add_submit('submit', _('Submit')) |
|
84 |
form.add_submit('cancel', _('Cancel')) |
|
85 |
if form.get_submit() == 'cancel': |
|
86 |
return redirect('..') |
|
87 |
if not form.is_submitted() or form.has_errors(): |
|
88 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
89 |
html_top('strongbox', title = _('Delete Item Type')) |
|
90 |
'<h2>%s</h2>' % _('Deleting Item Type: %s') % self.strongboxtype.label |
|
91 |
form.render() |
|
92 |
else: |
|
93 |
self.strongboxtype.remove_self() |
|
94 |
return redirect('..') |
|
95 | ||
96 | ||
97 |
class StrongboxTypesDirectory(Directory): |
|
98 |
_q_exports = ['', 'new'] |
|
99 | ||
100 |
def _q_traverse(self, path): |
|
101 |
get_response().breadcrumb.append(('types/', _('Item Types'))) |
|
102 |
return Directory._q_traverse(self, path) |
|
103 | ||
104 |
def _q_index [html] (self): |
|
105 |
return redirect('..') |
|
106 | ||
107 |
def new [html] (self): |
|
108 |
type_ui = StrongboxTypeDirectory(StrongboxType()) |
|
109 | ||
110 |
form = type_ui.form() |
|
111 |
if form.get_submit() == 'cancel': |
|
112 |
return redirect('.') |
|
113 | ||
114 |
if form.is_submitted() and not form.has_errors(): |
|
115 |
type_ui.submit(form) |
|
116 |
return redirect('%s/' % type_ui.strongboxtype.id) |
|
117 | ||
118 |
get_response().breadcrumb.append(('new', _('New Item Type'))) |
|
119 |
html_top('strongbox', title = _('New Item Type')) |
|
120 |
'<h2>%s</h2>' % _('New Item Type') |
|
121 |
form.render() |
|
122 | ||
123 |
def _q_lookup(self, component): |
|
124 |
try: |
|
125 |
strongboxtype = StrongboxType.get(component) |
|
126 |
except KeyError: |
|
127 |
raise errors.TraversalError() |
|
128 |
get_response().breadcrumb.append((str(strongboxtype.id), strongboxtype.label)) |
|
129 |
return StrongboxTypeDirectory(strongboxtype) |
|
130 | ||
131 | ||
132 |
class StrongboxDirectory(AccessControlled, Directory): |
|
133 |
_q_exports = ['', 'types', 'add', 'add_to'] |
|
134 |
label = N_('Strongbox') |
|
135 | ||
136 |
types = StrongboxTypesDirectory() |
|
137 | ||
138 |
def _q_access(self): |
|
139 |
user = get_request().user |
|
140 |
if not user: |
|
141 |
raise errors.AccessUnauthorizedError() |
|
142 |
admin_role = get_cfg('aq-permissions', {}).get('strongbox', None) |
|
143 |
if not (user.is_admin or admin_role in (user.roles or [])): |
|
144 |
raise errors.AccessForbiddenError( |
|
145 |
public_msg = _('You are not allowed to access Strongbox Management'), |
|
146 |
location_hint = 'backoffice') |
|
147 | ||
148 |
get_response().breadcrumb.append(('strongbox/', _('Strongbox'))) |
|
149 | ||
150 | ||
151 |
def _q_index [html] (self): |
|
152 |
html_top('strongbox', _('Strongbox')) |
|
153 | ||
154 |
'<ul id="main-actions">' |
|
155 |
' <li><a class="new-item" href="types/new">%s</a></li>' % _('New Item Type') |
|
156 |
'</ul>' |
|
157 | ||
158 |
get_session().display_message() |
|
159 | ||
160 |
'<div class="splitcontent-left">' |
|
161 |
'<div class="bo-block">' |
|
162 |
'<h2>%s</h2>' % _('Propose a file to a user') |
|
163 |
form = Form(enctype='multipart/form-data') |
|
164 |
form.add(StringWidget, 'q', title = _('User'), required=True) |
|
165 |
form.add_submit('search', _('Search')) |
|
166 |
form.render() |
|
167 |
if form.is_submitted() and not form.has_errors(): |
|
168 |
q = form.get_widget('q').parse() |
|
169 |
users = self.search_for_users(q) |
|
170 |
if users: |
|
171 |
if len(users) == 1: |
|
172 |
return redirect('add_to?user_id=%s' % users[0].id) |
|
173 |
if len(users) < 50: |
|
174 |
_('(first 50 users only)') |
|
175 |
'<ul>' |
|
176 |
for u in users: |
|
177 |
'<li><a href="add_to?user_id=%s">%s</a></li>' % (u.id, u.display_name) |
|
178 |
'</ul>' |
|
179 |
else: |
|
180 |
_('No user found.') |
|
181 |
'</div>' |
|
182 |
'</div>' |
|
183 | ||
184 |
'<div class="splitcontent-right">' |
|
185 |
'<div class="bo-block">' |
|
186 |
types = StrongboxType.select() |
|
187 |
'<h2>%s</h2>' % _('Item Types') |
|
188 |
if not types: |
|
189 |
'<p>' |
|
190 |
_('There is no item types defined at the moment.') |
|
191 |
'</p>' |
|
192 | ||
193 |
'<ul class="biglist" id="strongbox-list">' |
|
194 |
for l in types: |
|
195 |
type_id = l.id |
|
196 |
'<li class="biglistitem" id="itemId_%s">' % type_id |
|
197 |
'<strong class="label"><a href="types/%s/">%s</a></strong>' % (type_id, l.label) |
|
198 |
'</li>' |
|
199 |
'</ul>' |
|
200 |
'</div>' |
|
201 |
'</div>' |
|
202 | ||
203 |
def search_for_users(self, q): |
|
204 |
if hasattr(get_publisher().user_class, 'search'): |
|
205 |
return get_publisher().user_class.search(q) |
|
206 |
if q: |
|
207 |
users = [x for x in get_publisher().user_class.select() |
|
208 |
if q in (x.name or '') or q in (x.email or '')] |
|
209 |
return users |
|
210 |
else: |
|
211 |
return [] |
|
212 | ||
213 |
def get_form(self): |
|
214 |
types = [(x.id, x.label) for x in StrongboxType.select()] |
|
215 |
form = Form(action='add', enctype='multipart/form-data') |
|
216 |
form.add(StringWidget, 'description', title=_('Description'), size=60) |
|
217 |
form.add(FileWidget, 'file', title=_('File'), required=True) |
|
218 |
form.add(SingleSelectWidget, 'type_id', title=_('Document Type'), |
|
219 |
options = [(None, _('Not specified'))] + types) |
|
220 |
form.add(DateWidget, 'date_time', title = _('Document Date')) |
|
221 |
form.add_submit('submit', _('Upload')) |
|
222 |
return form |
|
223 | ||
224 |
def add(self): |
|
225 |
form = self.get_form() |
|
226 |
form.add(StringWidget, 'user_id', title=_('User')) |
|
227 |
if not form.is_submitted(): |
|
228 |
return redirect('.') |
|
229 | ||
230 |
sffile = StrongboxItem() |
|
231 |
sffile.user_id = form.get_widget('user_id').parse() |
|
232 |
sffile.description = form.get_widget('description').parse() |
|
233 |
sffile.proposed_time = time.localtime() |
|
234 |
sffile.proposed_id = get_request().user.id |
|
235 |
sffile.type_id = form.get_widget('type_id').parse() |
|
236 |
v = form.get_widget('date_time').parse() |
|
237 |
sffile.set_expiration_time_from_date(v) |
|
238 |
sffile.store() |
|
239 |
sffile.set_file(form.get_widget('file').parse()) |
|
240 |
sffile.store() |
|
241 |
return redirect('.') |
|
242 | ||
243 |
def add_to [html] (self): |
|
244 |
form = Form(enctype='multipart/form-data', action='add_to') |
|
245 |
form.add(StringWidget, 'user_id', title = _('User'), required=True) |
|
246 |
try: |
|
247 |
user_id = form.get_widget('user_id').parse() |
|
248 |
user = get_publisher().user_class.get(user_id) |
|
249 |
except: |
|
250 |
return redirect('.') |
|
251 |
if not user: |
|
252 |
return redirect('.') |
|
253 |
get_request().form = {} |
|
254 |
get_request().environ['REQUEST_METHOD'] = 'GET' |
|
255 | ||
256 |
html_top('strongbox', _('Strongbox')) |
|
257 |
'<h2>%s %s</h2>' % (_('Propose a file to:'), user.display_name) |
|
258 |
form = self.get_form() |
|
259 |
form.add(HiddenWidget, 'user_id', title=_('User'), value=user.id) |
|
260 |
form.render() |
|
261 |
extra/modules/strongbox_ui.py | ||
---|---|---|
1 |
import time |
|
2 | ||
3 |
from quixote import get_request, get_response, get_session, redirect |
|
4 |
from quixote.directory import Directory, AccessControlled |
|
5 |
from quixote.html import TemplateIO, htmltext |
|
6 | ||
7 |
import wcs |
|
8 |
import wcs.admin.root |
|
9 |
from wcs.backoffice.menu import * |
|
10 | ||
11 |
from qommon import errors, misc |
|
12 |
from qommon.form import * |
|
13 |
from qommon.strftime import strftime |
|
14 | ||
15 |
from strongbox import StrongboxType, StrongboxItem |
|
16 | ||
17 | ||
18 | ||
19 |
class StrongboxTypeDirectory(Directory): |
|
20 |
_q_exports = ['', 'edit', 'delete'] |
|
21 | ||
22 |
def __init__(self, strongboxtype): |
|
23 |
self.strongboxtype = strongboxtype |
|
24 | ||
25 |
def _q_index(self): |
|
26 |
html_top('strongbox', title = _('Item Type: %s') % self.strongboxtype.label) |
|
27 |
r += htmltext('<h2>%s</h2>') % _('Item Type: %s') % self.strongboxtype.label |
|
28 |
get_response().filter['sidebar'] = self.get_sidebar() |
|
29 |
r += get_session().display_message() |
|
30 | ||
31 |
if self.strongboxtype.validation_months: |
|
32 |
r += htmltext('<div class="bo-block">') |
|
33 |
r += htmltext('<ul>') |
|
34 |
r += htmltext('<li>') |
|
35 |
r += _('Number of months of validity:') |
|
36 |
r += ' ' |
|
37 |
r += self.strongboxtype.validation_months |
|
38 |
r += htmltext('</li>') |
|
39 |
r += htmltext('</ul>') |
|
40 |
r += htmltext('</div>') |
|
41 | ||
42 |
return r.getvalue() |
|
43 | ||
44 |
def get_sidebar(self): |
|
45 |
r = TemplateIO(html=True) |
|
46 |
r += htmltext('<ul>') |
|
47 |
r += htmltext('<li><a href="edit">%s</a></li>') % _('Edit') |
|
48 |
r += htmltext('<li><a href="delete">%s</a></li>') % _('Delete') |
|
49 |
r += htmltext('</ul>') |
|
50 |
return r.getvalue() |
|
51 | ||
52 |
def edit(self): |
|
53 |
form = self.form() |
|
54 |
if form.get_submit() == 'cancel': |
|
55 |
return redirect('.') |
|
56 | ||
57 |
if form.is_submitted() and not form.has_errors(): |
|
58 |
self.submit(form) |
|
59 |
return redirect('..') |
|
60 | ||
61 |
html_top('strongbox', title = _('Edit Item Type: %s') % self.strongboxtype.label) |
|
62 |
r = TemplateIO(html=True) |
|
63 |
r += htmltext('<h2>%s</h2>') % _('Edit Item Type: %s') % self.strongboxtype.label |
|
64 |
r += form.render() |
|
65 |
return r.getvalue() |
|
66 | ||
67 |
def form(self): |
|
68 |
form = Form(enctype='multipart/form-data') |
|
69 |
form.add(StringWidget, 'label', title = _('Label'), required = True, |
|
70 |
value = self.strongboxtype.label) |
|
71 |
form.add(IntWidget, 'validation_months', title=_('Number of months of validity'), |
|
72 |
value=self.strongboxtype.validation_months, |
|
73 |
hint=_('Use 0 if there is no expiration')) |
|
74 |
form.add_submit('submit', _('Submit')) |
|
75 |
form.add_submit('cancel', _('Cancel')) |
|
76 |
return form |
|
77 | ||
78 |
def submit(self, form): |
|
79 |
for k in ('label', 'validation_months'): |
|
80 |
widget = form.get_widget(k) |
|
81 |
if widget: |
|
82 |
setattr(self.strongboxtype, k, widget.parse()) |
|
83 |
self.strongboxtype.store() |
|
84 | ||
85 |
def delete(self): |
|
86 |
form = Form(enctype='multipart/form-data') |
|
87 |
form.widgets.append(HtmlWidget('<p>%s</p>' % _( |
|
88 |
'You are about to irrevocably delete this item type.'))) |
|
89 |
form.add_submit('submit', _('Submit')) |
|
90 |
form.add_submit('cancel', _('Cancel')) |
|
91 |
if form.get_submit() == 'cancel': |
|
92 |
return redirect('..') |
|
93 |
if not form.is_submitted() or form.has_errors(): |
|
94 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
95 |
html_top('strongbox', title = _('Delete Item Type')) |
|
96 |
r = TemplateIO(html=True) |
|
97 |
r += htmltext('<h2>%s</h2>') % _('Deleting Item Type: %s') % self.strongboxtype.label |
|
98 |
r += form.render() |
|
99 |
return r.getvalue() |
|
100 |
else: |
|
101 |
self.strongboxtype.remove_self() |
|
102 |
return redirect('..') |
|
103 | ||
104 | ||
105 |
class StrongboxTypesDirectory(Directory): |
|
106 |
_q_exports = ['', 'new'] |
|
107 | ||
108 |
def _q_traverse(self, path): |
|
109 |
get_response().breadcrumb.append(('types/', _('Item Types'))) |
|
110 |
return Directory._q_traverse(self, path) |
|
111 | ||
112 |
def _q_index(self): |
|
113 |
return redirect('..') |
|
114 | ||
115 |
def new(self): |
|
116 |
type_ui = StrongboxTypeDirectory(StrongboxType()) |
|
117 | ||
118 |
form = type_ui.form() |
|
119 |
if form.get_submit() == 'cancel': |
|
120 |
return redirect('.') |
|
121 | ||
122 |
if form.is_submitted() and not form.has_errors(): |
|
123 |
type_ui.submit(form) |
|
124 |
return redirect('%s/' % type_ui.strongboxtype.id) |
|
125 | ||
126 |
get_response().breadcrumb.append(('new', _('New Item Type'))) |
|
127 |
html_top('strongbox', title = _('New Item Type')) |
|
128 |
r = TemplateIO(html=True) |
|
129 |
r += htmltext('<h2>%s</h2>') % _('New Item Type') |
|
130 |
r += form.render() |
|
131 |
return r.getvalue() |
|
132 | ||
133 |
def _q_lookup(self, component): |
|
134 |
try: |
|
135 |
strongboxtype = StrongboxType.get(component) |
|
136 |
except KeyError: |
|
137 |
raise errors.TraversalError() |
|
138 |
get_response().breadcrumb.append((str(strongboxtype.id), strongboxtype.label)) |
|
139 |
return StrongboxTypeDirectory(strongboxtype) |
|
140 | ||
141 | ||
142 |
class StrongboxDirectory(AccessControlled, Directory): |
|
143 |
_q_exports = ['', 'types', 'add', 'add_to'] |
|
144 |
label = N_('Strongbox') |
|
145 | ||
146 |
types = StrongboxTypesDirectory() |
|
147 | ||
148 |
def _q_access(self): |
|
149 |
user = get_request().user |
|
150 |
if not user: |
|
151 |
raise errors.AccessUnauthorizedError() |
|
152 |
admin_role = get_cfg('aq-permissions', {}).get('strongbox', None) |
|
153 |
if not (user.is_admin or admin_role in (user.roles or [])): |
|
154 |
raise errors.AccessForbiddenError( |
|
155 |
public_msg = _('You are not allowed to access Strongbox Management'), |
|
156 |
location_hint = 'backoffice') |
|
157 | ||
158 |
get_response().breadcrumb.append(('strongbox/', _('Strongbox'))) |
|
159 | ||
160 | ||
161 |
def _q_index(self): |
|
162 |
html_top('strongbox', _('Strongbox')) |
|
163 |
r = TemplateIO(html=True) |
|
164 | ||
165 |
r += htmltext('<ul id="main-actions">') |
|
166 |
r += htmltext(' <li><a class="new-item" href="types/new">%s</a></li>') % _('New Item Type') |
|
167 |
r += htmltext('</ul>') |
|
168 | ||
169 |
r += get_session().display_message() |
|
170 | ||
171 |
r += htmltext('<div class="splitcontent-left">') |
|
172 |
r += htmltext('<div class="bo-block">') |
|
173 |
r += htmltext('<h2>%s</h2>') % _('Propose a file to a user') |
|
174 |
form = Form(enctype='multipart/form-data') |
|
175 |
form.add(StringWidget, 'q', title = _('User'), required=True) |
|
176 |
form.add_submit('search', _('Search')) |
|
177 |
r += form.render() |
|
178 |
if form.is_submitted() and not form.has_errors(): |
|
179 |
q = form.get_widget('q').parse() |
|
180 |
users = self.search_for_users(q) |
|
181 |
if users: |
|
182 |
if len(users) == 1: |
|
183 |
return redirect('add_to?user_id=%s' % users[0].id) |
|
184 |
if len(users) < 50: |
|
185 |
r += _('(first 50 users only)') |
|
186 |
r += htmltext('<ul>') |
|
187 |
for u in users: |
|
188 |
r += htmltext('<li><a href="add_to?user_id=%s">%s</a></li>') % (u.id, u.display_name) |
|
189 |
r += htmltext('</ul>') |
|
190 |
else: |
|
191 |
r += _('No user found.') |
|
192 |
r += htmltext('</div>') |
|
193 |
r += htmltext('</div>') |
|
194 | ||
195 |
r += htmltext('<div class="splitcontent-right">') |
|
196 |
r += htmltext('<div class="bo-block">') |
|
197 |
types = StrongboxType.select() |
|
198 |
r += htmltext('<h2>%s</h2>') % _('Item Types') |
|
199 |
if not types: |
|
200 |
r += htmltext('<p>') |
|
201 |
r += _('There is no item types defined at the moment.') |
|
202 |
r += htmltext('</p>') |
|
203 | ||
204 |
r += htmltext('<ul class="biglist" id="strongbox-list">') |
|
205 |
for l in types: |
|
206 |
type_id = l.id |
|
207 |
r += htmltext('<li class="biglistitem" id="itemId_%s">') % type_id |
|
208 |
r += htmltext('<strong class="label"><a href="types/%s/">%s</a></strong>') % (type_id, l.label) |
|
209 |
r += htmltext('</li>') |
|
210 |
r += htmltext('</ul>') |
|
211 |
r += htmltext('</div>') |
|
212 |
r += htmltext('</div>') |
|
213 |
return r.getvalue() |
|
214 | ||
215 |
def search_for_users(self, q): |
|
216 |
if hasattr(get_publisher().user_class, 'search'): |
|
217 |
return get_publisher().user_class.search(q) |
|
218 |
if q: |
|
219 |
users = [x for x in get_publisher().user_class.select() |
|
220 |
if q in (x.name or '') or q in (x.email or '')] |
|
221 |
return users |
|
222 |
else: |
|
223 |
return [] |
|
224 | ||
225 |
def get_form(self): |
|
226 |
types = [(x.id, x.label) for x in StrongboxType.select()] |
|
227 |
form = Form(action='add', enctype='multipart/form-data') |
|
228 |
form.add(StringWidget, 'description', title=_('Description'), size=60) |
|
229 |
form.add(FileWidget, 'file', title=_('File'), required=True) |
|
230 |
form.add(SingleSelectWidget, 'type_id', title=_('Document Type'), |
|
231 |
options = [(None, _('Not specified'))] + types) |
|
232 |
form.add(DateWidget, 'date_time', title = _('Document Date')) |
|
233 |
form.add_submit('submit', _('Upload')) |
|
234 |
return form |
|
235 | ||
236 |
def add(self): |
|
237 |
form = self.get_form() |
|
238 |
form.add(StringWidget, 'user_id', title=_('User')) |
|
239 |
if not form.is_submitted(): |
|
240 |
return redirect('.') |
|
241 | ||
242 |
sffile = StrongboxItem() |
|
243 |
sffile.user_id = form.get_widget('user_id').parse() |
|
244 |
sffile.description = form.get_widget('description').parse() |
|
245 |
sffile.proposed_time = time.localtime() |
|
246 |
sffile.proposed_id = get_request().user.id |
|
247 |
sffile.type_id = form.get_widget('type_id').parse() |
|
248 |
v = form.get_widget('date_time').parse() |
|
249 |
sffile.set_expiration_time_from_date(v) |
|
250 |
sffile.store() |
|
251 |
sffile.set_file(form.get_widget('file').parse()) |
|
252 |
sffile.store() |
|
253 |
return redirect('.') |
|
254 | ||
255 |
def add_to(self): |
|
256 |
form = Form(enctype='multipart/form-data', action='add_to') |
|
257 |
form.add(StringWidget, 'user_id', title = _('User'), required=True) |
|
258 |
try: |
|
259 |
user_id = form.get_widget('user_id').parse() |
|
260 |
user = get_publisher().user_class.get(user_id) |
|
261 |
except: |
|
262 |
return redirect('.') |
|
263 |
if not user: |
|
264 |
return redirect('.') |
|
265 |
get_request().form = {} |
|
266 |
get_request().environ['REQUEST_METHOD'] = 'GET' |
|
267 | ||
268 |
html_top('strongbox', _('Strongbox')) |
|
269 |
r = TemplateIO(html=True) |
|
270 |
r += htmltext('<h2>%s %s</h2>') % (_('Propose a file to:'), user.display_name) |
|
271 |
form = self.get_form() |
|
272 |
form.add(HiddenWidget, 'user_id', title=_('User'), value=user.id) |
|
273 |
r += form.render() |
|
274 |
return r.getvalue() |
|
0 |
- |