From 686c4babd7fbc9d1eabd6054ab971302e3697982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Fri, 18 Apr 2014 16:49:21 +0200 Subject: [PATCH 1/2] convert remaining files from .ptl to .py (#3930) --- extra/modules/events_ui.ptl | 390 ------------ extra/modules/events_ui.py | 406 +++++++++++++ extra/modules/formpage.ptl | 36 -- extra/modules/formpage.py | 36 ++ extra/modules/msp_ui.ptl | 176 ------ extra/modules/msp_ui.py | 183 ++++++ extra/modules/myspace.ptl | 715 ---------------------- extra/modules/myspace.py | 735 ++++++++++++++++++++++ extra/modules/payments_ui.ptl | 571 ------------------ extra/modules/payments_ui.py | 595 ++++++++++++++++++ extra/modules/root.ptl | 1275 --------------------------------------- extra/modules/root.py | 1308 ++++++++++++++++++++++++++++++++++++++++ extra/modules/strongbox_ui.ptl | 261 -------- extra/modules/strongbox_ui.py | 274 +++++++++ 14 files changed, 3537 insertions(+), 3424 deletions(-) delete mode 100644 extra/modules/events_ui.ptl create mode 100644 extra/modules/events_ui.py delete mode 100644 extra/modules/formpage.ptl create mode 100644 extra/modules/formpage.py delete mode 100644 extra/modules/msp_ui.ptl create mode 100644 extra/modules/msp_ui.py delete mode 100644 extra/modules/myspace.ptl create mode 100644 extra/modules/myspace.py delete mode 100644 extra/modules/payments_ui.ptl create mode 100644 extra/modules/payments_ui.py delete mode 100644 extra/modules/root.ptl create mode 100644 extra/modules/root.py delete mode 100644 extra/modules/strongbox_ui.ptl create mode 100644 extra/modules/strongbox_ui.py diff --git a/extra/modules/events_ui.ptl b/extra/modules/events_ui.ptl deleted file mode 100644 index 54dcf84..0000000 --- a/extra/modules/events_ui.ptl +++ /dev/null @@ -1,390 +0,0 @@ -import time - -from quixote import get_request, get_response, get_session, redirect -from quixote.directory import Directory, AccessControlled - -import wcs -import wcs.admin.root -from wcs.backoffice.menu import * - -from qommon import errors, misc -from qommon.form import * -from qommon.strftime import strftime - -from events import Event, RemoteCalendar, get_default_event_tags - - - -class RemoteCalendarDirectory(Directory): - _q_exports = ['', 'edit', 'delete', 'update'] - - def __init__(self, calendar): - self.calendar = calendar - - def _q_index [html] (self): - form = Form(enctype='multipart/form-data') - form.add_submit('edit', _('Edit')) - form.add_submit('delete', _('Delete')) - form.add_submit('update', _('Update')) - form.add_submit('back', _('Back')) - - if form.get_submit() == 'edit': - return redirect('edit') - if form.get_submit() == 'update': - return redirect('update') - if form.get_submit() == 'delete': - return redirect('delete') - if form.get_submit() == 'back': - return redirect('..') - - html_top('events', title = _('Remote Calendar: %s') % self.calendar.label) - '

%s

' % _('Remote Calendar: %s') % self.calendar.label - - get_session().display_message() - - '

' - self.calendar.url - if self.calendar.error: - ' - %s' % self.calendar.get_error_message() - '

' - - if not self.calendar.content: - '

' - _('No content has been retrieved yet.') - '

' - else: - '' - - form.render() - - def edit [html] (self): - form = self.form() - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - self.submit(form) - return redirect('..') - - html_top('events', title = _('Edit Remote Calendar: %s') % self.calendar.label) - '

%s

' % _('Edit Remote Calendar: %s') % self.calendar.label - form.render() - - - def form(self): - form = Form(enctype='multipart/form-data') - form.add(StringWidget, 'label', title = _('Label'), required = True, - value = self.calendar.label) - form.add(StringWidget, 'url', title = _('URL'), required = True, - value = self.calendar.url, size = 40) - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - return form - - def submit(self, form): - for k in ('label', 'url'): - widget = form.get_widget(k) - if widget: - setattr(self.calendar, k, widget.parse()) - self.calendar.store() - - def delete [html] (self): - form = Form(enctype='multipart/form-data') - form.widgets.append(HtmlWidget('

%s

' % _( - 'You are about to irrevocably delete this remote calendar.'))) - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - if form.get_submit() == 'cancel': - return redirect('..') - if not form.is_submitted() or form.has_errors(): - get_response().breadcrumb.append(('delete', _('Delete'))) - html_top('events', title = _('Delete Remote Calendar')) - '

%s

' % _('Deleting Remote Calendar: %s') % self.calendar.label - form.render() - else: - self.calendar.remove_self() - return redirect('..') - - def update(self): - get_session().message = ('info', - _('Calendar update has been requested, reload in a few moments')) - get_response().add_after_job('updating remote calendar', - self.calendar.download_and_parse, - fire_and_forget = True) - return redirect('.') - - - -class RemoteCalendarsDirectory(Directory): - _q_exports = ['', 'new'] - - def _q_traverse(self, path): - get_response().breadcrumb.append(('remote/', _('Remote Calendars'))) - return Directory._q_traverse(self, path) - - def _q_index [html] (self): - return redirect('..') - - def new [html] (self): - calendar_ui = RemoteCalendarDirectory(RemoteCalendar()) - - form = calendar_ui.form() - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - calendar_ui.submit(form) - return redirect('%s/' % calendar_ui.calendar.id) - - get_response().breadcrumb.append(('new', _('New Remote Calendar'))) - html_top('events', title = _('New Remote Calendar')) - '

%s

' % _('New Remote Calendar') - form.render() - - def _q_lookup(self, component): - try: - event = RemoteCalendar.get(component) - except KeyError: - raise errors.TraversalError() - get_response().breadcrumb.append((str(event.id), event.label)) - return RemoteCalendarDirectory(event) - - -class EventDirectory(Directory): - _q_exports = ['', 'edit', 'delete'] - - def __init__(self, event): - self.event = event - - def _q_index [html] (self): - form = Form(enctype='multipart/form-data') - form.add_submit('edit', _('Edit')) - form.add_submit('delete', _('Delete')) - form.add_submit('back', _('Back')) - - if form.get_submit() == 'edit': - return redirect('edit') - if form.get_submit() == 'delete': - return redirect('delete') - if form.get_submit() == 'back': - return redirect('..') - - html_top('events', title = _('Event: %s') % self.event.title) - '

%s

' % _('Event: %s') % self.event.title - '

' - self.event.description - '

' - '' - - if self.event.more_infos: - '

' - self.event.more_infos - '

' - - form.render() - - def edit [html] (self): - form = self.form() - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - self.submit(form) - return redirect('..') - - html_top('events', title = _('Edit Event: %s') % self.event.title) - '

%s

' % _('Edit Event: %s') % self.event.title - form.render() - - - def form(self): - form = Form(enctype='multipart/form-data') - form.add(StringWidget, 'title', title = _('Title'), required = True, - value = self.event.title) - form.add(TextWidget, 'description', title = _('Description'), - cols = 70, rows = 10, - required = True, value = self.event.description) - form.add(StringWidget, 'url', title = _('URL'), required = False, - value = self.event.url, size = 40) - form.add(DateWidget, 'date_start', title = _('Start Date'), required = True, - value = strftime(misc.date_format(), self.event.date_start)) - form.add(DateWidget, 'date_end', title = _('End Date'), required = False, - value = strftime(misc.date_format(), self.event.date_end)) - form.add(TextWidget, 'location', title = _('Location'), - cols = 70, rows = 4, - required = False, value = self.event.location) - form.add(StringWidget, 'organizer', title = _('Organizer'), required = False, - value = self.event.organizer, size = 40) - form.add(TextWidget, 'more_infos', title = _('More informations'), - cols = 70, rows = 10, - required = False, value = self.event.more_infos) - form.add(TagsWidget, 'keywords', title = _('Keywords'), - value = self.event.keywords, size = 50, - known_tags = get_cfg('misc', {}).get('event_tags', get_default_event_tags())) - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - return form - - def submit(self, form): - for k in ('title', 'description', 'url', 'date_start', 'date_end', - 'organizer', 'location', 'more_infos', 'keywords'): - widget = form.get_widget(k) - if widget: - if k in ('date_start', 'date_end'): - # convert dates to 9-item tuples - v = widget.parse() - if v: - setattr(self.event, k, time.strptime(v, misc.date_format())) - else: - setattr(self.event, k, None) - else: - setattr(self.event, k, widget.parse()) - self.event.store() - - def delete [html] (self): - form = Form(enctype='multipart/form-data') - form.widgets.append(HtmlWidget('

%s

' % _( - 'You are about to irrevocably delete this event.'))) - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - if form.get_submit() == 'cancel': - return redirect('..') - if not form.is_submitted() or form.has_errors(): - get_response().breadcrumb.append(('delete', _('Delete'))) - html_top('events', title = _('Delete Event')) - '

%s

' % _('Deleting Event: %s') % self.event.title - form.render() - else: - self.event.remove_self() - return redirect('..') - - - - -class EventsDirectory(AccessControlled, Directory): - _q_exports = ['', 'new', 'listing', 'remote'] - label = N_('Events') - - remote = RemoteCalendarsDirectory() - - def _q_access(self): - user = get_request().user - if not user: - raise errors.AccessUnauthorizedError() - admin_role = get_cfg('aq-permissions', {}).get('events', None) - if not (user.is_admin or admin_role in (user.roles or [])): - raise errors.AccessForbiddenError( - public_msg = _('You are not allowed to access Events Management'), - location_hint = 'backoffice') - - get_response().breadcrumb.append(('events/', _('Events'))) - - - def _q_index [html] (self): - html_top('events', _('Events')) - - '' - - '
' - - '
' - events = Event.select() - '

%s

' % _('Events') - if not events: - '

' - _('There is no event defined at the moment.') - '

' - '
    ' - for l in events: - event_id = l.id - '
  • ' % event_id - '%s' % (event_id, l.title) - ' - ' - l.format_date() - '

    ' - command_icon('%s/edit' % event_id, 'edit') - command_icon('%s/delete' % event_id, 'remove') - '

  • ' - '
' - '
' - '
' - - '
' - '
' - rcalendars = RemoteCalendar.select() - '

%s

' % _('Remote Calendars') - if not rcalendars: - '

' - _('There is no remote calendars defined at the moment.') - '

' - - '
    ' - for l in rcalendars: - rcal_id = l.id - '
  • ' % rcal_id - '%s' % (rcal_id, l.label) - '

    ' - l.url - if l.error: - '
    %s' % l.get_error_message() - '

    ' - '

    ' - command_icon('remote/%s/edit' % rcal_id, 'edit') - command_icon('remote/%s/delete' % rcal_id, 'remove') - '

  • ' - '
' - '
' - '
' - - def new [html] (self): - event_ui = EventDirectory(Event()) - - form = event_ui.form() - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - event_ui.submit(form) - return redirect('%s/' % event_ui.event.id) - - get_response().breadcrumb.append(('new', _('New Event'))) - html_top('events', title = _('New Event')) - '

%s

' % _('New Event') - form.render() - - def _q_lookup(self, component): - try: - event = Event.get(component) - except KeyError: - raise errors.TraversalError() - get_response().breadcrumb.append((str(event.id), event.title)) - return EventDirectory(event) - - def listing(self): - return redirect('.') - diff --git a/extra/modules/events_ui.py b/extra/modules/events_ui.py new file mode 100644 index 0000000..d13d1c7 --- /dev/null +++ b/extra/modules/events_ui.py @@ -0,0 +1,406 @@ +import time + +from quixote import get_request, get_response, get_session, redirect +from quixote.directory import Directory, AccessControlled +from quixote.html import TemplateIO, htmltext + +import wcs +import wcs.admin.root +from wcs.backoffice.menu import * + +from qommon import errors, misc +from qommon.form import * +from qommon.strftime import strftime + +from events import Event, RemoteCalendar, get_default_event_tags + + + +class RemoteCalendarDirectory(Directory): + _q_exports = ['', 'edit', 'delete', 'update'] + + def __init__(self, calendar): + self.calendar = calendar + + def _q_index(self): + form = Form(enctype='multipart/form-data') + form.add_submit('edit', _('Edit')) + form.add_submit('delete', _('Delete')) + form.add_submit('update', _('Update')) + form.add_submit('back', _('Back')) + + if form.get_submit() == 'edit': + return redirect('edit') + if form.get_submit() == 'update': + return redirect('update') + if form.get_submit() == 'delete': + return redirect('delete') + if form.get_submit() == 'back': + return redirect('..') + + html_top('events', title = _('Remote Calendar: %s') % self.calendar.label) + r = TemplateIO(html=True) + r += htmltext('

%s

') % _('Remote Calendar: %s') % self.calendar.label + + r += get_session().display_message() + + r += htmltext('

') + self.calendar.url + if self.calendar.error: + r += htmltext(' - %s') % self.calendar.get_error_message() + r += htmltext('

') + + if not self.calendar.content: + r += htmltext('

') + r += _('No content has been retrieved yet.') + r += htmltext('

') + else: + r += htmltext('') + + r += form.render() + return r.getvalue() + + def edit(self): + form = self.form() + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + self.submit(form) + return redirect('..') + + html_top('events', title = _('Edit Remote Calendar: %s') % self.calendar.label) + r = TemplateIO(html=True) + r += htmltext('

%s

') % _('Edit Remote Calendar: %s') % self.calendar.label + r += form.render() + return r.getvalue() + + def form(self): + form = Form(enctype='multipart/form-data') + form.add(StringWidget, 'label', title = _('Label'), required = True, + value = self.calendar.label) + form.add(StringWidget, 'url', title = _('URL'), required = True, + value = self.calendar.url, size = 40) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + return form + + def submit(self, form): + for k in ('label', 'url'): + widget = form.get_widget(k) + if widget: + setattr(self.calendar, k, widget.parse()) + self.calendar.store() + + def delete(self): + form = Form(enctype='multipart/form-data') + form.widgets.append(HtmlWidget('

%s

' % _( + 'You are about to irrevocably delete this remote calendar.'))) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + if form.get_submit() == 'cancel': + return redirect('..') + if not form.is_submitted() or form.has_errors(): + get_response().breadcrumb.append(('delete', _('Delete'))) + html_top('events', title = _('Delete Remote Calendar')) + r = TemplateIO(html=True) + r += htmltext('

%s

') % _('Deleting Remote Calendar: %s') % self.calendar.label + r += form.render() + return r.getvalue() + else: + self.calendar.remove_self() + return redirect('..') + + def update(self): + get_session().message = ('info', + _('Calendar update has been requested, reload in a few moments')) + get_response().add_after_job('updating remote calendar', + self.calendar.download_and_parse, + fire_and_forget = True) + return redirect('.') + + + +class RemoteCalendarsDirectory(Directory): + _q_exports = ['', 'new'] + + def _q_traverse(self, path): + get_response().breadcrumb.append(('remote/', _('Remote Calendars'))) + return Directory._q_traverse(self, path) + + def _q_index(self): + return redirect('..') + + def new(self): + calendar_ui = RemoteCalendarDirectory(RemoteCalendar()) + + form = calendar_ui.form() + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + calendar_ui.submit(form) + return redirect('%s/' % calendar_ui.calendar.id) + + get_response().breadcrumb.append(('new', _('New Remote Calendar'))) + html_top('events', title = _('New Remote Calendar')) + r = TemplateIO(html=True) + r += htmltext('

%s

') % _('New Remote Calendar') + r += form.render() + return r.getvalue() + + def _q_lookup(self, component): + try: + event = RemoteCalendar.get(component) + except KeyError: + raise errors.TraversalError() + get_response().breadcrumb.append((str(event.id), event.label)) + return RemoteCalendarDirectory(event) + + +class EventDirectory(Directory): + _q_exports = ['', 'edit', 'delete'] + + def __init__(self, event): + self.event = event + + def _q_index(self): + form = Form(enctype='multipart/form-data') + form.add_submit('edit', _('Edit')) + form.add_submit('delete', _('Delete')) + form.add_submit('back', _('Back')) + + if form.get_submit() == 'edit': + return redirect('edit') + if form.get_submit() == 'delete': + return redirect('delete') + if form.get_submit() == 'back': + return redirect('..') + + html_top('events', title = _('Event: %s') % self.event.title) + r = TemplateIO(html=True) + r += htmltext('

%s

') % _('Event: %s') % self.event.title + r += htmltext('

') + r += self.event.description + r += htmltext('

') + r += htmltext('') + + if self.event.more_infos: + r += htmltext('

') + r += self.event.more_infos + r += htmltext('

') + + r += form.render() + return r.getvalue() + + def edit(self): + form = self.form() + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + self.submit(form) + return redirect('..') + + html_top('events', title = _('Edit Event: %s') % self.event.title) + r = TemplateIO(html=True) + r += htmltext('

%s

') % _('Edit Event: %s') % self.event.title + r += form.render() + return r.getvalue() + + def form(self): + form = Form(enctype='multipart/form-data') + form.add(StringWidget, 'title', title = _('Title'), required = True, + value = self.event.title) + form.add(TextWidget, 'description', title = _('Description'), + cols = 70, rows = 10, + required = True, value = self.event.description) + form.add(StringWidget, 'url', title = _('URL'), required = False, + value = self.event.url, size = 40) + form.add(DateWidget, 'date_start', title = _('Start Date'), required = True, + value = strftime(misc.date_format(), self.event.date_start)) + form.add(DateWidget, 'date_end', title = _('End Date'), required = False, + value = strftime(misc.date_format(), self.event.date_end)) + form.add(TextWidget, 'location', title = _('Location'), + cols = 70, rows = 4, + required = False, value = self.event.location) + form.add(StringWidget, 'organizer', title = _('Organizer'), required = False, + value = self.event.organizer, size = 40) + form.add(TextWidget, 'more_infos', title = _('More informations'), + cols = 70, rows = 10, + required = False, value = self.event.more_infos) + form.add(TagsWidget, 'keywords', title = _('Keywords'), + value = self.event.keywords, size = 50, + known_tags = get_cfg('misc', {}).get('event_tags', get_default_event_tags())) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + return form + + def submit(self, form): + for k in ('title', 'description', 'url', 'date_start', 'date_end', + 'organizer', 'location', 'more_infos', 'keywords'): + widget = form.get_widget(k) + if widget: + if k in ('date_start', 'date_end'): + # convert dates to 9-item tuples + v = widget.parse() + if v: + setattr(self.event, k, time.strptime(v, misc.date_format())) + else: + setattr(self.event, k, None) + else: + setattr(self.event, k, widget.parse()) + self.event.store() + + def delete(self): + form = Form(enctype='multipart/form-data') + form.widgets.append(HtmlWidget('

%s

' % _( + 'You are about to irrevocably delete this event.'))) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + if form.get_submit() == 'cancel': + return redirect('..') + if not form.is_submitted() or form.has_errors(): + get_response().breadcrumb.append(('delete', _('Delete'))) + html_top('events', title = _('Delete Event')) + r = TemplateIO(html=True) + r += htmltext('

%s

') % _('Deleting Event: %s') % self.event.title + r += form.render() + return r.getvalue() + else: + self.event.remove_self() + return redirect('..') + + + + +class EventsDirectory(AccessControlled, Directory): + _q_exports = ['', 'new', 'listing', 'remote'] + label = N_('Events') + + remote = RemoteCalendarsDirectory() + + def _q_access(self): + user = get_request().user + if not user: + raise errors.AccessUnauthorizedError() + admin_role = get_cfg('aq-permissions', {}).get('events', None) + if not (user.is_admin or admin_role in (user.roles or [])): + raise errors.AccessForbiddenError( + public_msg = _('You are not allowed to access Events Management'), + location_hint = 'backoffice') + + get_response().breadcrumb.append(('events/', _('Events'))) + + + def _q_index(self): + html_top('events', _('Events')) + r = TemplateIO(html=True) + + r += htmltext('') + + r += htmltext('
') + + r += htmltext('
') + events = Event.select() + r += htmltext('

%s

') % _('Events') + if not events: + r += htmltext('

') + r += _('There is no event defined at the moment.') + r += htmltext('

') + r += htmltext('
    ') + for l in events: + event_id = l.id + r += htmltext('
  • ') % event_id + r += htmltext('%s') % (event_id, l.title) + r += ' - ' + r += l.format_date() + r += htmltext('

    ') + r += command_icon('%s/edit' % event_id, 'edit') + r += command_icon('%s/delete' % event_id, 'remove') + r += htmltext('

  • ') + r += htmltext('
') + r += htmltext('
') + r += htmltext('
') + + r += htmltext('
') + r += htmltext('
') + rcalendars = RemoteCalendar.select() + r += htmltext('

%s

') % _('Remote Calendars') + if not rcalendars: + r += htmltext('

') + r += _('There is no remote calendars defined at the moment.') + r += htmltext('

') + + r += htmltext('
    ') + for l in rcalendars: + rcal_id = l.id + r += htmltext('
  • ') % rcal_id + r += htmltext('%s') % (rcal_id, l.label) + r += htmltext('

    ') + r += l.url + if l.error: + r += htmltext('
    %s') % l.get_error_message() + r += htmltext('

    ') + r += htmltext('

    ') + r += command_icon('remote/%s/edit' % rcal_id, 'edit') + r += command_icon('remote/%s/delete' % rcal_id, 'remove') + r += htmltext('

  • ') + r += htmltext('
') + r += htmltext('
') + r += htmltext('
') + return r.getvalue() + + def new(self): + event_ui = EventDirectory(Event()) + + form = event_ui.form() + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + event_ui.submit(form) + return redirect('%s/' % event_ui.event.id) + + get_response().breadcrumb.append(('new', _('New Event'))) + html_top('events', title = _('New Event')) + r = TemplateIO(html=True) + r += htmltext('

%s

') % _('New Event') + r += form.render() + return r.getvalue() + + def _q_lookup(self, component): + try: + event = Event.get(component) + except KeyError: + raise errors.TraversalError() + get_response().breadcrumb.append((str(event.id), event.title)) + return EventDirectory(event) + + def listing(self): + return redirect('.') diff --git a/extra/modules/formpage.ptl b/extra/modules/formpage.ptl deleted file mode 100644 index dc11710..0000000 --- a/extra/modules/formpage.ptl +++ /dev/null @@ -1,36 +0,0 @@ -from quixote import get_publisher, get_request, redirect -from quixote.directory import Directory -from quixote.html import htmltext - -import os - -import wcs -import wcs.forms.root -from qommon import template -from qommon import errors -from qommon.form import * -from wcs.roles import logged_users_role - -from qommon import emails - -OldFormPage = wcs.forms.root.FormPage - -class AlternateFormPage(OldFormPage): - def step(self, *args, **kwargs): - steps_html = OldFormPage.step(self, *args, **kwargs) - steps_html = str(steps_html).replace('
    ', '

    %s

    \n
      ' % _('Steps')) - get_response().filter['gauche'] = steps_html - get_response().filter['steps'] = steps_html - return - -wcs.forms.root.FormPage = AlternateFormPage - - -OldFormsRootDirectory = wcs.forms.root.RootDirectory - -class AlternateFormsRootDirectory(OldFormsRootDirectory): - def form_list(self, *args, **kwargs): - form_list = OldFormsRootDirectory.form_list(self, *args, **kwargs) - return htmltext(str(form_list).replace('h2>', 'h3>')) - -wcs.forms.root.RootDirectory = AlternateFormsRootDirectory diff --git a/extra/modules/formpage.py b/extra/modules/formpage.py new file mode 100644 index 0000000..dc11710 --- /dev/null +++ b/extra/modules/formpage.py @@ -0,0 +1,36 @@ +from quixote import get_publisher, get_request, redirect +from quixote.directory import Directory +from quixote.html import htmltext + +import os + +import wcs +import wcs.forms.root +from qommon import template +from qommon import errors +from qommon.form import * +from wcs.roles import logged_users_role + +from qommon import emails + +OldFormPage = wcs.forms.root.FormPage + +class AlternateFormPage(OldFormPage): + def step(self, *args, **kwargs): + steps_html = OldFormPage.step(self, *args, **kwargs) + steps_html = str(steps_html).replace('
        ', '

        %s

        \n
          ' % _('Steps')) + get_response().filter['gauche'] = steps_html + get_response().filter['steps'] = steps_html + return + +wcs.forms.root.FormPage = AlternateFormPage + + +OldFormsRootDirectory = wcs.forms.root.RootDirectory + +class AlternateFormsRootDirectory(OldFormsRootDirectory): + def form_list(self, *args, **kwargs): + form_list = OldFormsRootDirectory.form_list(self, *args, **kwargs) + return htmltext(str(form_list).replace('h2>', 'h3>')) + +wcs.forms.root.RootDirectory = AlternateFormsRootDirectory diff --git a/extra/modules/msp_ui.ptl b/extra/modules/msp_ui.ptl deleted file mode 100644 index 347f2a5..0000000 --- a/extra/modules/msp_ui.ptl +++ /dev/null @@ -1,176 +0,0 @@ -import urllib -import urllib2 -import urlparse -import json -import base64 -import datetime - -from quixote import get_publisher, get_request, redirect, get_response, get_session_manager, get_session -from quixote.directory import AccessControlled, Directory - -import qommon.form -from qommon.misc import http_post_request, http_get_page - - -class MSPDirectory(Directory): - _q_exports = ['', 'pick', 'download'] - - @property - def msp_gateway_base_url(self): - return get_publisher().get_site_option('msp') - - @property - def authorize_url(self): - return urlparse.urljoin(self.msp_gateway_base_url, 'authorize/') - - @property - def access_token_url(self): - return urlparse.urljoin(self.msp_gateway_base_url, 'access_token/') - - @property - def documents_url(self): - return urlparse.urljoin(self.msp_gateway_base_url, 'documents/') - - @property - def document_url(self): - return urlparse.urljoin(self.msp_gateway_base_url, 'documents/%s/') - - @property - def client_id(self): - return get_publisher().get_site_option('msp_client_id') - - @property - def client_secret(self): - return get_publisher().get_site_option('msp_client_secret') - - def fix_document(self, document): - site_charset = get_publisher().site_charset - for key, value in document.iteritems(): - # date are returned as millisecond POSIX timestamp, - # it should be ISO-8601 instead - if key.endswith('Date') and value is not None: - document[key] = unicode(datetime.date.fromtimestamp(value // 1000)) - value = document[key] = document[key].replace(u'-', u'\u2011') - if isinstance(value, unicode) and key != 'content': - document[key] = value.encode(site_charset) - - def get_documents(self, access_token): - response, status, content, auth_header = http_get_page(self.documents_url, { - 'Authorization': 'Bearer %s' % access_token, - }); - documents = json.loads(content) - for document in documents: - self.fix_document(document) - return documents - - def get_document(self, doc_id, access_token): - response, status, content, auth_header = http_get_page( - self.document_url % doc_id, - { 'Authorization': 'Bearer %s' % access_token, } - ); - document = json.loads(content) - self.fix_document(document) - return document - - def authorize(self, self_url, scope): - params = { - 'redirect_uri': self_url, - 'response_type': 'code', - 'scope': scope, - } - return redirect('%s?%s' % ( - self.authorize_url, urllib.urlencode(params))) - - def access_token(self, self_url, code): - params = { - 'code': code, - 'client_id': self.client_id, - 'client_secret': self.client_secret, - 'grant_type': 'authorization_code', - 'redirect_uri': self_url, - } - response, status, data, auth_header = http_post_request(self.access_token_url, - urllib.urlencode(params), { 'Content-Type': 'application/x-www-form-urlencoded' }) - return json.loads(data)['access_token'] - - def pick (self): - request = get_request() - frontoffice_url = get_publisher().get_frontoffice_url() - self_url = frontoffice_url - self_url += '/msp/pick' - self_url = self_url.replace('://', '://iframe-') - - if 'code' not in request.form and 'error' not in request.form: - return self.authorize(self_url, 'LIST_DOCS') - access_token = self.access_token(self_url, request.form['code']) - return self.pick_display(access_token) - - def pick_display [html] (self, access_token): - request = get_request() - - get_response().add_javascript(['jquery.js', - 'tablesorter/jquery.tablesorter.min.js']) - get_response().add_javascript_code( - str('''$(function() { $("table.sortable").tablesorter(); });''')) - get_response().add_css_include('../js/tablesorter/themes/blue/style.css') - - frontoffice_url = get_publisher().get_frontoffice_url() - - '

          %s

          ' % _('Pick a file') - - if 'error' not in request.form: - documents = self.get_documents(access_token) - '' - '' - '' - '' % _('Filename') - '' % _('Expiration date') - '' - '' - '' - for document in documents: - '' - for key in ('name', 'expirationDate'): - '' - '' - '' - '
          %s%s
          ' % key - if key == 'name': - '' % \ - (frontoffice_url, document['id']) - '%s' % (document[key] or '') - if key == 'name': - '' - '
          ' - else: - '

          %s

          ' % _('Unable to access your mon.Service-Public.fr documents') - - def set_token [html] (self, token, document): - get_response().add_javascript(['jquery.js']) - get_response().page_template_key = 'iframe' - '' - '
          Token: %s
          ' % token - '' % (token, document['name']) - '' - - def download(self): - request = get_request() - assert 'doc_id' in request.form - doc_id = request.form['doc_id'] - frontoffice_url = get_publisher().get_frontoffice_url() - self_url = frontoffice_url - self_url += '/msp/download?%s' % urllib.urlencode({'doc_id': doc_id}) - if 'code' not in request.form and 'error' not in request.form: - return self.authorize(self_url, 'GET_DOC') - if 'error' in request.form: - return self.download_error() - else: - access_token = self.access_token(self_url, request.form['code']) - document = self.get_document(doc_id, access_token) - download = qommon.form.PicklableUpload(document['name'], - content_type='application/pdf') - download.__setstate__({ - 'data': base64.b64decode(document['content']), - }) - token = get_session().add_tempfile(download) - return self.set_token(token, document) diff --git a/extra/modules/msp_ui.py b/extra/modules/msp_ui.py new file mode 100644 index 0000000..78f82f1 --- /dev/null +++ b/extra/modules/msp_ui.py @@ -0,0 +1,183 @@ +import urllib +import urllib2 +import urlparse +import json +import base64 +import datetime + +from quixote import get_publisher, get_request, redirect, get_response, get_session_manager, get_session +from quixote.directory import AccessControlled, Directory +from quixote.html import TemplateIO, htmltext + +import qommon.form +from qommon.misc import http_post_request, http_get_page + + +class MSPDirectory(Directory): + _q_exports = ['', 'pick', 'download'] + + @property + def msp_gateway_base_url(self): + return get_publisher().get_site_option('msp') + + @property + def authorize_url(self): + return urlparse.urljoin(self.msp_gateway_base_url, 'authorize/') + + @property + def access_token_url(self): + return urlparse.urljoin(self.msp_gateway_base_url, 'access_token/') + + @property + def documents_url(self): + return urlparse.urljoin(self.msp_gateway_base_url, 'documents/') + + @property + def document_url(self): + return urlparse.urljoin(self.msp_gateway_base_url, 'documents/%s/') + + @property + def client_id(self): + return get_publisher().get_site_option('msp_client_id') + + @property + def client_secret(self): + return get_publisher().get_site_option('msp_client_secret') + + def fix_document(self, document): + site_charset = get_publisher().site_charset + for key, value in document.iteritems(): + # date are returned as millisecond POSIX timestamp, + # it should be ISO-8601 instead + if key.endswith('Date') and value is not None: + document[key] = unicode(datetime.date.fromtimestamp(value // 1000)) + value = document[key] = document[key].replace(u'-', u'\u2011') + if isinstance(value, unicode) and key != 'content': + document[key] = value.encode(site_charset) + + def get_documents(self, access_token): + response, status, content, auth_header = http_get_page(self.documents_url, { + 'Authorization': 'Bearer %s' % access_token, + }); + documents = json.loads(content) + for document in documents: + self.fix_document(document) + return documents + + def get_document(self, doc_id, access_token): + response, status, content, auth_header = http_get_page( + self.document_url % doc_id, + { 'Authorization': 'Bearer %s' % access_token, } + ); + document = json.loads(content) + self.fix_document(document) + return document + + def authorize(self, self_url, scope): + params = { + 'redirect_uri': self_url, + 'response_type': 'code', + 'scope': scope, + } + return redirect('%s?%s' % ( + self.authorize_url, urllib.urlencode(params))) + + def access_token(self, self_url, code): + params = { + 'code': code, + 'client_id': self.client_id, + 'client_secret': self.client_secret, + 'grant_type': 'authorization_code', + 'redirect_uri': self_url, + } + response, status, data, auth_header = http_post_request(self.access_token_url, + urllib.urlencode(params), { 'Content-Type': 'application/x-www-form-urlencoded' }) + return json.loads(data)['access_token'] + + def pick (self): + request = get_request() + frontoffice_url = get_publisher().get_frontoffice_url() + self_url = frontoffice_url + self_url += '/msp/pick' + self_url = self_url.replace('://', '://iframe-') + + if 'code' not in request.form and 'error' not in request.form: + return self.authorize(self_url, 'LIST_DOCS') + access_token = self.access_token(self_url, request.form['code']) + return self.pick_display(access_token) + + def pick_display(self, access_token): + r = TemplateIO(html=True) + + request = get_request() + + get_response().add_javascript(['jquery.js', + 'tablesorter/jquery.tablesorter.min.js']) + get_response().add_javascript_code( + str('''$(function() { $("table.sortable").tablesorter(); });''')) + get_response().add_css_include('../js/tablesorter/themes/blue/style.css') + + frontoffice_url = get_publisher().get_frontoffice_url() + + r += htmltext('

          %s

          ') % _('Pick a file') + + if 'error' not in request.form: + documents = self.get_documents(access_token) + r += htmltext('') + r += htmltext('') + r += htmltext('') + r += htmltext('') % _('Filename') + r += htmltext('') % _('Expiration date') + r += htmltext('') + r += htmltext('') + r += htmltext('') + for document in documents: + r += htmltext('') + for key in ('name', 'expirationDate'): + r += htmltext('') + r += htmltext('') + r += htmltext('') + r += htmltext('
          %s%s
          ' % key) + if key == 'name': + r += htmltext('' % \ + (frontoffice_url, document['id'])) + r += '%s' % (document[key] or '') + if key == 'name': + r += htmltext('') + r += htmltext('
          ') + else: + r += htmltext('

          %s

          ') % _('Unable to access your mon.Service-Public.fr documents') + return r.getvalue() + + def set_token(self, token, document): + get_response().add_javascript(['jquery.js']) + get_response().page_template_key = 'iframe' + r = TemplateIO(html=True) + r += htmltext('') + r += htmltext('
          Token: %s
          ') % token + r += htmltext('' % ( + token, document['name'])) + r += htmltext('') + return r.getvalue() + + def download(self): + request = get_request() + assert 'doc_id' in request.form + doc_id = request.form['doc_id'] + frontoffice_url = get_publisher().get_frontoffice_url() + self_url = frontoffice_url + self_url += '/msp/download?%s' % urllib.urlencode({'doc_id': doc_id}) + if 'code' not in request.form and 'error' not in request.form: + return self.authorize(self_url, 'GET_DOC') + if 'error' in request.form: + return self.download_error() + else: + access_token = self.access_token(self_url, request.form['code']) + document = self.get_document(doc_id, access_token) + download = qommon.form.PicklableUpload(document['name'], + content_type='application/pdf') + download.__setstate__({ + 'data': base64.b64decode(document['content']), + }) + token = get_session().add_tempfile(download) + return self.set_token(token, document) diff --git a/extra/modules/myspace.ptl b/extra/modules/myspace.ptl deleted file mode 100644 index f7bdd96..0000000 --- a/extra/modules/myspace.ptl +++ /dev/null @@ -1,715 +0,0 @@ -try: - import lasso -except ImportError: - pass - -import json - -from quixote import get_publisher, get_request, redirect, get_response, get_session_manager, get_session -from quixote.directory import AccessControlled, Directory -from quixote.util import StaticFile, FileStream - -from qommon import template -from qommon.form import * -from qommon import get_cfg, get_logger -from qommon import errors -from wcs.api import get_user_from_api_query_string - -import qommon.ident.password -from qommon.ident.password_accounts import PasswordAccount - -from qommon.admin.texts import TextsDirectory - -from wcs.formdef import FormDef -import root - -from announces import AnnounceSubscription -from strongbox import StrongboxItem, StrongboxType -from payments import Invoice, Regie, is_payment_supported -import msp_ui - -class MyInvoicesDirectory(Directory): - _q_exports = [''] - - def _q_traverse(self, path): - if not is_payment_supported(): - raise errors.TraversalError() - get_response().breadcrumb.append(('invoices/', _('Invoices'))) - return Directory._q_traverse(self, path) - - def _q_index [html] (self): - user = get_request().user - if not user or user.anonymous: - raise errors.AccessUnauthorizedError() - - template.html_top(_('Invoices')) - TextsDirectory.get_html_text('aq-myspace-invoice') - - get_session().display_message() - - invoices = [] - invoices.extend(Invoice.get_with_indexed_value( - str('user_id'), str(user.id))) - try: - invoices.extend(Invoice.get_with_indexed_value( - str('user_hash'), str(user.hash))) - except AttributeError: - pass - - def cmp_invoice(a, b): - t = cmp(a.regie_id, b.regie_id) - if t != 0: - return t - return -cmp(a.date, b.date) - - invoices.sort(cmp_invoice) - - last_regie_id = None - unpaid = False - for invoice in invoices: - if invoice.regie_id != last_regie_id: - if last_regie_id: - '' - if unpaid: - '' % _('Pay Selected Invoices') - '' - last_regie_id = invoice.regie_id - '

          %s

          ' % Regie.get(last_regie_id).label - unpaid = False - '
          ' % get_publisher().get_frontoffice_url() - '
            ' - - '
          • ' - if not (invoice.paid or invoice.canceled): - '' % invoice.id - unpaid = True - misc.localstrftime(invoice.date) - ' - ' - '%s' % invoice.subject - ' - ' - '%s' % invoice.amount - ' €' - ' - ' - button = '%s' % _('Pay') - if invoice.canceled: - _('canceled on %s') % misc.localstrftime(invoice.canceled_date) - ' - ' - button = _('Details') - if invoice.paid: - _('paid on %s') % misc.localstrftime(invoice.paid_date) - ' - ' - button = _('Details') - '%s' % (get_publisher().get_frontoffice_url(), - invoice.id, button) - '
          • ' - - if last_regie_id: - '
          ' - if unpaid: - '' % _('Pay Selected Invoices') - '
          ' - -class StrongboxDirectory(Directory): - _q_exports = ['', 'add', 'download', 'remove', 'pick', 'validate'] - - def _q_traverse(self, path): - if not get_cfg('misc', {}).get('aq-strongbox'): - raise errors.TraversalError() - get_response().breadcrumb.append(('strongbox/', _('Strongbox'))) - return Directory._q_traverse(self, path) - - def get_form(self): - types = [(x.id, x.label) for x in StrongboxType.select()] - form = Form(action='add', enctype='multipart/form-data') - form.add(StringWidget, 'description', title=_('Description'), size=60) - form.add(FileWidget, 'file', title=_('File'), required=True) - form.add(SingleSelectWidget, 'type_id', title=_('Document Type'), - options = [(None, _('Not specified'))] + types) - form.add(DateWidget, 'date_time', title = _('Document Date')) - form.add_submit('submit', _('Upload')) - return form - - def _q_index [html] (self): - template.html_top(_('Strongbox')) - - # TODO: a paragraph of explanations here could be useful - - sffiles = StrongboxItem.get_with_indexed_value( - str('user_id'), str(get_request().user.id)) - if sffiles: - '' - '' % ( - _('Type'), _('Expiration')) - else: - '

          ' - _('There is currently nothing in your strongbox.') - '

          ' - has_items_to_validate = False - for i, sffile in enumerate(sffiles): - expired = False - if not sffile.validated_time: - has_items_to_validate = True - continue - if sffile.expiration_time and sffile.expiration_time < time.localtime(): - expired = True - if i%2: - classnames = ['odd'] - else: - classnames = ['even'] - if expired: - classnames.append('expired') - '' % ' '.join(classnames) - '' - if sffile.type_id: - '' % StrongboxType.get(sffile.type_id).label - else: - '' - if sffile.expiration_time: - '' - else: - '' - '' - '' - - if has_items_to_validate: - '' % _('Proposed Items') - for sffile in sffiles: - if sffile.validated_time: - continue - if sffile.expiration_time and sffile.expiration_time < time.localtime(): - expired = True - if i%2: - classnames = ['odd'] - else: - classnames = ['even'] - if expired: - classnames.append('expired') - '' % ' '.join(classnames) - - '' - if sffile.type_id: - '' % StrongboxType.get(sffile.type_id).label - else: - '' - - if sffile.expiration_time: - '' - else: - '' - '' - '' - if sffiles: - '
          %s%s
          ' - sffile.get_display_name() - '%s-%s' % strftime(misc.date_format(), sffile.expiration_time) - if expired: - ' (%s)' % _('expired') - '-' - ' [%s] ' % (sffile.id, _('download')) - '[%s] ' % (sffile.id, _('remove')) - '

          %s

          ' - sffile.get_display_name() - '%s-%s' % strftime(misc.date_format(), sffile.expiration_time) - if expired: - ' (%s)' % _('expired') - '-' - ' [%s] ' % (sffile.id, _('download')) - ' [%s] ' % (sffile.id, _('validate')) - ' [%s] ' % (sffile.id, _('reject')) - '
          ' - - '

          %s

          ' % _('Add a file to the strongbox') - form = self.get_form() - form.render() - - def add(self): - form = self.get_form() - if not form.is_submitted(): - if get_request().form.get('mode') == 'pick': - return redirect('pick') - else: - return redirect('.') - - sffile = StrongboxItem() - sffile.user_id = get_request().user.id - sffile.description = form.get_widget('description').parse() - sffile.validated_time = time.localtime() - sffile.type_id = form.get_widget('type_id').parse() - v = form.get_widget('date_time').parse() - sffile.set_expiration_time_from_date(v) - sffile.store() - sffile.set_file(form.get_widget('file').parse()) - sffile.store() - if get_request().form.get('mode') == 'pick': - return redirect('pick') - else: - return redirect('.') - - def download(self): - id = get_request().form.get('id') - if not id: - raise errors.TraversalError() - try: - sffile = StrongboxItem.get(id) - except KeyError: - raise errors.TraversalError() - if str(sffile.user_id) != str(get_request().user.id): - raise errors.TraversalError() - - filename = sffile.file.filename - fd = file(filename) - size = os.path.getsize(filename) - response = get_response() - response.set_content_type('application/octet-stream') - response.set_header('content-disposition', 'attachment; filename="%s"' % sffile.file.base_filename) - return FileStream(fd, size) - - def validate(self): - id = get_request().form.get('id') - if not id: - raise errors.TraversalError() - try: - sffile = StrongboxItem.get(id) - except KeyError: - raise errors.TraversalError() - if str(sffile.user_id) != str(get_request().user.id): - raise errors.TraversalError() - sffile.validated_time = time.time() - sffile.store() - return redirect('.') - - def remove [html] (self): - id = get_request().form.get('id') - if not id: - raise errors.TraversalError() - try: - sffile = StrongboxItem.get(id) - except KeyError: - raise errors.TraversalError() - if str(sffile.user_id) != str(get_request().user.id): - raise errors.TraversalError() - - form = Form(enctype='multipart/form-data') - form.add_hidden('id', get_request().form.get('id')) - form.widgets.append(HtmlWidget('

          %s

          ' % _( - 'You are about to irrevocably delete this item from your strongbox.'))) - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - if form.get_submit() == 'cancel': - return redirect('.') - if not form.is_submitted() or form.has_errors(): - if sffile.type_id: - '

          %s

          ' % _('Deleting %(filetype)s: %(filename)s') % { - 'filetype': StrongboxType.get(sffile.type_id).label, - 'filename': sffile.get_display_name() - } - else: - '

          %s

          ' % _('Deleting %(filename)s') % {'filename': sffile.get_display_name()} - form.render() - else: - sffile.remove_self() - sffile.remove_file() - return redirect('.') - - def picked_file(self): - get_response().set_content_type('application/json') - sffile = StrongboxItem.get(get_request().form.get('val')) - sffile.file.fp = file(sffile.file.filename) - if sffile.user_id != get_request().user.id: - raise errors.TraversalError() - # XXX: this will copy the file, it would be quite nice if it was - # possible to just make it a symlink to the sffile - token = get_session().add_tempfile(sffile.file) - return json.dumps({'token': token, 'filename': sffile.file.base_filename}) - - def pick [html] (self): - if get_request().form.get('select') == 'true': - return self.picked_file() - root_url = get_publisher().get_root_url() - sffiles = StrongboxItem.get_with_indexed_value( - str('user_id'), str(get_request().user.id)) - '

          %s

          ' % _('Pick a file') - - if not sffiles: - '

          ' - _('You do not have any file in your strongbox at the moment.') - '

          ' - '
          ' - '%s' % (root_url, - _('Open Strongbox Management')) - '
          ' - else: - '
          ' - '
            ' - for sffile in sffiles: - '
          • ' % ( - sffile.id, sffile.get_display_name()) - ' [%s] ' % ( - root_url, sffile.id, _('view')) - '
          • ' - '
          ' - - '
          ' - '' % _('Cancel') - ' ' - '' % _('Pick') - '
          ' - '
          ' - -class JsonDirectory(Directory): - '''Export of several lists in json, related to the current user or the - SAMLv2 NameID we'd get in the URL''' - - _q_exports = ['forms'] - - user = None - - def _q_traverse(self, path): - self.user = get_user_from_api_query_string() or get_request().user - if not self.user: - raise errors.AccessUnauthorizedError() - return Directory._q_traverse(self, path) - - def forms(self): - formdefs = FormDef.select(lambda x: not x.is_disabled(), order_by = 'name') - user_forms = [] - for formdef in formdefs: - user_forms.extend(formdef.data_class().get_with_indexed_value( - 'user_id', self.user.id)) - try: - user_forms.extend(formdef.data_class().get_with_indexed_value( - 'user_hash', self.user.hash)) - except AttributeError: - pass - user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) - - get_response().set_content_type('application/json') - - - forms_output = [] - for form in user_forms: - visible_status = form.get_visible_status(user=self.user) - # skip hidden forms - if not visible_status: - continue - name = form.formdef.name - id = form.get_display_id() - status = visible_status.name - title = _('%(name)s #%(id)s (%(status)s)') % { - 'name': name, - 'id': id, - 'status': status - } - url = form.get_url() - d = { 'title': title, 'url': url } - d.update(form.get_substitution_variables(minimal=True)) - forms_output.append(d) - return json.dumps(forms_output) - - -class MyspaceDirectory(Directory): - _q_exports = ['', 'profile', 'new', 'password', 'remove', 'announces', - 'strongbox', 'invoices', 'json', 'msp'] - - msp = msp_ui.MSPDirectory() - strongbox = StrongboxDirectory() - invoices = MyInvoicesDirectory() - json = JsonDirectory() - - def _q_traverse(self, path): - if (path[0] not in ('new', 'json')) and (not get_request().user or get_request().user.anonymous): - raise errors.AccessUnauthorizedError() - get_response().filter['bigdiv'] = 'profile' - get_response().breadcrumb.append(('myspace/', _('My Space'))) - - # Migrate custom text settings - texts_cfg = get_cfg('texts', {}) - if 'text-aq-top-of-profile' in texts_cfg and ( - not 'text-top-of-profile' in texts_cfg): - texts_cfg['text-top-of-profile'] = texts_cfg['text-aq-top-of-profile'] - del texts_cfg['text-aq-top-of-profile'] - get_publisher().write_cfg() - - return Directory._q_traverse(self, path) - - - def _q_index [html] (self): - user = get_request().user - if not user: - raise errors.AccessUnauthorizedError() - template.html_top(_('My Space')) - if user.anonymous: - return redirect('new') - - user_formdef = user.get_formdef() - - user_forms = [] - if user: - formdefs = FormDef.select(lambda x: not x.is_disabled(), order_by = 'name') - user_forms = [] - for formdef in formdefs: - user_forms.extend(formdef.data_class().get_with_indexed_value( - 'user_id', user.id)) - try: - user_forms.extend(formdef.data_class().get_with_indexed_value( - 'user_hash', user.hash)) - except AttributeError: - pass - user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) - - profile_links = [] - if not get_cfg('sp', {}).get('idp-manage-user-attributes', False): - if user_formdef: - profile_links.append('%s' % _('My Profile')) - if user_forms: - profile_links.append('%s' % _('My Forms')) - if get_cfg('misc', {}).get('aq-strongbox'): - profile_links.append('%s' % _('My Strongbox')) - if is_payment_supported(): - profile_links.append('%s' % _('My Invoices')) - - root_url = get_publisher().get_root_url() - if user.can_go_in_backoffice(): - profile_links.append('%s' % (root_url, _('Back office'))) - if user.is_admin: - profile_links.append('%s' % (root_url, _('Admin'))) - - if profile_links: - '' - - if not get_cfg('sp', {}).get('idp-manage-user-attributes', False): - if user_formdef: - self._my_profile(user_formdef, user) - - self._index_buttons(user_formdef) - - try: - x = PasswordAccount.get_on_index(get_request().user.id, str('user_id')) - except KeyError: - pass - else: - '

          ' - _('You can delete your account freely from the services portal. ' - 'This action is irreversible; it will destruct your personal ' - 'datas and destruct the access to your request history.') - ' %s.' % _('Delete My Account') - '

          ' - - options = get_cfg('misc', {}).get('announce_themes') - if options: - try: - subscription = AnnounceSubscription.get_on_index( - get_request().user.id, str('user_id')) - except KeyError: - pass - else: - '

          %s

          ' % _( - 'Edit my Subscription to Announces') - - if user_forms: - '

          %s

          ' % _('My Forms') - root.FormsRootDirectory().user_forms(user_forms) - - def _my_profile [html] (self, user_formdef, user): - '

          %s

          ' % _('My Profile') - - TextsDirectory.get_html_text('top-of-profile') - - if user.form_data: - '
            ' - for field in user_formdef.fields: - if not hasattr(field, str('get_view_value')): - continue - value = user.form_data.get(field.id) - '
          • ' - field.label - ' : ' - if value: - field.get_view_value(value) - '
          • ' - '
          ' - else: - '

          %s

          ' % _('Empty profile') - - def _index_buttons [html] (self, form_data): - passwords_cfg = get_cfg('passwords', {}) - ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0] - if get_session().lasso_session_dump: - ident_method = 'idp' - - if form_data and ident_method != 'idp': - '

          %s

          ' % _('Edit My Profile') - - if ident_method == 'password' and passwords_cfg.get('can_change', False): - '

          %s

          ' % _('Change My Password') - - def profile [html] (self): - user = get_request().user - if not user or user.anonymous: - raise errors.AccessUnauthorizedError() - - form = Form(enctype = 'multipart/form-data') - formdef = user.get_formdef() - formdef.add_fields_to_form(form, form_data = user.form_data) - - form.add_submit('submit', _('Apply Changes')) - form.add_submit('cancel', _('Cancel')) - - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - self.profile_submit(form, formdef) - return redirect('.') - - template.html_top(_('Edit Profile')) - form.render() - - def profile_submit(self, form, formdef): - user = get_request().user - data = formdef.get_data(form) - - user.set_attributes_from_formdata(data) - user.form_data = data - - user.store() - - def password [html] (self): - ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0] - if ident_method != 'password': - raise errors.TraversalError() - - user = get_request().user - if not user or user.anonymous: - raise errors.AccessUnauthorizedError() - - form = Form(enctype = 'multipart/form-data') - form.add(PasswordWidget, 'new_password', title = _('New Password'), - required=True) - form.add(PasswordWidget, 'new2_password', title = _('New Password (confirm)'), - required=True) - - form.add_submit('submit', _('Change Password')) - form.add_submit('cancel', _('Cancel')) - - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - qommon.ident.password.check_password(form, 'new_password') - new_password = form.get_widget('new_password').parse() - new2_password = form.get_widget('new2_password').parse() - if new_password != new2_password: - form.set_error('new2_password', _('Passwords do not match')) - - if form.is_submitted() and not form.has_errors(): - self.submit_password(new_password) - return redirect('.') - - template.html_top(_('Change Password')) - form.render() - - def submit_password(self, new_password): - passwords_cfg = get_cfg('passwords', {}) - account = PasswordAccount.get(get_session().username) - account.hashing_algo = passwords_cfg.get('hashing_algo') - account.set_password(new_password) - account.store() - - def new [html] (self): - if not get_request().user or not get_request().user.anonymous: - raise errors.AccessUnauthorizedError() - - form = Form(enctype = 'multipart/form-data') - formdef = get_publisher().user_class.get_formdef() - if formdef: - formdef.add_fields_to_form(form) - else: - get_logger().error('missing user formdef (in myspace/new)') - - form.add_submit('submit', _('Register')) - - if form.is_submitted() and not form.has_errors(): - user = get_publisher().user_class() - data = formdef.get_data(form) - user.set_attributes_from_formdata(data) - user.name_identifiers = get_request().user.name_identifiers - user.lasso_dump = get_request().user.lasso_dump - user.set_attributes_from_formdata(data) - user.form_data = data - user.store() - get_session().set_user(user.id) - root_url = get_publisher().get_root_url() - return redirect('%smyspace' % root_url) - - template.html_top(_('Welcome')) - form.render() - - - def remove [html] (self): - user = get_request().user - if not user or user.anonymous: - raise errors.AccessUnauthorizedError() - - form = Form(enctype = 'multipart/form-data') - form.widgets.append(HtmlWidget('

          %s

          ' % _( - 'Are you really sure you want to remove your account?'))) - form.add_submit('submit', _('Remove my account')) - form.add_submit('cancel', _('Cancel')) - - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - user = get_request().user - account = PasswordAccount.get_on_index(user.id, str('user_id')) - get_session_manager().expire_session() - account.remove_self() - return redirect(get_publisher().get_root_url()) - - template.html_top(_('Removing Account')) - form.render() - - def announces [html] (self): - options = get_cfg('misc', {}).get('announce_themes') - if not options: - raise errors.TraversalError() - subscription = AnnounceSubscription.get_on_index(get_request().user.id, str('user_id')) - if not subscription: - raise errors.TraversalError() - - if subscription.enabled_themes is None: - enabled_themes = options - else: - enabled_themes = subscription.enabled_themes - - form = Form(enctype = 'multipart/form-data') - form.add(CheckboxesWidget, 'themes', title=_('Announce Themes'), - value=enabled_themes, elements=options, - inline=False, required=False) - - form.add_submit('submit', _('Apply Changes')) - form.add_submit('cancel', _('Cancel')) - - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - chosen_themes = form.get_widget('themes').parse() - if chosen_themes == options: - chosen_themes = None - subscription.enabled_themes = chosen_themes - subscription.store() - return redirect('.') - - template.html_top() - get_response().breadcrumb.append(('announces', _('Announce Subscription'))) - form.render() - - -TextsDirectory.register('aq-myspace-invoice', - N_('Message on top of invoices page'), - category = N_('Invoices')) - diff --git a/extra/modules/myspace.py b/extra/modules/myspace.py new file mode 100644 index 0000000..2d5e3db --- /dev/null +++ b/extra/modules/myspace.py @@ -0,0 +1,735 @@ +try: + import lasso +except ImportError: + pass + +import json + +from quixote import get_publisher, get_request, redirect, get_response, get_session_manager, get_session +from quixote.directory import AccessControlled, Directory +from quixote.html import TemplateIO, htmltext +from quixote.util import StaticFile, FileStream + +from qommon import template +from qommon.form import * +from qommon import get_cfg, get_logger +from qommon import errors +from wcs.api import get_user_from_api_query_string + +import qommon.ident.password +from qommon.ident.password_accounts import PasswordAccount + +from qommon.admin.texts import TextsDirectory + +from wcs.formdef import FormDef +import root + +from announces import AnnounceSubscription +from strongbox import StrongboxItem, StrongboxType +from payments import Invoice, Regie, is_payment_supported +import msp_ui + +class MyInvoicesDirectory(Directory): + _q_exports = [''] + + def _q_traverse(self, path): + if not is_payment_supported(): + raise errors.TraversalError() + get_response().breadcrumb.append(('invoices/', _('Invoices'))) + return Directory._q_traverse(self, path) + + def _q_index(self): + user = get_request().user + if not user or user.anonymous: + raise errors.AccessUnauthorizedError() + + template.html_top(_('Invoices')) + r = TemplateIO(html=True) + r += TextsDirectory.get_html_text('aq-myspace-invoice') + + r += get_session().display_message() + + invoices = [] + invoices.extend(Invoice.get_with_indexed_value( + str('user_id'), str(user.id))) + try: + invoices.extend(Invoice.get_with_indexed_value( + str('user_hash'), str(user.hash))) + except AttributeError: + pass + + def cmp_invoice(a, b): + t = cmp(a.regie_id, b.regie_id) + if t != 0: + return t + return -cmp(a.date, b.date) + + invoices.sort(cmp_invoice) + + last_regie_id = None + unpaid = False + for invoice in invoices: + if invoice.regie_id != last_regie_id: + if last_regie_id: + r += htmltext('') + if unpaid: + r += htmltext('') % _('Pay Selected Invoices') + r += htmltext('') + last_regie_id = invoice.regie_id + r += htmltext('

          %s

          ') % Regie.get(last_regie_id).label + unpaid = False + r += htmltext('
          ' % get_publisher().get_frontoffice_url()) + r += htmltext('
            ') + + r += htmltext('
          • ') + if not (invoice.paid or invoice.canceled): + r += htmltext('' % invoice.id) + unpaid = True + r += misc.localstrftime(invoice.date) + r += ' - ' + r += '%s' % invoice.subject + r += ' - ' + r += '%s' % invoice.amount + r += ' €' + r += ' - ' + button = '%s' % _('Pay') + if invoice.canceled: + r += _('canceled on %s') % misc.localstrftime(invoice.canceled_date) + r += ' - ' + button = _('Details') + if invoice.paid: + r += _('paid on %s') % misc.localstrftime(invoice.paid_date) + r += ' - ' + button = _('Details') + r += htmltext('%s' % (get_publisher().get_frontoffice_url(), + invoice.id, button)) + r += htmltext('
          • ') + + if last_regie_id: + r += htmltext('
          ') + if unpaid: + r += htmltext('') % _('Pay Selected Invoices') + r += htmltext('
          ') + + return r.getvalue() + + +class StrongboxDirectory(Directory): + _q_exports = ['', 'add', 'download', 'remove', 'pick', 'validate'] + + def _q_traverse(self, path): + if not get_cfg('misc', {}).get('aq-strongbox'): + raise errors.TraversalError() + get_response().breadcrumb.append(('strongbox/', _('Strongbox'))) + return Directory._q_traverse(self, path) + + def get_form(self): + types = [(x.id, x.label) for x in StrongboxType.select()] + form = Form(action='add', enctype='multipart/form-data') + form.add(StringWidget, 'description', title=_('Description'), size=60) + form.add(FileWidget, 'file', title=_('File'), required=True) + form.add(SingleSelectWidget, 'type_id', title=_('Document Type'), + options = [(None, _('Not specified'))] + types) + form.add(DateWidget, 'date_time', title = _('Document Date')) + form.add_submit('submit', _('Upload')) + return form + + def _q_index(self): + template.html_top(_('Strongbox')) + r = TemplateIO(html=True) + + # TODO: a paragraph of explanations here could be useful + + sffiles = StrongboxItem.get_with_indexed_value( + str('user_id'), str(get_request().user.id)) + if sffiles: + r += htmltext('') + r += htmltext('') % ( + _('Type'), _('Expiration')) + else: + r += htmltext('

          ') + r += _('There is currently nothing in your strongbox.') + r += htmltext('

          ') + has_items_to_validate = False + for i, sffile in enumerate(sffiles): + expired = False + if not sffile.validated_time: + has_items_to_validate = True + continue + if sffile.expiration_time and sffile.expiration_time < time.localtime(): + expired = True + if i%2: + classnames = ['odd'] + else: + classnames = ['even'] + if expired: + classnames.append('expired') + r += htmltext('') % ' '.join(classnames) + r += htmltext('') + if sffile.type_id: + r += htmltext('') % StrongboxType.get(sffile.type_id).label + else: + r += htmltext('') + if sffile.expiration_time: + r += htmltext('') + else: + r += htmltext('') + r += htmltext('') + r += htmltext('') + + if has_items_to_validate: + r += htmltext('') % _('Proposed Items') + for sffile in sffiles: + if sffile.validated_time: + continue + if sffile.expiration_time and sffile.expiration_time < time.localtime(): + expired = True + if i%2: + classnames = ['odd'] + else: + classnames = ['even'] + if expired: + classnames.append('expired') + r += htmltext('') % ' '.join(classnames) + + r += htmltext('') + if sffile.type_id: + r += htmltext('') % StrongboxType.get(sffile.type_id).label + else: + r += htmltext('') + + if sffile.expiration_time: + r += htmltext('') + else: + r += htmltext('') + r += htmltext('') + r += htmltext('') + if sffiles: + r += htmltext('
          %s%s
          ') + r += sffile.get_display_name() + r += htmltext('%s-%s') % strftime(misc.date_format(), sffile.expiration_time) + if expired: + r += ' (%s)' % _('expired') + r += htmltext('-') + r += htmltext(' [%s] ') % (sffile.id, _('download')) + r += htmltext('[%s] ') % (sffile.id, _('remove')) + r += htmltext('

          %s

          ') + r += sffile.get_display_name() + r += htmltext('%s-%s') % strftime(misc.date_format(), sffile.expiration_time) + if expired: + r += ' (%s)' % _('expired') + r += htmltext('-') + r += htmltext(' [%s] ') % (sffile.id, _('download')) + r += htmltext(' [%s] ') % (sffile.id, _('validate')) + r += htmltext(' [%s] ') % (sffile.id, _('reject')) + r += htmltext('
          ') + + r += htmltext('

          %s

          ') % _('Add a file to the strongbox') + form = self.get_form() + r += form.render() + return r.getvalue() + + def add(self): + form = self.get_form() + if not form.is_submitted(): + if get_request().form.get('mode') == 'pick': + return redirect('pick') + else: + return redirect('.') + + sffile = StrongboxItem() + sffile.user_id = get_request().user.id + sffile.description = form.get_widget('description').parse() + sffile.validated_time = time.localtime() + sffile.type_id = form.get_widget('type_id').parse() + v = form.get_widget('date_time').parse() + sffile.set_expiration_time_from_date(v) + sffile.store() + sffile.set_file(form.get_widget('file').parse()) + sffile.store() + if get_request().form.get('mode') == 'pick': + return redirect('pick') + else: + return redirect('.') + + def download(self): + id = get_request().form.get('id') + if not id: + raise errors.TraversalError() + try: + sffile = StrongboxItem.get(id) + except KeyError: + raise errors.TraversalError() + if str(sffile.user_id) != str(get_request().user.id): + raise errors.TraversalError() + + filename = sffile.file.filename + fd = file(filename) + size = os.path.getsize(filename) + response = get_response() + response.set_content_type('application/octet-stream') + response.set_header('content-disposition', 'attachment; filename="%s"' % sffile.file.base_filename) + return FileStream(fd, size) + + def validate(self): + id = get_request().form.get('id') + if not id: + raise errors.TraversalError() + try: + sffile = StrongboxItem.get(id) + except KeyError: + raise errors.TraversalError() + if str(sffile.user_id) != str(get_request().user.id): + raise errors.TraversalError() + sffile.validated_time = time.time() + sffile.store() + return redirect('.') + + def remove(self): + id = get_request().form.get('id') + if not id: + raise errors.TraversalError() + try: + sffile = StrongboxItem.get(id) + except KeyError: + raise errors.TraversalError() + if str(sffile.user_id) != str(get_request().user.id): + raise errors.TraversalError() + + r = TemplateIO(html=True) + form = Form(enctype='multipart/form-data') + form.add_hidden('id', get_request().form.get('id')) + form.widgets.append(HtmlWidget('

          %s

          ' % _( + 'You are about to irrevocably delete this item from your strongbox.'))) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + if form.get_submit() == 'cancel': + return redirect('.') + if not form.is_submitted() or form.has_errors(): + if sffile.type_id: + r += htmltext('

          %s

          ') % _('Deleting %(filetype)s: %(filename)s') % { + 'filetype': StrongboxType.get(sffile.type_id).label, + 'filename': sffile.get_display_name() + } + else: + r += htmltext('

          %s

          ') % _('Deleting %(filename)s') % {'filename': sffile.get_display_name()} + r += form.render() + return r.getvalue() + else: + sffile.remove_self() + sffile.remove_file() + return redirect('.') + + def picked_file(self): + get_response().set_content_type('application/json') + sffile = StrongboxItem.get(get_request().form.get('val')) + sffile.file.fp = file(sffile.file.filename) + if sffile.user_id != get_request().user.id: + raise errors.TraversalError() + # XXX: this will copy the file, it would be quite nice if it was + # possible to just make it a symlink to the sffile + token = get_session().add_tempfile(sffile.file) + return json.dumps({'token': token, 'filename': sffile.file.base_filename}) + + def pick(self): + if get_request().form.get('select') == 'true': + return self.picked_file() + r = TemplateIO(html=True) + root_url = get_publisher().get_root_url() + sffiles = StrongboxItem.get_with_indexed_value( + str('user_id'), str(get_request().user.id)) + r += htmltext('

          %s

          ') % _('Pick a file') + + if not sffiles: + r += htmltext('

          ') + r += _('You do not have any file in your strongbox at the moment.') + r += htmltext('

          ') + r += htmltext('
          ') + r += htmltext('%s') % (root_url, + _('Open Strongbox Management')) + r += htmltext('
          ') + else: + r += htmltext('
          ') + r += htmltext('
            ') + for sffile in sffiles: + r += htmltext('
          • ') % ( + sffile.id, sffile.get_display_name()) + r += htmltext(' [%s] ') % ( + root_url, sffile.id, _('view')) + r += htmltext('
          • ') + r += htmltext('
          ') + + r += htmltext('
          ') + r += htmltext('') % _('Cancel') + r += ' ' + r += htmltext('') % _('Pick') + r += htmltext('
          ') + r += htmltext('
          ') + return r.getvalue() + + +class JsonDirectory(Directory): + '''Export of several lists in json, related to the current user or the + SAMLv2 NameID we'd get in the URL''' + + _q_exports = ['forms'] + + user = None + + def _q_traverse(self, path): + self.user = get_user_from_api_query_string() or get_request().user + if not self.user: + raise errors.AccessUnauthorizedError() + return Directory._q_traverse(self, path) + + def forms(self): + formdefs = FormDef.select(lambda x: not x.is_disabled(), order_by = 'name') + user_forms = [] + for formdef in formdefs: + user_forms.extend(formdef.data_class().get_with_indexed_value( + 'user_id', self.user.id)) + try: + user_forms.extend(formdef.data_class().get_with_indexed_value( + 'user_hash', self.user.hash)) + except AttributeError: + pass + user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) + + get_response().set_content_type('application/json') + + + forms_output = [] + for form in user_forms: + visible_status = form.get_visible_status(user=self.user) + # skip hidden forms + if not visible_status: + continue + name = form.formdef.name + id = form.get_display_id() + status = visible_status.name + title = _('%(name)s #%(id)s (%(status)s)') % { + 'name': name, + 'id': id, + 'status': status + } + url = form.get_url() + d = { 'title': title, 'url': url } + d.update(form.get_substitution_variables(minimal=True)) + forms_output.append(d) + return json.dumps(forms_output) + + +class MyspaceDirectory(Directory): + _q_exports = ['', 'profile', 'new', 'password', 'remove', 'announces', + 'strongbox', 'invoices', 'json', 'msp'] + + msp = msp_ui.MSPDirectory() + strongbox = StrongboxDirectory() + invoices = MyInvoicesDirectory() + json = JsonDirectory() + + def _q_traverse(self, path): + if (path[0] not in ('new', 'json')) and (not get_request().user or get_request().user.anonymous): + raise errors.AccessUnauthorizedError() + get_response().filter['bigdiv'] = 'profile' + get_response().breadcrumb.append(('myspace/', _('My Space'))) + + # Migrate custom text settings + texts_cfg = get_cfg('texts', {}) + if 'text-aq-top-of-profile' in texts_cfg and ( + not 'text-top-of-profile' in texts_cfg): + texts_cfg['text-top-of-profile'] = texts_cfg['text-aq-top-of-profile'] + del texts_cfg['text-aq-top-of-profile'] + get_publisher().write_cfg() + + return Directory._q_traverse(self, path) + + + def _q_index(self): + user = get_request().user + if not user: + raise errors.AccessUnauthorizedError() + template.html_top(_('My Space')) + r = TemplateIO(html=True) + if user.anonymous: + return redirect('new') + + user_formdef = user.get_formdef() + + user_forms = [] + if user: + formdefs = FormDef.select(lambda x: not x.is_disabled(), order_by = 'name') + user_forms = [] + for formdef in formdefs: + user_forms.extend(formdef.data_class().get_with_indexed_value( + 'user_id', user.id)) + try: + user_forms.extend(formdef.data_class().get_with_indexed_value( + 'user_hash', user.hash)) + except AttributeError: + pass + user_forms.sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) + + profile_links = [] + if not get_cfg('sp', {}).get('idp-manage-user-attributes', False): + if user_formdef: + profile_links.append('%s' % _('My Profile')) + if user_forms: + profile_links.append('%s' % _('My Forms')) + if get_cfg('misc', {}).get('aq-strongbox'): + profile_links.append('%s' % _('My Strongbox')) + if is_payment_supported(): + profile_links.append('%s' % _('My Invoices')) + + root_url = get_publisher().get_root_url() + if user.can_go_in_backoffice(): + profile_links.append('%s' % (root_url, _('Back office'))) + if user.is_admin: + profile_links.append('%s' % (root_url, _('Admin'))) + + if profile_links: + r += htmltext('') + + if not get_cfg('sp', {}).get('idp-manage-user-attributes', False): + if user_formdef: + r += self._my_profile(user_formdef, user) + + r += self._index_buttons(user_formdef) + + try: + x = PasswordAccount.get_on_index(get_request().user.id, str('user_id')) + except KeyError: + pass + else: + r += htmltext('

          ') + r += _('You can delete your account freely from the services portal. ' + 'This action is irreversible; it will destruct your personal ' + 'datas and destruct the access to your request history.') + r += htmltext(' %s.') % _('Delete My Account') + r += htmltext('

          ') + + options = get_cfg('misc', {}).get('announce_themes') + if options: + try: + subscription = AnnounceSubscription.get_on_index( + get_request().user.id, str('user_id')) + except KeyError: + pass + else: + r += htmltext('

          %s

          ') % _( + 'Edit my Subscription to Announces') + + if user_forms: + r += htmltext('

          %s

          ') % _('My Forms') + r += root.FormsRootDirectory().user_forms(user_forms) + + return r.getvalue() + + def _my_profile(self, user_formdef, user): + r = TemplateIO(html=True) + r += htmltext('

          %s

          ') % _('My Profile') + + r += TextsDirectory.get_html_text('top-of-profile') + + if user.form_data: + r += htmltext('
            ') + for field in user_formdef.fields: + if not hasattr(field, str('get_view_value')): + continue + value = user.form_data.get(field.id) + r += htmltext('
          • ') + r += field.label + r += ' : ' + if value: + r += field.get_view_value(value) + r += htmltext('
          • ') + r += htmltext('
          ') + else: + r += htmltext('

          %s

          ') % _('Empty profile') + return r.getvalue() + + def _index_buttons(self, form_data): + r = TemplateIO(html=True) + passwords_cfg = get_cfg('passwords', {}) + ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0] + if get_session().lasso_session_dump: + ident_method = 'idp' + + if form_data and ident_method != 'idp': + r += htmltext('

          %s

          ') % _('Edit My Profile') + + if ident_method == 'password' and passwords_cfg.get('can_change', False): + r += htmltext('

          %s

          ') % _('Change My Password') + + return r.getvalue() + + def profile(self): + user = get_request().user + if not user or user.anonymous: + raise errors.AccessUnauthorizedError() + + form = Form(enctype = 'multipart/form-data') + formdef = user.get_formdef() + formdef.add_fields_to_form(form, form_data = user.form_data) + + form.add_submit('submit', _('Apply Changes')) + form.add_submit('cancel', _('Cancel')) + + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + self.profile_submit(form, formdef) + return redirect('.') + + template.html_top(_('Edit Profile')) + return form.render() + + def profile_submit(self, form, formdef): + user = get_request().user + data = formdef.get_data(form) + + user.set_attributes_from_formdata(data) + user.form_data = data + + user.store() + + def password(self): + ident_method = get_cfg('identification', {}).get('methods', ['idp'])[0] + if ident_method != 'password': + raise errors.TraversalError() + + user = get_request().user + if not user or user.anonymous: + raise errors.AccessUnauthorizedError() + + form = Form(enctype = 'multipart/form-data') + form.add(PasswordWidget, 'new_password', title = _('New Password'), + required=True) + form.add(PasswordWidget, 'new2_password', title = _('New Password (confirm)'), + required=True) + + form.add_submit('submit', _('Change Password')) + form.add_submit('cancel', _('Cancel')) + + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + qommon.ident.password.check_password(form, 'new_password') + new_password = form.get_widget('new_password').parse() + new2_password = form.get_widget('new2_password').parse() + if new_password != new2_password: + form.set_error('new2_password', _('Passwords do not match')) + + if form.is_submitted() and not form.has_errors(): + self.submit_password(new_password) + return redirect('.') + + template.html_top(_('Change Password')) + return form.render() + + def submit_password(self, new_password): + passwords_cfg = get_cfg('passwords', {}) + account = PasswordAccount.get(get_session().username) + account.hashing_algo = passwords_cfg.get('hashing_algo') + account.set_password(new_password) + account.store() + + def new(self): + if not get_request().user or not get_request().user.anonymous: + raise errors.AccessUnauthorizedError() + + form = Form(enctype = 'multipart/form-data') + formdef = get_publisher().user_class.get_formdef() + if formdef: + formdef.add_fields_to_form(form) + else: + get_logger().error('missing user formdef (in myspace/new)') + + form.add_submit('submit', _('Register')) + + if form.is_submitted() and not form.has_errors(): + user = get_publisher().user_class() + data = formdef.get_data(form) + user.set_attributes_from_formdata(data) + user.name_identifiers = get_request().user.name_identifiers + user.lasso_dump = get_request().user.lasso_dump + user.set_attributes_from_formdata(data) + user.form_data = data + user.store() + get_session().set_user(user.id) + root_url = get_publisher().get_root_url() + return redirect('%smyspace' % root_url) + + template.html_top(_('Welcome')) + return form.render() + + + def remove(self): + user = get_request().user + if not user or user.anonymous: + raise errors.AccessUnauthorizedError() + + form = Form(enctype = 'multipart/form-data') + form.widgets.append(HtmlWidget('

          %s

          ' % _( + 'Are you really sure you want to remove your account?'))) + form.add_submit('submit', _('Remove my account')) + form.add_submit('cancel', _('Cancel')) + + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + user = get_request().user + account = PasswordAccount.get_on_index(user.id, str('user_id')) + get_session_manager().expire_session() + account.remove_self() + return redirect(get_publisher().get_root_url()) + + template.html_top(_('Removing Account')) + return form.render() + + def announces(self): + options = get_cfg('misc', {}).get('announce_themes') + if not options: + raise errors.TraversalError() + subscription = AnnounceSubscription.get_on_index(get_request().user.id, str('user_id')) + if not subscription: + raise errors.TraversalError() + + if subscription.enabled_themes is None: + enabled_themes = options + else: + enabled_themes = subscription.enabled_themes + + form = Form(enctype = 'multipart/form-data') + form.add(CheckboxesWidget, 'themes', title=_('Announce Themes'), + value=enabled_themes, elements=options, + inline=False, required=False) + + form.add_submit('submit', _('Apply Changes')) + form.add_submit('cancel', _('Cancel')) + + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + chosen_themes = form.get_widget('themes').parse() + if chosen_themes == options: + chosen_themes = None + subscription.enabled_themes = chosen_themes + subscription.store() + return redirect('.') + + template.html_top() + get_response().breadcrumb.append(('announces', _('Announce Subscription'))) + return form.render() + + +TextsDirectory.register('aq-myspace-invoice', + N_('Message on top of invoices page'), + category = N_('Invoices')) + diff --git a/extra/modules/payments_ui.ptl b/extra/modules/payments_ui.ptl deleted file mode 100644 index a6f1385..0000000 --- a/extra/modules/payments_ui.ptl +++ /dev/null @@ -1,571 +0,0 @@ -import time -import pprint -import locale -import decimal -import datetime - -from quixote import get_request, get_response, get_session, redirect -from quixote.directory import Directory, AccessControlled - -import wcs -import wcs.admin.root -from wcs.backoffice.menu import * -from wcs.formdef import FormDef - -from qommon import errors, misc, template, get_logger -from qommon.form import * -from qommon.strftime import strftime -from qommon.admin.emails import EmailsDirectory - -from payments import (eopayment, Regie, is_payment_supported, Invoice, - Transaction, notify_paid_invoice) - -from qommon.admin.texts import TextsDirectory - -if not set: - from sets import Set as set - -def invoice_as_html [html] (invoice): - '
          ' - '

          %s

          ' % _('Invoice: %s') % invoice.subject - '

          %s' % _('Amount: %s') % invoice.amount - ' €

          ' - '' - if invoice.formdef_id and invoice.formdata_id and \ - get_session().user == invoice.user_id: - formdef = FormDef.get(invoice.formdef_id) - if formdef: - formdata = formdef.data_class().get(invoice.formdata_id, ignore_errors=True) - if formdata: - name = _('%(form_name)s #%(formdata_id)s') % { - 'form_name': formdata.formdef.name, - 'formdata_id': formdata.id } - '

          %s %s

          ' % (_('From:'), formdata.get_url(), name) - '

          %s

          ' % _('Regie: %s') % Regie.get(invoice.regie_id).label - '

          %s

          ' % _('Created on: %s') % misc.localstrftime(invoice.date) - if invoice.details: - '

          %s

          ' % _('Details:') - '
          ' - htmltext(invoice.details) - '
          ' - if invoice.canceled: - '

          ' - '%s' % _('canceled on %s') % misc.localstrftime(invoice.canceled_date) - if invoice.canceled_reason: - ' (%s)' % invoice.canceled_reason - '

          ' - if invoice.paid: - '' % _('paid on %s') % misc.localstrftime(invoice.paid_date) - '
          ' - -class InvoicesDirectory(Directory): - _q_exports = ['', 'multiple'] - - def _q_traverse(self, path): - if not is_payment_supported(): - raise errors.TraversalError() - get_response().filter['bigdiv'] = 'profile' - if get_session().user: - # fake breadcrumb - get_response().breadcrumb.append(('myspace/', _('My Space'))) - get_response().breadcrumb.append(('invoices/', _('Invoices'))) - return Directory._q_traverse(self, path) - - def multiple [html] (self): - invoice_ids = get_request().form.get('invoice') - if type(invoice_ids) is not list: - return redirect('%s' % invoice_ids) - return redirect('+'.join(invoice_ids)) - - def _q_lookup [html] (self, component): - if str('+') in component: - invoice_ids = component.split(str('+')) - else: - invoice_ids = [component] - for invoice_id in invoice_ids: - if not Invoice.check_crc(invoice_id): - raise errors.TraversalError() - - template.html_top(_('Invoices')) - TextsDirectory.get_html_text('aq-invoice') - - regies_id = set() - for invoice_id in invoice_ids: - try: - invoice = Invoice.get(invoice_id) - except KeyError: - raise errors.TraversalError() - invoice_as_html(invoice) - if not (invoice.paid or invoice.canceled): - regies_id.add(invoice.regie_id) - - if len(regies_id) == 1: - '

          ' - '' % (get_publisher().get_frontoffice_url(), component) - if len(invoice_ids) > 1: - _('Pay Selected Invoices') - else: - _('Pay') - '

          ' - if len(regies_id) > 1: - _('You can not pay to different regies.') - - def _q_index(self): - return redirect('..') - - -class RegieDirectory(Directory): - _q_exports = ['', 'edit', 'delete', 'options'] - - def __init__(self, regie): - self.regie = regie - - def _q_index [html] (self): - html_top('payments', title = _('Regie: %s') % self.regie.label) - get_response().filter['sidebar'] = self.get_sidebar() - '

          %s

          ' % _('Regie: %s') % self.regie.label - - get_session().display_message() - - if self.regie.description: - '
          ' - '

          ' - self.regie.description - '

          ' - '
          ' - - if self.regie.service: - '
          ' - url = get_publisher().get_frontoffice_url() + '/payment/back_asynchronous/' - url += str(self.regie.id) - '

          ' - '%s %s' % (_('Banking Service:'), self.regie.service) - ' (%s)' % _('options') - '

          ' - '

          ' - '%s %s' % (_('Payment notification URL:'), url) - '

          ' - - self.invoice_listing() - - def get_sidebar [html] (self): - '
            ' - '
          • %s
          • ' % _('Edit') - '
          • %s
          • ' % _('Delete') - '
          ' - - def edit [html] (self): - form = self.form() - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - self.submit(form) - return redirect('..') - - html_top('payments', title = _('Edit Regie: %s') % self.regie.label) - '

          %s

          ' % _('Edit Regie: %s') % self.regie.label - form.render() - - - def form(self): - form = Form(enctype='multipart/form-data') - form.add(StringWidget, 'label', title=_('Label'), required=True, - value=self.regie.label) - form.add(TextWidget, 'description', title=_('Description'), - value=self.regie.description, rows=5, cols=60) - form.add(SingleSelectWidget, 'service', title=_('Banking Service'), - value=self.regie.service, required=True, - options = [ - ('dummy', _('Dummy (for tests)')), - ('sips', 'SIPS'), - ('systempayv2', 'systempay (Banque Populaire)'), - ('spplus', _('SP+ (Caisse d\'epargne)'))]) - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - return form - - def submit(self, form): - for k in ('label', 'description', 'service'): - widget = form.get_widget(k) - if widget: - setattr(self.regie, k, widget.parse()) - self.regie.store() - - def delete [html] (self): - form = Form(enctype='multipart/form-data') - form.widgets.append(HtmlWidget('

          %s

          ' % _( - 'You are about to irrevocably delete this regie.'))) - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - if form.get_submit() == 'cancel': - return redirect('..') - if not form.is_submitted() or form.has_errors(): - get_response().breadcrumb.append(('delete', _('Delete'))) - html_top('payments', title = _('Delete Regie')) - '

          %s

          ' % _('Deleting Regie: %s') % self.regie.label - form.render() - else: - self.regie.remove_self() - return redirect('..') - - def option_form(self): - form = Form(enctype='multipart/form-data') - module = eopayment.get_backend(self.regie.service) - service_options = {} - for infos in module.description['parameters']: - if 'default' in infos: - service_options[infos['name']] = infos['default'] - service_options.update(self.regie.service_options or {}) - - banking_titles = { - ('dummy', 'direct_notification_url'): N_('Direct Notification URL'), - ('dummy', 'siret'): N_('Dummy SIRET'), - } - - for infos in module.description['parameters']: - name = infos['name'] - caption = infos.get('caption', name).encode(get_publisher().site_charset) - title = banking_titles.get((self.regie.service, name), caption) - kwargs = {} - widget = StringWidget - if infos.get('help_text') is not None: - kwargs['hint'] = _(infos['help_text'].encode(get_publisher().site_charset)) - if infos.get('required', False): - kwargs['required'] = True - if infos.get('max_length') is not None: - kwargs['size'] = infos['max_length'] - elif infos.get('length') is not None: - kwargs['size'] = infos['length'] - else: - kwargs['size'] = 80 - if kwargs['size'] > 100: - widget = TextWidget - kwargs['cols'] = 80 - kwargs['rows'] = 5 - if 'type' not in infos or infos['type'] is str: - form.add(widget, name, title=_(title), - value=service_options.get(name), **kwargs) - elif infos['type'] is bool: - form.add(CheckboxWidget, name, title=title, - value=service_options.get(name), **kwargs) - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - return form - - def options [html] (self): - form = self.option_form() - - module = eopayment.get_backend(self.regie.service) - try: - '' - except: - return template.error_page(_('Payment backend do not list its options')) - raise errors.TraversalError() - '' - - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - if self.submit_options(form, module): - return redirect('..') - - html_top('payments', title=_('Edit Service Options')) - '

          %s

          ' % _('Edit Service Options') - form.render() - - def submit_options(self, form, module): - # extra validation - error = False - for infos in module.description['parameters']: - widget = form.get_widget(infos['name']) - value = widget.parse() - if value and 'validation' in infos: - try: - if not infos['validation'](value): - widget.set_error(_('Valeur invalide')) - error = True - except ValueError, e: - widget.set_error(_(e.message)) - error = True - if error: - return False - if not self.regie.service_options: - self.regie.service_options = {} - for infos in module.description['parameters']: - name = infos['name'] - value = form.get_widget(name).parse() - if value is None: - value = '' - if hasattr(value, 'strip'): - value = value.strip() - if infos.get('default') is not None: - if value == infos['default']: - self.regie.service_options.pop(name, None) - else: - self.regie.service_options[name] = form.get_widget(name).parse() - elif not value: - self.regie.service_options.pop(name, None) - else: - self.regie.service_options[name] = form.get_widget(name).parse() - self.regie.store() - return True - - PAGINATION = 50 - - def monetary_amount(self, val): - if isinstance(val, basestring): - val = val.replace(',', '.') - return '%.2f' % decimal.Decimal(val) - - def get_sort_by(self): - request = get_request() - sort_by = request.form.get('sort_by') - if sort_by not in ('date', 'paid_date', 'username'): - sort_by = 'date' - return sort_by - - def get_invoices(self): - sort_by = self.get_sort_by() - invoices = Invoice.get_with_indexed_value('regie_id', self.regie.id, - ignore_errors=True) - if 'date' in sort_by: - reverse = True - key = lambda i: getattr(i, sort_by) or datetime.datetime.now() - else: - reverse = False - key = lambda i: getattr(i, sort_by) or '' - invoices.sort(reverse=reverse, key=key) - return invoices - - def unpay(self, request, invoice): - get_logger().info(_('manually set unpaid invoice %(invoice_id)s in regie %(regie)s') - % dict(invoice_id=invoice.id, regie=self.regie.id)) - transaction = Transaction() - transaction.invoice_ids = [ invoice.id ] - transaction.order_id = 'Manual action' - transaction.start = datetime.datetime.now() - transaction.end = transaction.start - transaction.bank_data = { - 'action': 'Set unpaid', - 'by': request.user.get_display_name() + ' (%s)' % request.user.id - } - transaction.store() - invoice.unpay() - - def pay(self, request, invoice): - get_logger().info(_('manually set paid invoice %(invoice_id)s in regie %(regie)s') - % dict(invoice_id=invoice.id, regie=self.regie.id)) - transaction = Transaction() - transaction.invoice_ids = [ invoice.id ] - transaction.order_id = 'Manual action' - transaction.start = datetime.datetime.now() - transaction.end = transaction.start - transaction.bank_data = { - 'action': 'Set paid', - 'by': request.user.get_display_name() + ' (%s)' % request.user.id - } - transaction.store() - invoice.pay() - - def invoice_listing [html] (self): - request = get_request() - get_response().add_css_include('../../themes/auquotidien/admin.css') - if request.get_method() == 'POST': - invoice_id = request.form.get('id') - invoice = Invoice.get(invoice_id, ignore_errors=True) - if invoice: - if 'unpay' in request.form: - self.unpay(request, invoice) - elif 'pay' in request.form: - self.pay(request, invoice) - return redirect('') - try: - offset = int(request.form.get('offset', 0)) - except ValueError: - offset = 0 - '' - '' - '' - '' % offset - '' - '' % offset - '' % offset - '' - '' - '' - '' - invoices = self.get_invoices() - for invoice in invoices[offset:offset+self.PAGINATION]: - '' - '' % (invoice.subject or '') - '' - transactions = Transaction.get_with_indexed_value('invoice_ids', - invoice.id) - for transaction in sorted(transactions, key=lambda x: x.start): - '' - '' - '' - '' - '' - '
          CreationAmountPaidUserTitre
          ' - misc.localstrftime(invoice.date) - '' - self.monetary_amount(invoice.amount) - '' - if invoice.paid: - misc.localstrftime(invoice.paid_date) - else: - '' - '' - user = invoice.get_user() - if user: - user.name - '%s' - '
          ' - ' ' % invoice.id - if invoice.paid: - '' % _('Set unpaid') - else: - '' % _('Set paid') - '
          ' - - '
          ' - 'OrderID: %s' % transaction.order_id - ' Start: %s' % transaction.start - if transaction.end: - ' End: %s' % transaction.end - if transaction.bank_data: - ' Bank data: %r' % transaction.bank_data - '
          ' - if offset != 0: - '%s ' % ( - max(0, offset-self.PAGINATION), _('Previous')) - - -class RegiesDirectory(Directory): - _q_exports = ['', 'new'] - - def _q_traverse(self, path): - get_response().breadcrumb.append(('regie/', _('Regies'))) - return Directory._q_traverse(self, path) - - def _q_index [html] (self): - return redirect('..') - - def new [html] (self): - regie_ui = RegieDirectory(Regie()) - - form = regie_ui.form() - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - regie_ui.submit(form) - return redirect('%s/' % regie_ui.regie.id) - - get_response().breadcrumb.append(('new', _('New Regie'))) - html_top('payments', title = _('New Regie')) - '

          %s

          ' % _('New Regie') - form.render() - - def _q_lookup(self, component): - try: - regie = Regie.get(component) - except KeyError: - raise errors.TraversalError() - get_response().breadcrumb.append((str(regie.id), regie.label)) - return RegieDirectory(regie) - - -class PaymentsDirectory(AccessControlled, Directory): - _q_exports = ['', 'regie'] - label = N_('Payments') - - regie = RegiesDirectory() - - def _q_access(self): - user = get_request().user - if not user: - raise errors.AccessUnauthorizedError() - admin_role = get_cfg('aq-permissions', {}).get('payments', None) - if not (user.is_admin or admin_role in (user.roles or [])): - raise errors.AccessForbiddenError( - public_msg = _('You are not allowed to access Payments Management'), - location_hint = 'backoffice') - - get_response().breadcrumb.append(('payments/', _('Payments'))) - - - def _q_index [html] (self): - html_top('payments', _('Payments')) - - '
            ' - '
          • %s
          • ' % _('New Regie') - '
          ' - - if not is_payment_supported: - '

          ' - _('Payment is not supported.') - '

          ' - - regies = Regie.select() - '

          %s

          ' % _('Regies') - if not regies: - '

          ' - _('There are no regies defined at the moment.') - '

          ' - '
            ' - for l in regies: - regie_id = l.id - '
          • ' % regie_id - '%s' % (regie_id, l.label) - '
          • ' - '
          ' - - -TextsDirectory.register('aq-invoice', - N_('Message on top of an invoice'), - category = N_('Invoices')) - -EmailsDirectory.register('payment-new-invoice-email', - N_('New invoice'), - N_('Available variables: user, regie, invoice, invoice_url'), - category = N_('Invoices'), - default_subject = N_('New invoice'), - default_body = N_(''' -A new invoice is available at [invoice_url]. -''')) - -EmailsDirectory.register('payment-invoice-paid-email', - N_('Paid invoice'), - N_('Available variables: user, regie, invoice, invoice_url'), - category = N_('Invoices'), - default_subject = N_('Paid invoice'), - default_body = N_(''' -The invoice [invoice_url] has been paid. -''')) - -EmailsDirectory.register('payment-invoice-canceled-email', - N_('Canceled invoice'), - N_('Available variables: user, regie, invoice, invoice_url'), - category = N_('Invoices'), - default_subject = N_('Canceled invoice'), - default_body = N_(''' -The invoice [invoice.id] has been canceled. -''')) diff --git a/extra/modules/payments_ui.py b/extra/modules/payments_ui.py new file mode 100644 index 0000000..9b07950 --- /dev/null +++ b/extra/modules/payments_ui.py @@ -0,0 +1,595 @@ +import time +import pprint +import locale +import decimal +import datetime + +from quixote import get_request, get_response, get_session, redirect +from quixote.directory import Directory, AccessControlled +from quixote.html import TemplateIO, htmltext + +import wcs +import wcs.admin.root +from wcs.backoffice.menu import * +from wcs.formdef import FormDef + +from qommon import errors, misc, template, get_logger +from qommon.form import * +from qommon.strftime import strftime +from qommon.admin.emails import EmailsDirectory + +from payments import (eopayment, Regie, is_payment_supported, Invoice, + Transaction, notify_paid_invoice) + +from qommon.admin.texts import TextsDirectory + +if not set: + from sets import Set as set + +def invoice_as_html(invoice): + r = TemplateIO(html=True) + r += htmltext('
          ') + r += htmltext('

          %s

          ') % _('Invoice: %s') % invoice.subject + r += htmltext('

          %s') % _('Amount: %s') % invoice.amount + r += htmltext(' €

          ') + r += htmltext('' + if invoice.formdef_id and invoice.formdata_id and \ + get_session().user == invoice.user_id: + formdef = FormDef.get(invoice.formdef_id) + if formdef: + formdata = formdef.data_class().get(invoice.formdata_id, ignore_errors=True) + if formdata: + name = _('%(form_name)s #%(formdata_id)s') % { + 'form_name': formdata.formdef.name, + 'formdata_id': formdata.id } + r += htmltext('

          %s %s

          ') % (_('From:'), formdata.get_url(), name) + r += htmltext('

          %s

          ') % _('Regie: %s') % Regie.get(invoice.regie_id).label + r += htmltext('

          %s

          ') % _('Created on: %s') % misc.localstrftime(invoice.date) + if invoice.details: + r += htmltext('

          %s

          ') % _('Details:') + r += htmltext('
          ') + r += htmltext(invoice.details) + r += htmltext('
          ') + if invoice.canceled: + r += htmltext('

          ') + r += '%s' % _('canceled on %s') % misc.localstrftime(invoice.canceled_date) + if invoice.canceled_reason: + r += ' (%s)' % invoice.canceled_reason + r += htmltext('

          ') + if invoice.paid: + r += htmltext('') % _('paid on %s') % misc.localstrftime(invoice.paid_date) + r += htmltext('
          ') + return r.getvalue() + + +class InvoicesDirectory(Directory): + _q_exports = ['', 'multiple'] + + def _q_traverse(self, path): + if not is_payment_supported(): + raise errors.TraversalError() + get_response().filter['bigdiv'] = 'profile' + if get_session().user: + # fake breadcrumb + get_response().breadcrumb.append(('myspace/', _('My Space'))) + get_response().breadcrumb.append(('invoices/', _('Invoices'))) + return Directory._q_traverse(self, path) + + def multiple(self): + invoice_ids = get_request().form.get('invoice') + if type(invoice_ids) is not list: + return redirect('%s' % invoice_ids) + return redirect('+'.join(invoice_ids)) + + def _q_lookup(self, component): + if str('+') in component: + invoice_ids = component.split(str('+')) + else: + invoice_ids = [component] + for invoice_id in invoice_ids: + if not Invoice.check_crc(invoice_id): + raise errors.TraversalError() + + template.html_top(_('Invoices')) + r = TemplateIO(html=True) + r += TextsDirectory.get_html_text('aq-invoice') + + regies_id = set() + for invoice_id in invoice_ids: + try: + invoice = Invoice.get(invoice_id) + except KeyError: + raise errors.TraversalError() + r += invoice_as_html(invoice) + if not (invoice.paid or invoice.canceled): + regies_id.add(invoice.regie_id) + + if len(regies_id) == 1: + r += htmltext('

          ') + r += htmltext('') % ( + get_publisher().get_frontoffice_url(), component) + if len(invoice_ids) > 1: + r += _('Pay Selected Invoices') + else: + r += _('Pay') + r += htmltext('

          ') + if len(regies_id) > 1: + r += _('You can not pay to different regies.') + + return r.getvalue() + + def _q_index(self): + return redirect('..') + + +class RegieDirectory(Directory): + _q_exports = ['', 'edit', 'delete', 'options'] + + def __init__(self, regie): + self.regie = regie + + def _q_index(self): + html_top('payments', title = _('Regie: %s') % self.regie.label) + r = TemplateIO(html=True) + get_response().filter['sidebar'] = self.get_sidebar() + r += htmltext('

          %s

          ') % _('Regie: %s') % self.regie.label + + r += get_session().display_message() + + if self.regie.description: + r += htmltext('
          ') + r += htmltext('

          ') + r += self.regie.description + r += htmltext('

          ') + r += htmltext('
          ') + + if self.regie.service: + r += htmltext('
          ') + url = get_publisher().get_frontoffice_url() + '/payment/back_asynchronous/' + url += str(self.regie.id) + r += htmltext('

          ') + r += '%s %s' % (_('Banking Service:'), self.regie.service) + r += htmltext(' (%s)') % _('options') + r += htmltext('

          ') + r += htmltext('

          ') + r += '%s %s' % (_('Payment notification URL:'), url) + r += htmltext('

          ') + + r += self.invoice_listing() + return r.getvalue() + + def get_sidebar(self): + r = TemplateIO(html=True) + r += htmltext('
            ') + r += htmltext('
          • %s
          • ') % _('Edit') + r += htmltext('
          • %s
          • ') % _('Delete') + r += htmltext('
          ') + return r.getvalue() + + def edit(self): + form = self.form() + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + self.submit(form) + return redirect('..') + + html_top('payments', title = _('Edit Regie: %s') % self.regie.label) + r = TemplateIO(html=True) + r += htmltext('

          %s

          ') % _('Edit Regie: %s') % self.regie.label + r += form.render() + return r.getvalue() + + + def form(self): + form = Form(enctype='multipart/form-data') + form.add(StringWidget, 'label', title=_('Label'), required=True, + value=self.regie.label) + form.add(TextWidget, 'description', title=_('Description'), + value=self.regie.description, rows=5, cols=60) + form.add(SingleSelectWidget, 'service', title=_('Banking Service'), + value=self.regie.service, required=True, + options = [ + ('dummy', _('Dummy (for tests)')), + ('sips', 'SIPS'), + ('systempayv2', 'systempay (Banque Populaire)'), + ('spplus', _('SP+ (Caisse d\'epargne)'))]) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + return form + + def submit(self, form): + for k in ('label', 'description', 'service'): + widget = form.get_widget(k) + if widget: + setattr(self.regie, k, widget.parse()) + self.regie.store() + + def delete(self): + form = Form(enctype='multipart/form-data') + form.widgets.append(HtmlWidget('

          %s

          ' % _( + 'You are about to irrevocably delete this regie.'))) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + if form.get_submit() == 'cancel': + return redirect('..') + if not form.is_submitted() or form.has_errors(): + get_response().breadcrumb.append(('delete', _('Delete'))) + r = TemplateIO(html=True) + html_top('payments', title = _('Delete Regie')) + r += htmltext('

          %s

          ') % _('Deleting Regie: %s') % self.regie.label + r += form.render() + return r.getvalue() + else: + self.regie.remove_self() + return redirect('..') + + def option_form(self): + form = Form(enctype='multipart/form-data') + module = eopayment.get_backend(self.regie.service) + service_options = {} + for infos in module.description['parameters']: + if 'default' in infos: + service_options[infos['name']] = infos['default'] + service_options.update(self.regie.service_options or {}) + + banking_titles = { + ('dummy', 'direct_notification_url'): N_('Direct Notification URL'), + ('dummy', 'siret'): N_('Dummy SIRET'), + } + + for infos in module.description['parameters']: + name = infos['name'] + caption = infos.get('caption', name).encode(get_publisher().site_charset) + title = banking_titles.get((self.regie.service, name), caption) + kwargs = {} + widget = StringWidget + if infos.get('help_text') is not None: + kwargs['hint'] = _(infos['help_text'].encode(get_publisher().site_charset)) + if infos.get('required', False): + kwargs['required'] = True + if infos.get('max_length') is not None: + kwargs['size'] = infos['max_length'] + elif infos.get('length') is not None: + kwargs['size'] = infos['length'] + else: + kwargs['size'] = 80 + if kwargs['size'] > 100: + widget = TextWidget + kwargs['cols'] = 80 + kwargs['rows'] = 5 + if 'type' not in infos or infos['type'] is str: + form.add(widget, name, title=_(title), + value=service_options.get(name), **kwargs) + elif infos['type'] is bool: + form.add(CheckboxWidget, name, title=title, + value=service_options.get(name), **kwargs) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + return form + + def options(self): + r = TemplateIO(html=True) + form = self.option_form() + + module = eopayment.get_backend(self.regie.service) + try: + r += htmltext('') + except: + return template.error_page(_('Payment backend do not list its options')) + raise errors.TraversalError() + r += htmltext('') + + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + if self.submit_options(form, module): + return redirect('..') + + html_top('payments', title=_('Edit Service Options')) + r += htmltext('

          %s

          ') % _('Edit Service Options') + r += form.render() + return r.getvalue() + + def submit_options(self, form, module): + # extra validation + error = False + for infos in module.description['parameters']: + widget = form.get_widget(infos['name']) + value = widget.parse() + if value and 'validation' in infos: + try: + if not infos['validation'](value): + widget.set_error(_('Valeur invalide')) + error = True + except ValueError, e: + widget.set_error(_(e.message)) + error = True + if error: + return False + if not self.regie.service_options: + self.regie.service_options = {} + for infos in module.description['parameters']: + name = infos['name'] + value = form.get_widget(name).parse() + if value is None: + value = '' + if hasattr(value, 'strip'): + value = value.strip() + if infos.get('default') is not None: + if value == infos['default']: + self.regie.service_options.pop(name, None) + else: + self.regie.service_options[name] = form.get_widget(name).parse() + elif not value: + self.regie.service_options.pop(name, None) + else: + self.regie.service_options[name] = form.get_widget(name).parse() + self.regie.store() + return True + + PAGINATION = 50 + + def monetary_amount(self, val): + if isinstance(val, basestring): + val = val.replace(',', '.') + return '%.2f' % decimal.Decimal(val) + + def get_sort_by(self): + request = get_request() + sort_by = request.form.get('sort_by') + if sort_by not in ('date', 'paid_date', 'username'): + sort_by = 'date' + return sort_by + + def get_invoices(self): + sort_by = self.get_sort_by() + invoices = Invoice.get_with_indexed_value('regie_id', self.regie.id, + ignore_errors=True) + if 'date' in sort_by: + reverse = True + key = lambda i: getattr(i, sort_by) or datetime.datetime.now() + else: + reverse = False + key = lambda i: getattr(i, sort_by) or '' + invoices.sort(reverse=reverse, key=key) + return invoices + + def unpay(self, request, invoice): + get_logger().info(_('manually set unpaid invoice %(invoice_id)s in regie %(regie)s') + % dict(invoice_id=invoice.id, regie=self.regie.id)) + transaction = Transaction() + transaction.invoice_ids = [ invoice.id ] + transaction.order_id = 'Manual action' + transaction.start = datetime.datetime.now() + transaction.end = transaction.start + transaction.bank_data = { + 'action': 'Set unpaid', + 'by': request.user.get_display_name() + ' (%s)' % request.user.id + } + transaction.store() + invoice.unpay() + + def pay(self, request, invoice): + get_logger().info(_('manually set paid invoice %(invoice_id)s in regie %(regie)s') + % dict(invoice_id=invoice.id, regie=self.regie.id)) + transaction = Transaction() + transaction.invoice_ids = [ invoice.id ] + transaction.order_id = 'Manual action' + transaction.start = datetime.datetime.now() + transaction.end = transaction.start + transaction.bank_data = { + 'action': 'Set paid', + 'by': request.user.get_display_name() + ' (%s)' % request.user.id + } + transaction.store() + invoice.pay() + + def invoice_listing(self): + request = get_request() + get_response().add_css_include('../../themes/auquotidien/admin.css') + if request.get_method() == 'POST': + invoice_id = request.form.get('id') + invoice = Invoice.get(invoice_id, ignore_errors=True) + if invoice: + if 'unpay' in request.form: + self.unpay(request, invoice) + elif 'pay' in request.form: + self.pay(request, invoice) + return redirect('') + try: + offset = int(request.form.get('offset', 0)) + except ValueError: + offset = 0 + r = TemplateIO(html=True) + r += htmltext('') + r += htmltext('') + r += htmltext('') + r += htmltext('') % offset + r += htmltext('') + r += htmltext('') % offset + r += htmltext('') % offset + r += htmltext('') + r += htmltext('') + r += htmltext('') + r += htmltext('') + invoices = self.get_invoices() + for invoice in invoices[offset:offset+self.PAGINATION]: + r += htmltext('') + r += htmltext('') % (invoice.subject or '') + r += htmltext('') + transactions = Transaction.get_with_indexed_value('invoice_ids', + invoice.id) + for transaction in sorted(transactions, key=lambda x: x.start): + r += htmltext('') + r += htmltext('') + r += htmltext('') + r += htmltext('') + r += htmltext('') + r += htmltext('
          CreationAmountPaidUserTitre
          ') + r += misc.localstrftime(invoice.date) + r += htmltext('') + r += self.monetary_amount(invoice.amount) + r += htmltext('') + if invoice.paid: + r += misc.localstrftime(invoice.paid_date) + else: + r += '' + r += htmltext('') + user = invoice.get_user() + if user: + r += user.name + r += htmltext('%s') + r += htmltext('
          ') + r += htmltext(' ') % invoice.id + if invoice.paid: + r += htmltext('') % _('Set unpaid') + else: + r += htmltext('') % _('Set paid') + r += htmltext('
          ') + + r += htmltext('
          ') + r += 'OrderID: %s' % transaction.order_id + r += ' Start: %s' % transaction.start + if transaction.end: + r += ' End: %s' % transaction.end + if transaction.bank_data: + r += ' Bank data: %r' % transaction.bank_data + r += htmltext('
          ') + if offset != 0: + r += htmltext('%s ') % ( + max(0, offset-self.PAGINATION), _('Previous')) + return r.getvalue() + + +class RegiesDirectory(Directory): + _q_exports = ['', 'new'] + + def _q_traverse(self, path): + get_response().breadcrumb.append(('regie/', _('Regies'))) + return Directory._q_traverse(self, path) + + def _q_index(self): + return redirect('..') + + def new(self): + regie_ui = RegieDirectory(Regie()) + + form = regie_ui.form() + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + regie_ui.submit(form) + return redirect('%s/' % regie_ui.regie.id) + + get_response().breadcrumb.append(('new', _('New Regie'))) + html_top('payments', title = _('New Regie')) + r = TemplateIO(html=True) + r += htmltext('

          %s

          ') % _('New Regie') + r += form.render() + return r.getvalue() + + def _q_lookup(self, component): + try: + regie = Regie.get(component) + except KeyError: + raise errors.TraversalError() + get_response().breadcrumb.append((str(regie.id), regie.label)) + return RegieDirectory(regie) + + +class PaymentsDirectory(AccessControlled, Directory): + _q_exports = ['', 'regie'] + label = N_('Payments') + + regie = RegiesDirectory() + + def _q_access(self): + user = get_request().user + if not user: + raise errors.AccessUnauthorizedError() + admin_role = get_cfg('aq-permissions', {}).get('payments', None) + if not (user.is_admin or admin_role in (user.roles or [])): + raise errors.AccessForbiddenError( + public_msg = _('You are not allowed to access Payments Management'), + location_hint = 'backoffice') + + get_response().breadcrumb.append(('payments/', _('Payments'))) + + + def _q_index(self): + html_top('payments', _('Payments')) + r = TemplateIO(html=True) + + r += htmltext('
            ') + r += htmltext('
          • %s
          • ') % _('New Regie') + r += htmltext('
          ') + + if not is_payment_supported: + r += htmltext('

          ') + r += _('Payment is not supported.') + r += htmltext('

          ') + + regies = Regie.select() + r += htmltext('

          %s

          ') % _('Regies') + if not regies: + r += htmltext('

          ') + r += _('There are no regies defined at the moment.') + r += htmltext('

          ') + r += htmltext('
            ') + for l in regies: + regie_id = l.id + r += htmltext('
          • ') % regie_id + r += htmltext('%s') % (regie_id, l.label) + r += htmltext('
          • ') + r += htmltext('
          ') + return r.getvalue() + + +TextsDirectory.register('aq-invoice', + N_('Message on top of an invoice'), + category = N_('Invoices')) + +EmailsDirectory.register('payment-new-invoice-email', + N_('New invoice'), + N_('Available variables: user, regie, invoice, invoice_url'), + category = N_('Invoices'), + default_subject = N_('New invoice'), + default_body = N_(''' +A new invoice is available at [invoice_url]. +''')) + +EmailsDirectory.register('payment-invoice-paid-email', + N_('Paid invoice'), + N_('Available variables: user, regie, invoice, invoice_url'), + category = N_('Invoices'), + default_subject = N_('Paid invoice'), + default_body = N_(''' +The invoice [invoice_url] has been paid. +''')) + +EmailsDirectory.register('payment-invoice-canceled-email', + N_('Canceled invoice'), + N_('Available variables: user, regie, invoice, invoice_url'), + category = N_('Invoices'), + default_subject = N_('Canceled invoice'), + default_body = N_(''' +The invoice [invoice.id] has been canceled. +''')) diff --git a/extra/modules/root.ptl b/extra/modules/root.ptl deleted file mode 100644 index 097bbf2..0000000 --- a/extra/modules/root.ptl +++ /dev/null @@ -1,1275 +0,0 @@ -from quixote import get_publisher, get_response, get_request, redirect, get_session -from quixote.directory import Directory -from quixote.html import htmltext -from quixote.util import StaticDirectory - -from wcs.qommon.misc import get_variadic_url - -import os -import re -import string -import urlparse - -try: - import lasso -except ImportError: - pass - -import wcs -import wcs.root -import qommon -from qommon import get_cfg, get_logger -from qommon import template -from qommon import errors -from qommon.form import * -from qommon import logger -from wcs.roles import logged_users_role - -from qommon import emails -from qommon.sms import SMS -from wcs.categories import Category -from wcs.formdef import FormDef -from qommon.tokens import Token -from qommon.admin.emails import EmailsDirectory -from qommon.admin.texts import TextsDirectory - -from links import Link -from announces import Announce, AnnounceSubscription -from myspace import MyspaceDirectory -from agenda import AgendaDirectory -from events import Event, get_default_event_tags -from payments import PublicPaymentDirectory -from payments_ui import InvoicesDirectory - -import admin - -import wcs.forms.root -from wcs.workflows import Workflow - -from saml2 import Saml2Directory - -OldRootDirectory = wcs.root.RootDirectory - -import qommon.ident.password -import qommon.ident.idp - -import drupal -import ezldap_ui -import msp_ui - -def category_get_homepage_position(self): - if hasattr(self, 'homepage_position') and self.homepage_position: - return self.homepage_position - if self.url_name == 'consultations': - return '2nd' - return '1st' -Category.get_homepage_position = category_get_homepage_position - -def category_get_limit(self): - if hasattr(self, 'limit') and self.limit is not None: - return self.limit - return 3 -Category.get_limit = category_get_limit - - -class FormsRootDirectory(wcs.forms.root.RootDirectory): - - def _q_index(self, *args): - get_response().filter['is_index'] = True - return wcs.forms.root.RootDirectory._q_index(self, *args) - - def user_forms [html] (self, user_forms): - base_url = get_publisher().get_root_url() - - draft = [x for x in user_forms if x.is_draft()] - if draft: - '

          %s

          ' % _('My Current Drafts') - '
            ' - for f in draft: - '
          • %s, %s
          • ' % (base_url, - f.formdef.category.url_name, - f.formdef.url_name, f.id, f.formdef.name, - misc.localstrftime(f.receipt_time)) - '
          ' - - forms_by_status_name = {} - for f in user_forms: - if f.is_draft(): - continue - status = f.get_visible_status() - if status: - status_name = status.name - else: - status_name = None - if status_name in forms_by_status_name: - forms_by_status_name[status_name].append(f) - else: - forms_by_status_name[status_name] = [f] - for status_name in forms_by_status_name: - if status_name: - '

          %s

          ' % _('My forms with status "%s"') % status_name - else: - '

          %s

          ' % _('My forms with an unknown status') % status_name - '
            ' - forms_by_status_name[status_name].sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) - for f in forms_by_status_name[status_name]: - if f.formdef.category_id: - category_url = f.formdef.category.url_name - else: - category_url = '.' - '
          • %s, %s
          • ' % ( - base_url, - category_url, - f.formdef.url_name, f.id, f.formdef.name, - misc.localstrftime(f.receipt_time)) - '
          ' - - -class AnnounceDirectory(Directory): - _q_exports = ['', 'edit', 'delete', 'email'] - - def __init__(self, announce): - self.announce = announce - - def _q_index [html] (self): - template.html_top(_('Announces to citizens')) - - if self.announce.publication_time: - date_heading = '%s - ' % time.strftime(misc.date_format(), self.announce.publication_time) - else: - date_heading = '' - - '

          %s%s

          ' % (date_heading, self.announce.title) - - '

          ' - self.announce.text - '

          ' - - '

          ' - '%s' % _('Back') - '

          ' - - -class AnnouncesDirectory(Directory): - _q_exports = ['', 'subscribe', 'email', 'atom', 'sms', 'emailconfirm', - 'email_unsubscribe', 'sms_unsubscribe', 'smsconfirm', 'rawlist'] - - - def _q_traverse(self, path): - get_response().breadcrumb.append(('announces/', _('Announces'))) - return Directory._q_traverse(self, path) - - def _q_index [html] (self): - template.html_top(_('Announces to citizens')) - self.announces_list() - '' - - def _get_announce_subscription(self): - """ """ - sub = None - if get_request().user: - subs = AnnounceSubscription.select(lambda x: x.user_id == get_request().user.id) - if subs: - sub = subs[0] - return sub - - def rawlist [html] (self): - self.announces_list() - get_response().filter = None - - def announces_list [html] (self): - announces = Announce.get_published_announces() - if not announces: - raise errors.TraversalError() - - # XXX: will need pagination someday - for item in announces: - '
          \n' - '

          ' - if item.publication_time: - time.strftime(misc.date_format(), item.publication_time) - ' - ' - item.title - '

          \n' - '

          \n' - item.text - '\n

          \n' - '
          \n' - - - def sms [html] (self): - sms_mode = get_cfg('sms', {}).get('mode', 'none') - - if sms_mode == 'none': - raise errors.TraversalError() - - get_response().breadcrumb.append(('sms', _('SMS'))) - template.html_top(_('Receiving announces by SMS')) - - if sms_mode == 'demo': - TextsDirectory.get_html_text('aq-sms-demo') - else: - announces_cfg = get_cfg('announces',{}) - mobile_mask = announces_cfg.get('mobile_mask') - if mobile_mask: - mobile_mask = ' (' + mobile_mask + ')' - else: - mobile_mask = '' - form = Form(enctype='multipart/form-data') - form.add(StringWidget, 'mobile', title = _('Mobile number %s') % mobile_mask, size=12, required=True) - form.add_submit('submit', _('Subscribe')) - form.add_submit('cancel', _('Cancel')) - - if form.get_submit() == 'cancel': - return redirect('subscribe') - - if form.is_submitted() and not form.has_errors(): - s = self.sms_submit(form) - if s == False: - form.render() - else: - return redirect("smsconfirm") - else: - form.render() - - def sms_submit(self, form): - mobile = form.get_widget("mobile").parse() - # clean the string, remove any extra character - mobile = re.sub('[^0-9+]','',mobile) - # if a mask was set, validate - announces_cfg = get_cfg('announces',{}) - mobile_mask = announces_cfg.get('mobile_mask') - if mobile_mask: - mobile_regexp = re.sub('X','[0-9]', mobile_mask) + '$' - if not re.match(mobile_regexp, mobile): - form.set_error("mobile", _("Phone number invalid ! It must match ") + mobile_mask) - return False - if mobile.startswith('00'): - mobile = '+' + mobile[2:] - else: - # Default to france international prefix - if not mobile.startswith('+'): - mobile = re.sub("^0", "+33", mobile) - sub = self._get_announce_subscription() - if not sub: - sub = AnnounceSubscription() - if get_request().user: - sub.user_id = get_request().user.id - - if mobile: - sub.sms = mobile - - if not get_request().user: - sub.enabled = False - - sub.store() - - # Asking sms confirmation - token = Token(3 * 86400, 4, string.digits) - token.type = 'announces-subscription-confirmation' - token.subscription_id = sub.id - token.store() - - message = _("Confirmation code : %s") % str(token.id) - sms_cfg = get_cfg('sms', {}) - sender = sms_cfg.get('sender', 'AuQuotidien')[:11] - mode = sms_cfg.get('mode', 'none') - sms = SMS.get_sms_class(mode) - try: - sms.send(sender, [mobile], message) - except errors.SMSError, e: - get_logger().error(e) - form.set_error("mobile", _("Send SMS confirmation failed")) - sub.remove("sms") - return False - - def smsconfirm [html] (self): - template.html_top(_('Receiving announces by SMS confirmation')) - "

          %s

          " % _("You will receive a confirmation code by SMS.") - form = Form(enctype='multipart/form-data') - form.add(StringWidget, 'code', title = _('Confirmation code (4 characters)'), size=12, required=True) - form.add_submit('submit', _('Subscribe')) - form.add_submit('cancel', _('Cancel')) - - if form.get_submit() == 'cancel': - return redirect('..') - - if form.is_submitted() and not form.has_errors(): - token = None - id = form.get_widget("code").parse() - try: - token = Token.get(id) - except KeyError: - form.set_error("code", _('Invalid confirmation code.')) - else: - if token.type != 'announces-subscription-confirmation': - form.set_error("code", _('Invalid confirmation code.')) - else: - sub = AnnounceSubscription.get(token.subscription_id) - token.remove_self() - sub.enabled_sms = True - sub.store() - return redirect('.') - form.render() - else: - form.render() - - def sms_unsubscribe [html] (self): - sub = self._get_announce_subscription() - - form = Form(enctype='multipart/form-data') - if not sub: - return redirect('..') - - form.add_submit('submit', _('Unsubscribe')) - form.add_submit('cancel', _('Cancel')) - - if form.get_submit() == 'cancel': - return redirect('..') - - get_response().breadcrumb.append(('sms', _('SMS Unsubscription'))) - template.html_top() - - if form.is_submitted() and not form.has_errors(): - if sub: - sub.remove("sms") - - def sms_unsub_ok [html] (): - root_url = get_publisher().get_root_url() - '

          ' - _('You have been unsubscribed from announces') - '

          ' - if not get_response().iframe_mode: - '%s' % (root_url, _('Back Home')) - - return sms_unsub_ok() - - else: - '

          ' - _('Do you want to stop receiving announces by sms ?') - '

          ' - form.render() - - - def subscribe [html] (self): - get_response().breadcrumb.append(('subscribe', _('Subscription'))) - template.html_top(_('Receiving Announces')) - - TextsDirectory.get_html_text('aq-announces-subscription') - - sub = self._get_announce_subscription() - - '
            ' - if sub and sub.email: - '
          • ' - '%s' % _('Email (currently subscribed)') - ' %s
          • ' % _('Unsubscribe') - else: - '
          • %s
          • ' % _('Email') - if sub and sub.sms: - '
          • ' - if sub.enabled_sms: - '%s' % _('SMS %s (currently subscribed)') % sub.sms - else: - '%s' % _('SMS %s (currently not confirmed)') % sub.sms - ' %s ' % _('Confirmation') - ' %s
          • ' % _('Unsubscribe') - elif get_cfg('sms', {}).get('mode', 'none') != 'none': - '
          • %s
          • ' % _('SMS') - '
          • %s' % _('Feed') - '
          ' - - - def email [html] (self): - get_response().breadcrumb.append(('email', _('Email Subscription'))) - template.html_top(_('Receiving Announces by email')) - - form = Form(enctype='multipart/form-data') - if get_request().user: - if get_request().user.email: - '

          ' - _('You are logged in and your email is %s, ok to subscribe ?') % \ - get_request().user.email - '

          ' - form.add_submit('submit', _('Subscribe')) - else: - '

          ' - _("You are logged in but there is no email address in your profile.") - '

          ' - form.add(EmailWidget, 'email', title = _('Email'), required = True) - form.add_submit('submit', _('Subscribe')) - form.add_submit('submit-remember', _('Subscribe and add this email to my profile')) - else: - '

          ' - _('FIXME will only be used for this purpose etc.') - '

          ' - form.add(EmailWidget, 'email', title = _('Email'), required = True) - form.add_submit('submit', _('Subscribe')) - - form.add_submit('cancel', _('Cancel')) - - if form.get_submit() == 'cancel': - return redirect('subscribe') - - if form.is_submitted() and not form.has_errors(): - s = self.email_submit(form) - if s is not False: - return s - else: - form.render() - - def email_submit(self, form): - sub = self._get_announce_subscription() - if not sub: - sub = AnnounceSubscription() - - if get_request().user: - sub.user_id = get_request().user.id - - if form.get_widget('email'): - sub.email = form.get_widget('email').parse() - elif get_request().user.email: - sub.email = get_request().user.email - - if not get_request().user: - sub.enabled = False - - sub.store() - - if get_request().user: - def email_submit_ok [html] (): - root_url = get_publisher().get_root_url() - '

          ' - _('You have been subscribed to the announces.') - '

          ' - if not get_response().iframe_mode: - '%s' % (root_url, _('Back Home')) - - return email_submit_ok() - - # asking email confirmation before subscribing someone - token = Token(3 * 86400) - token.type = 'announces-subscription-confirmation' - token.subscription_id = sub.id - token.store() - data = { - 'confirm_url': get_request().get_url() + 'confirm?t=%s&a=cfm' % token.id, - 'cancel_url': get_request().get_url() + 'confirm?t=%s&a=cxl' % token.id, - 'time': misc.localstrftime(time.localtime(token.expiration)), - } - - emails.custom_ezt_email('announces-subscription-confirmation', - data, sub.email, exclude_current_user = False) - - def email_submit_ok [html] (): - root_url = get_publisher().get_root_url() - '

          ' - _('You have been sent an email for confirmation') - '

          ' - if not get_response().iframe_mode: - '%s' % (root_url, _('Back Home')) - - return email_submit_ok() - - def emailconfirm(self): - tokenv = get_request().form.get('t') - action = get_request().form.get('a') - - root_url = get_publisher().get_root_url() - - try: - token = Token.get(tokenv) - except KeyError: - return template.error_page( - _('The token you submitted does not exist, has expired, or has been cancelled.'), - continue_to = (root_url, _('home page'))) - - if token.type != 'announces-subscription-confirmation': - return template.error_page( - _('The token you submitted is not appropriate for the requested task.'), - continue_to = (root_url, _('home page'))) - - sub = AnnounceSubscription.get(token.subscription_id) - - if action == 'cxl': - def cancel [html](): - root_url = get_publisher().get_root_url() - template.html_top(_('Email Subscription')) - '

          %s

          ' % _('Request Cancelled') - '

          %s

          ' % _('The request for subscription has been cancelled.') - '

          ' - htmltext(_('Continue to home page') % root_url) - '

          ' - token.remove_self() - sub.remove_self() - return cancel() - - if action == 'cfm': - token.remove_self() - sub.enabled = True - sub.store() - def sub [html] (): - root_url = get_publisher().get_root_url() - template.html_top(_('Email Subscription')) - '

          %s

          ' % _('Subscription Confirmation') - '

          %s

          ' % _('Your subscription to announces is now effective.') - '

          ' - htmltext(_('Continue to home page') % root_url) - '

          ' - return sub() - - - def atom [plain] (self): - response = get_response() - response.set_content_type('application/atom+xml') - - from pyatom import pyatom - xmldoc = pyatom.XMLDoc() - feed = pyatom.Feed() - xmldoc.root_element = feed - feed.title = get_cfg('misc', {}).get('sitename') or 'Au Quotidien' - feed.id = get_request().get_url() - - author_email = get_cfg('emails', {}).get('reply_to') - if not author_email: - author_email = get_cfg('emails', {}).get('from') - if author_email: - feed.authors.append(pyatom.Author(author_email)) - - announces = Announce.get_published_announces() - - if announces and announces[0].modification_time: - feed.updated = misc.format_time(announces[0].modification_time, - '%(year)s-%(month)02d-%(day)02dT%(hour)02d:%(minute)02d:%(second)02dZ', - gmtime = True) - feed.links.append(pyatom.Link(get_request().get_url(1) + '/')) - - for item in announces: - entry = item.get_atom_entry() - if entry: - feed.entries.append(entry) - - str(feed) - - def email_unsubscribe [html] (self): - sub = self._get_announce_subscription() - - form = Form(enctype='multipart/form-data') - if not sub: - form.add(EmailWidget, 'email', title = _('Email'), required = True) - - form.add_submit('submit', _('Unsubscribe')) - form.add_submit('cancel', _('Cancel')) - - if form.get_submit() == 'cancel': - return redirect('..') - - get_response().breadcrumb.append(('email', _('Email Unsubscription'))) - template.html_top() - - if form.is_submitted() and not form.has_errors(): - if sub: - sub.remove("email") - else: - email = form.get_widget('email').parse() - for s in AnnounceSubscription.select(): - if s.email == email: - s.remove("email") - - def email_unsub_ok [html] (): - root_url = get_publisher().get_root_url() - '

          ' - _('You have been unsubscribed from announces') - '

          ' - if not get_response().iframe_mode: - '%s' % (root_url, _('Back Home')) - - return email_unsub_ok() - - else: - '

          ' - _('Do you want to stop receiving announces by email?') - '

          ' - form.render() - - def _q_lookup(self, component): - try: - announce = Announce.get(component) - except KeyError: - raise errors.TraversalError() - - if announce.hidden: - raise errors.TraversalError() - - get_response().breadcrumb.append((str(announce.id), announce.title)) - return AnnounceDirectory(announce) - -OldRegisterDirectory = wcs.root.RegisterDirectory - -class AlternateRegisterDirectory(OldRegisterDirectory): - def _q_traverse(self, path): - get_response().filter['bigdiv'] = 'new_member' - return OldRegisterDirectory._q_traverse(self, path) - - def _q_index [html] (self): - get_logger().info('register') - ident_methods = get_cfg('identification', {}).get('methods', []) - - if len(ident_methods) == 0: - idps = get_cfg('idp', {}) - if len(idps) == 0: - return template.error_page(_('Authentication subsystem is not yet configured.')) - ident_methods = ['idp'] # fallback to old behaviour; liberty. - - if len(ident_methods) == 1: - method = ident_methods[0] - else: - method = 'password' - - return qommon.ident.register(method) - -OldLoginDirectory = wcs.root.LoginDirectory - -class AlternateLoginDirectory(OldLoginDirectory): - def _q_traverse(self, path): - get_response().filter['bigdiv'] = 'member' - return OldLoginDirectory._q_traverse(self, path) - - def _q_index [html] (self): - get_logger().info('login') - ident_methods = get_cfg('identification', {}).get('methods', []) - - if len(ident_methods) > 1 and 'idp' in ident_methods: - # if there is more than one identification method, and there is a - # possibility of SSO, if we got there as a consequence of an access - # unauthorized url on admin/ or backoffice/, then idp auth method - # is chosen forcefully. - after_url = get_session().after_url - if after_url: - root_url = get_publisher().get_root_url() - after_path = urlparse.urlparse(after_url)[2] - after_path = after_path[len(root_url):] - if after_path.startswith(str('admin')) or \ - after_path.startswith(str('backoffice')): - ident_methods = ['idp'] - - # don't display authentication system choice - if len(ident_methods) == 1: - method = ident_methods[0] - try: - return qommon.ident.login(method) - except KeyError: - get_logger().error('failed to login with method %s' % method) - return errors.TraversalError() - - if sorted(ident_methods) == ['idp', 'password']: - get_response().breadcrumb.append(('login', _('Login'))) - identities_cfg = get_cfg('identities', {}) - form = Form(enctype = 'multipart/form-data', id = 'login-form', use_tokens = False) - if identities_cfg.get('email-as-username', False): - form.add(StringWidget, 'username', title = _('Email'), size=25, required=True) - else: - form.add(StringWidget, 'username', title = _('Username'), size=25, required=True) - form.add(PasswordWidget, 'password', title = _('Password'), size=25, required=True) - form.add_submit('submit', _('Connect')) - if form.is_submitted() and not form.has_errors(): - tmp = qommon.ident.password.MethodDirectory().login_submit(form) - if not form.has_errors(): - return tmp - - '
          ' - get_session().display_message() - form.render() - - base_url = get_publisher().get_root_url() - '

          %s

          ' % ( - base_url, _('Forgotten password ?')) - - '
          ' - - # XXX: this part only supports a single IdP - '
          ' - TextsDirectory.get_html_text('aq-sso-text') - form = Form(enctype='multipart/form-data', - action = '%sident/idp/login' % base_url) - form.add_hidden('method', 'idp') - for kidp, idp in get_cfg('idp', {}).items(): - p = lasso.Provider(lasso.PROVIDER_ROLE_IDP, - misc.get_abs_path(idp['metadata']), - misc.get_abs_path(idp.get('publickey')), None) - form.add_hidden('idp', p.providerId) - break - form.add_submit('submit', _('Connect')) - - form.render() - '
          ' - - get_request().environ['REQUEST_METHOD'] = 'GET' - - """""" - - else: - return OldLoginDirectory._q_index(self) - - -OldIdentDirectory = wcs.root.IdentDirectory -class AlternateIdentDirectory(OldIdentDirectory): - def _q_traverse(self, path): - get_response().filter['bigdiv'] = 'member' - return OldIdentDirectory._q_traverse(self, path) - - -class AlternateRootDirectory(OldRootDirectory): - _q_exports = ['', 'admin', 'backoffice', 'forms', 'login', 'logout', - 'liberty', 'token', 'saml', 'register', 'ident', 'afterjobs', - ('informations-editeur', 'informations_editeur'), 'index2', - ('announces', 'announces_dir'), - 'accessibility', 'contact', 'help', - 'myspace', 'services', 'agenda', 'categories', 'user', - ('tmp-upload', 'tmp_upload'), 'json', '__version__', - 'themes', 'pages', 'payment', 'invoices', 'accesscode', 'roles', - 'msp'] - - admin = admin.AdminRootDirectory() - announces_dir = AnnouncesDirectory() - register = AlternateRegisterDirectory() - login = AlternateLoginDirectory() - ident = AlternateIdentDirectory() - myspace = MyspaceDirectory() - agenda = AgendaDirectory() - saml = Saml2Directory() - payment = PublicPaymentDirectory() - invoices = InvoicesDirectory() - msp = msp_ui.MSPDirectory() - - def get_substitution_variables(self): - d = {} - def print_links(fd): - fd.write(str(self.links())) - d['links'] = print_links - return d - - def _q_traverse(self, path): - if get_publisher().has_site_option('drupal'): - drupal.try_auth() - if get_publisher().has_site_option('ezldap'): - ezldap_ui.try_auth(self) - - session = get_session() - if session: - get_request().user = session.get_user() - else: - get_request().user = None - - get_publisher().substitutions.feed(get_request().user) - - response = get_response() - if not hasattr(response, 'filter'): - response.filter = {} - - response.filter['gauche'] = self.box_side(path) - response.filter['keywords'] = template.get_current_theme().get('keywords') - get_publisher().substitutions.feed(self) - - response.breadcrumb = [ ('', _('Home')) ] - - if not self.admin: - self.admin = get_publisher().admin_directory_class() - - if not self.backoffice: - self.backoffice = get_publisher().backoffice_directory_class() - - try: - return Directory._q_traverse(self, path) - except errors.TraversalError, e: - try: - f = FormDef.get_by_urlname(path[0]) - except KeyError: - pass - else: - base_url = get_publisher().get_root_url() - - uri_rest = get_request().environ.get('REQUEST_URI') - if not uri_rest: - uri_rest = get_request().get_path() - if uri_rest.startswith(base_url): - uri_rest = uri_rest[len(base_url):] - elif uri_rest.startswith('/'): - # dirty hack, ezldap reverseproxy uses a fake base_url - uri_rest = uri_rest[1:] - if f.category_id: - return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest)) - - raise e - - - def _q_lookup(self, component): - if component == 'qo': - dirname = os.path.join(get_publisher().data_dir, 'qommon') - return StaticDirectory(dirname, follow_symlinks = True) - - if component == 'aq': - dirname = os.path.join(get_publisher().data_dir, 'qommon', 'auquotidien') - return StaticDirectory(dirname, follow_symlinks = True) - - if component in ('css','images'): - return OldRootDirectory._q_lookup(self, component) - - # is this a category ? - try: - category = Category.get_by_urlname(component) - except KeyError: - pass - else: - return FormsRootDirectory(category) - - # is this a formdef ? - try: - formdef = FormDef.get_by_urlname(component) - except KeyError: - pass - else: - if formdef.category_id is None: - get_response().filter['bigdiv'] = 'rub_service' - return FormsRootDirectory()._q_lookup(component) - # if there is category, let it fall back to raise TraversalError, - # it will get caught in _q_traverse that will redirect it to an - # URL embedding the category - - return None - - def json(self): - return FormsRootDirectory().json() - - def categories(self): - return FormsRootDirectory().categories() - - def _q_index [html] (self): - if get_request().is_json(): - return FormsRootDirectory().json() - - root_url = get_publisher().get_root_url() - if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump: - return redirect('%smyspace/new' % root_url) - - if get_response().iframe_mode: - # never display home page in an iframe - return redirect('%sservices' % root_url) - - template.html_top() - get_response().filter['is_index'] = True - - if not 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []): - t = TextsDirectory.get_html_text('aq-home-page') - if not t: - if get_request().user: - t = TextsDirectory.get_html_text('welcome-logged') - else: - t = TextsDirectory.get_html_text('welcome-unlogged') - if t: - '
          ' - t - '
          ' - - '
          ' - self.box_services(position='1st') - '
          ' - '
          ' - self.myspace_snippet() - self.box_services(position='2nd') - self.consultations() - self.announces() - '
          ' - - user = get_request().user - if user and user.can_go_in_backoffice(): - get_response().filter['backoffice'] = True - - def services [html] (self): - template.html_top() - get_response().filter['bigdiv'] = 'rub_service' - self.box_services(level = 2) - - def box_services [html] (self, level=3, position=None): - ## Services - if get_request().user and get_request().user.roles: - accepted_roles = get_request().user.roles - else: - accepted_roles = [] - - cats = Category.select(order_by = 'name') - cats = [x for x in cats if x.url_name != 'consultations'] - Category.sort_by_position(cats) - - all_formdefs = FormDef.select(lambda x: not x.is_disabled() or x.disabled_redirection, - order_by = 'name') - - if position: - t = self.display_list_of_formdefs( - [x for x in cats if x.get_homepage_position() == position], - all_formdefs, accepted_roles) - else: - t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles) - - if not t: - return - - if position == '2nd': - '
          ' - else: - '
          ' - if level == 2: - '

          %s

          ' % _('Services') - else: - '

          %s

          ' % _('Services') - - if get_response().iframe_mode: - if get_request().user: - message = TextsDirectory.get_html_text('welcome-logged') - else: - message = TextsDirectory.get_html_text('welcome-unlogged') - - if message: - '
          ' - message - '
          ' - elif 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []): - homepage_text = TextsDirectory.get_html_text('aq-home-page') - if homepage_text: - '
          ' - homepage_text - '
          ' - - '
            ' - t - '
          ' - - '
          ' - - def display_list_of_formdefs [html] (self, cats, all_formdefs, accepted_roles): - for category in cats: - if category.url_name == 'consultations': - self.consultations_category = category - continue - formdefs = [x for x in all_formdefs if x.category_id == category.id] - formdefs_advertise = [] - - for formdef in formdefs[:]: - if formdef.is_disabled(): # is a redirection - continue - if not formdef.roles: - continue - if not get_request().user: - if formdef.always_advertise: - formdefs_advertise.append(formdef) - formdefs.remove(formdef) - continue - if logged_users_role().id in formdef.roles: - continue - for q in accepted_roles: - if q in formdef.roles: - break - else: - if formdef.always_advertise: - formdefs_advertise.append(formdef) - formdefs.remove(formdef) - - if not formdefs and not formdefs_advertise: - continue - - '
        1. ' - '' - '' % category.url_name - category.name - '\n' - if category.description: - if category.description[0] == '<': - htmltext(category.description) - else: - '

          ' - category.description - '

          ' - '
            ' - limit = category.get_limit() - for formdef in formdefs[:limit]: - '
          • ' - '%s' % (category.url_name, formdef.url_name, formdef.name) - '
          • \n' - if len(formdefs) < limit: - for formdef in formdefs_advertise[:limit-len(formdefs)]: - '
          • ' - '%s' % (category.url_name, formdef.url_name, formdef.name) - ' (%s)' % _('authentication required') - '
          • \n' - if (len(formdefs)+len(formdefs_advertise)) > limit: - '
          • %s
          • ' % (category.url_name, - _('Access to all forms of the "%s" category') % category.name, - _('Access to all forms in this category')) - '
          ' - '
        2. \n' - - def consultations [html] (self): - cats = [x for x in Category.select() if x.url_name == 'consultations'] - if not cats: - return - consultations_category = cats[0] - formdefs = FormDef.select(lambda x: ( - x.category_id == consultations_category.id and - (not x.is_disabled() or x.disabled_redirection)), - order_by = 'name') - if not formdefs: - return - ## Consultations - '
          ' - '

          %s

          ' % _('Consultations') - if consultations_category.description: - if consultations_category.description[0] == '<': - htmltext(consultations_category.description) - else: - '

          ' - consultations_category.description - '

          ' - '
            ' - for formdef in formdefs: - '
          • ' - '%s' % (consultations_category.url_name, - formdef.url_name, formdef.name) - '
          • ' - '
          ' - '
          ' - - def box_side [html] (self, path): - '' - - def has_anonymous_access_codes(self): - for workflow in Workflow.select(): - for wfstatus in workflow.possible_status: - for wfitem in wfstatus.items: - if wfitem.key == 'create-anonymous-access-code': - return True - return False - - def accesscode(self): - code = get_request().form.get('code') - if not code: - return redirect(get_publisher().get_root_url()) - try: - token = Token.get(code) - except KeyError: - return redirect(get_publisher().get_root_url()) - if token.type != 'anonymous-access-code': - return redirect(get_publisher().get_root_url()) - formdef_urlname, formdata_id = token.formdata_reference - try: - formdata = FormDef.get_by_urlname(formdef_urlname).data_class().get(formdata_id) - except KeyError: - return redirect(get_publisher().get_root_url()) - session = get_session() - if not hasattr(session, '_wf_anonymous_access_authorized'): - session._wf_anonymous_access_authorized = [] - session._wf_anonymous_access_authorized.append(formdata.get_url()) - return redirect(formdata.get_url() + 'access/') - - def links [html] (self): - links = Link.select() - if not links: - return - - Link.sort_by_position(links) - - '' - - - def announces [html] (self): - announces = Announce.get_published_announces() - if not announces: - return - - '
          ' - '

          %s

          ' % _('Announces to citizens') - for item in announces[:3]: - '
          ' - '

          ' - if item.publication_time: - time.strftime(misc.date_format(), item.publication_time) - ' - ' - item.title - '

          ' - '

          ' - item.text - '

          ' - '
          ' - - '' - '
          ' - - def myspace_snippet [html] (self): - '
          ' - '

          %s

          ' % _('My Space') - '
            ' - if get_request().user and not get_request().user.anonymous: - '
          • %s
          • ' % _('Access to your personal space') - '
          • %s
          • ' % _('Logout') - else: - '
          • %s
          • ' % _('Registration') - '
          • %s
          • ' % _('Login') - '
          ' - '
          ' - - - def page_view [html] (self, key, title, urlname = None): - if not urlname: - urlname = key[3:].replace(str('_'), str('-')) - get_response().breadcrumb.append((urlname, title)) - template.html_top(title) - '
          ' - htmltext(TextsDirectory.get_html_text(key)) - '
          ' - - def informations_editeur [html] (self): - get_response().filter['bigdiv'] = 'info' - return self.page_view('aq-editor-info', _('Editor Informations'), - urlname = 'informations_editeur') - - def accessibility(self): - get_response().filter['bigdiv'] = 'accessibility' - return self.page_view('aq-accessibility', _('Accessibility Statement')) - - def contact(self): - get_response().filter['bigdiv'] = 'contact' - return self.page_view('aq-contact', _('Contact')) - - def help(self): - get_response().filter['bigdiv'] = 'help' - return self.page_view('aq-help', _('Help')) - - -from qommon.publisher import get_publisher_class -get_publisher_class().root_directory_class = AlternateRootDirectory -get_publisher_class().after_login_url = 'myspace/' -get_publisher_class().use_sms_feature = True - -# help links -get_publisher_class().backoffice_help_url = { - 'fr': 'https://doc.entrouvert.org/au-quotidien/stable/guide-gestionnaire.html', -} -get_publisher_class().admin_help_url = { - 'fr': 'https://doc.entrouvert.org/auquotidien/dev/', -} - - -EmailsDirectory.register('announces-subscription-confirmation', - N_('Confirmation of Announces Subscription'), - N_('Available variables: change_url, cancel_url, time, sitename'), - default_subject = N_('Announce Subscription Request'), - default_body = N_("""\ -You have (or someone impersonating you has) requested to subscribe to -announces from [sitename]. To confirm this request, visit the -following link: - -[confirm_url] - -If you are not the person who made this request, or you wish to cancel -this request, visit the following link: - -[cancel_url] - -If you do nothing, the request will lapse after 3 days (precisely on -[time]). -""")) - - -TextsDirectory.register('aq-announces-subscription', - N_('Text on announces subscription page'), - default = N_('''\ -

          -FIXME -'

          ''')) - -TextsDirectory.register('aq-sms-demo', - N_('Text when subscribing to announces SMS and configured as demo'), - default = N_(''' -

          -Receiving announces by SMS is not possible in this demo -

          ''')) - -TextsDirectory.register('aq-editor-info', N_('Editor Informations')) -TextsDirectory.register('aq-accessibility', N_('Accessibility Statement')) -TextsDirectory.register('aq-contact', N_('Contact Information')) -TextsDirectory.register('aq-help', N_('Help')) -TextsDirectory.register('aq-sso-text', N_('Connecting with Identity Provider'), - default = N_('''

          Connecting with Identity Provider

          -

          You can also use your identity provider to connect. -

          ''')) - -TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg = True) diff --git a/extra/modules/root.py b/extra/modules/root.py new file mode 100644 index 0000000..11f0d54 --- /dev/null +++ b/extra/modules/root.py @@ -0,0 +1,1308 @@ +from quixote import get_publisher, get_response, get_request, redirect, get_session +from quixote.directory import Directory +from quixote.html import TemplateIO, htmltext +from quixote.util import StaticDirectory + +from wcs.qommon.misc import get_variadic_url + +import os +import re +import string +import urlparse + +try: + import lasso +except ImportError: + pass + +import wcs +import wcs.root +import qommon +from qommon import get_cfg, get_logger +from qommon import template +from qommon import errors +from qommon.form import * +from qommon import logger +from wcs.roles import logged_users_role + +from qommon import emails +from qommon.sms import SMS +from wcs.categories import Category +from wcs.formdef import FormDef +from qommon.tokens import Token +from qommon.admin.emails import EmailsDirectory +from qommon.admin.texts import TextsDirectory + +from links import Link +from announces import Announce, AnnounceSubscription +from myspace import MyspaceDirectory +from agenda import AgendaDirectory +from events import Event, get_default_event_tags +from payments import PublicPaymentDirectory +from payments_ui import InvoicesDirectory + +import admin + +import wcs.forms.root +from wcs.workflows import Workflow + +from saml2 import Saml2Directory + +OldRootDirectory = wcs.root.RootDirectory + +import qommon.ident.password +import qommon.ident.idp + +import drupal +import ezldap_ui +import msp_ui + +def category_get_homepage_position(self): + if hasattr(self, 'homepage_position') and self.homepage_position: + return self.homepage_position + if self.url_name == 'consultations': + return '2nd' + return '1st' +Category.get_homepage_position = category_get_homepage_position + +def category_get_limit(self): + if hasattr(self, 'limit') and self.limit is not None: + return self.limit + return 3 +Category.get_limit = category_get_limit + + +class FormsRootDirectory(wcs.forms.root.RootDirectory): + + def _q_index(self, *args): + get_response().filter['is_index'] = True + return wcs.forms.root.RootDirectory._q_index(self, *args) + + def user_forms(self, user_forms): + r = TemplateIO(html=True) + base_url = get_publisher().get_root_url() + + draft = [x for x in user_forms if x.is_draft()] + if draft: + r += htmltext('

          %s

          ') % _('My Current Drafts') + r += htmltext('
            ') + for f in draft: + r += htmltext('
          • %s, %s
          • ') % (base_url, + f.formdef.category.url_name, + f.formdef.url_name, f.id, f.formdef.name, + misc.localstrftime(f.receipt_time)) + r += htmltext('
          ') + + forms_by_status_name = {} + for f in user_forms: + if f.is_draft(): + continue + status = f.get_visible_status() + if status: + status_name = status.name + else: + status_name = None + if status_name in forms_by_status_name: + forms_by_status_name[status_name].append(f) + else: + forms_by_status_name[status_name] = [f] + for status_name in forms_by_status_name: + if status_name: + r += htmltext('

          %s

          ') % _('My forms with status "%s"') % status_name + else: + r += htmltext('

          %s

          ') % _('My forms with an unknown status') % status_name + r += htmltext('
            ') + forms_by_status_name[status_name].sort(lambda x,y: cmp(x.receipt_time, y.receipt_time)) + for f in forms_by_status_name[status_name]: + if f.formdef.category_id: + category_url = f.formdef.category.url_name + else: + category_url = '.' + r += htmltext('
          • %s, %s
          • ') % ( + base_url, + category_url, + f.formdef.url_name, f.id, f.formdef.name, + misc.localstrftime(f.receipt_time)) + r += htmltext('
          ') + return r.getvalue() + + +class AnnounceDirectory(Directory): + _q_exports = ['', 'edit', 'delete', 'email'] + + def __init__(self, announce): + self.announce = announce + + def _q_index(self): + template.html_top(_('Announces to citizens')) + r = TemplateIO(html=True) + + if self.announce.publication_time: + date_heading = '%s - ' % time.strftime(misc.date_format(), self.announce.publication_time) + else: + date_heading = '' + + r += htmltext('

          %s%s

          ') % (date_heading, self.announce.title) + + r += htmltext('

          ') + r += self.announce.text + r += htmltext('

          ') + + r += htmltext('

          ') + r += htmltext('%s') % _('Back') + r += htmltext('

          ') + return r.getvalue() + + +class AnnouncesDirectory(Directory): + _q_exports = ['', 'subscribe', 'email', 'atom', 'sms', 'emailconfirm', + 'email_unsubscribe', 'sms_unsubscribe', 'smsconfirm', 'rawlist'] + + def _q_traverse(self, path): + get_response().breadcrumb.append(('announces/', _('Announces'))) + return Directory._q_traverse(self, path) + + def _q_index(self): + template.html_top(_('Announces to citizens')) + r = TemplateIO(html=True) + r += self.announces_list() + r += htmltext('') + return r.getvalue() + + def _get_announce_subscription(self): + """ """ + sub = None + if get_request().user: + subs = AnnounceSubscription.select(lambda x: x.user_id == get_request().user.id) + if subs: + sub = subs[0] + return sub + + def rawlist(self): + get_response().filter = None + return self.announces_list() + + def announces_list(self): + announces = Announce.get_published_announces() + if not announces: + raise errors.TraversalError() + + # XXX: will need pagination someday + r = TemplateIO(html=True) + for item in announces: + r += htmltext('
          \n') + r += htmltext('

          ') + if item.publication_time: + r += time.strftime(misc.date_format(), item.publication_time) + r += ' - ' + r += item.title + r += htmltext('

          \n') + r += htmltext('

          \n') + r += item.text + r += htmltext('\n

          \n') + r += htmltext('
          \n') + return r.getvalue() + + + def sms(self): + sms_mode = get_cfg('sms', {}).get('mode', 'none') + + if sms_mode == 'none': + raise errors.TraversalError() + + get_response().breadcrumb.append(('sms', _('SMS'))) + template.html_top(_('Receiving announces by SMS')) + r = TemplateIO(html=True) + + if sms_mode == 'demo': + r += TextsDirectory.get_html_text('aq-sms-demo') + else: + announces_cfg = get_cfg('announces',{}) + mobile_mask = announces_cfg.get('mobile_mask') + if mobile_mask: + mobile_mask = ' (' + mobile_mask + ')' + else: + mobile_mask = '' + form = Form(enctype='multipart/form-data') + form.add(StringWidget, 'mobile', title = _('Mobile number %s') % mobile_mask, size=12, required=True) + form.add_submit('submit', _('Subscribe')) + form.add_submit('cancel', _('Cancel')) + + if form.get_submit() == 'cancel': + return redirect('subscribe') + + if form.is_submitted() and not form.has_errors(): + s = self.sms_submit(form) + if s == False: + r += form.render() + else: + return redirect("smsconfirm") + else: + r += form.render() + return r.getvalue() + + def sms_submit(self, form): + mobile = form.get_widget("mobile").parse() + # clean the string, remove any extra character + mobile = re.sub('[^0-9+]','',mobile) + # if a mask was set, validate + announces_cfg = get_cfg('announces',{}) + mobile_mask = announces_cfg.get('mobile_mask') + if mobile_mask: + mobile_regexp = re.sub('X','[0-9]', mobile_mask) + '$' + if not re.match(mobile_regexp, mobile): + form.set_error("mobile", _("Phone number invalid ! It must match ") + mobile_mask) + return False + if mobile.startswith('00'): + mobile = '+' + mobile[2:] + else: + # Default to france international prefix + if not mobile.startswith('+'): + mobile = re.sub("^0", "+33", mobile) + sub = self._get_announce_subscription() + if not sub: + sub = AnnounceSubscription() + if get_request().user: + sub.user_id = get_request().user.id + + if mobile: + sub.sms = mobile + + if not get_request().user: + sub.enabled = False + + sub.store() + + # Asking sms confirmation + token = Token(3 * 86400, 4, string.digits) + token.type = 'announces-subscription-confirmation' + token.subscription_id = sub.id + token.store() + + message = _("Confirmation code : %s") % str(token.id) + sms_cfg = get_cfg('sms', {}) + sender = sms_cfg.get('sender', 'AuQuotidien')[:11] + mode = sms_cfg.get('mode', 'none') + sms = SMS.get_sms_class(mode) + try: + sms.send(sender, [mobile], message) + except errors.SMSError, e: + get_logger().error(e) + form.set_error("mobile", _("Send SMS confirmation failed")) + sub.remove("sms") + return False + + def smsconfirm(self): + template.html_top(_('Receiving announces by SMS confirmation')) + r = TemplateIO(html=True) + r += htmltext("

          %s

          ") % _("You will receive a confirmation code by SMS.") + form = Form(enctype='multipart/form-data') + form.add(StringWidget, 'code', title = _('Confirmation code (4 characters)'), size=12, required=True) + form.add_submit('submit', _('Subscribe')) + form.add_submit('cancel', _('Cancel')) + + if form.get_submit() == 'cancel': + return redirect('..') + + if form.is_submitted() and not form.has_errors(): + token = None + id = form.get_widget("code").parse() + try: + token = Token.get(id) + except KeyError: + form.set_error("code", _('Invalid confirmation code.')) + else: + if token.type != 'announces-subscription-confirmation': + form.set_error("code", _('Invalid confirmation code.')) + else: + sub = AnnounceSubscription.get(token.subscription_id) + token.remove_self() + sub.enabled_sms = True + sub.store() + return redirect('.') + r += form.render() + else: + r += form.render() + + return r.getvalue() + + def sms_unsubscribe(self): + sub = self._get_announce_subscription() + + form = Form(enctype='multipart/form-data') + if not sub: + return redirect('..') + + form.add_submit('submit', _('Unsubscribe')) + form.add_submit('cancel', _('Cancel')) + + if form.get_submit() == 'cancel': + return redirect('..') + + get_response().breadcrumb.append(('sms', _('SMS Unsubscription'))) + template.html_top() + r = TemplateIO(html=True) + + if form.is_submitted() and not form.has_errors(): + if sub: + sub.remove("sms") + + root_url = get_publisher().get_root_url() + r += htmltext('

          ') + r += _('You have been unsubscribed from announces') + r += htmltext('

          ') + if not get_response().iframe_mode: + r += htmltext('%s') % (root_url, _('Back Home')) + else: + r += htmltext('

          ') + r += _('Do you want to stop receiving announces by sms ?') + r += htmltext('

          ') + r += form.render() + + return r.getvalue() + + + def subscribe(self): + get_response().breadcrumb.append(('subscribe', _('Subscription'))) + template.html_top(_('Receiving Announces')) + r = TemplateIO(html=True) + + r += TextsDirectory.get_html_text('aq-announces-subscription') + + sub = self._get_announce_subscription() + + r += htmltext('
            ') + if sub and sub.email: + r += htmltext('
          • ') + r += htmltext('%s') % _('Email (currently subscribed)') + r += htmltext(' %s
          • ') % _('Unsubscribe') + else: + r += htmltext('
          • %s
          • ') % _('Email') + if sub and sub.sms: + r += htmltext('
          • ') + if sub.enabled_sms: + r += htmltext('%s') % _('SMS %s (currently subscribed)') % sub.sms + else: + r += htmltext('%s') % _('SMS %s (currently not confirmed)') % sub.sms + r += htmltext(' %s ') % _('Confirmation') + r += htmltext(' %s
          • ') % _('Unsubscribe') + elif get_cfg('sms', {}).get('mode', 'none') != 'none': + r += htmltext('
          • %s
          • ') % _('SMS') + r += htmltext('
          • %s') % _('Feed') + r += htmltext('
          ') + return r.getvalue() + + def email(self): + get_response().breadcrumb.append(('email', _('Email Subscription'))) + template.html_top(_('Receiving Announces by email')) + r = TemplateIO(html=True) + + form = Form(enctype='multipart/form-data') + if get_request().user: + if get_request().user.email: + r += htmltext('

          ') + r += _('You are logged in and your email is %s, ok to subscribe ?') % \ + get_request().user.email + r += htmltext('

          ') + form.add_submit('submit', _('Subscribe')) + else: + r += htmltext('

          ') + r += _("You are logged in but there is no email address in your profile.") + r += htmltext('

          ') + form.add(EmailWidget, 'email', title = _('Email'), required = True) + form.add_submit('submit', _('Subscribe')) + form.add_submit('submit-remember', _('Subscribe and add this email to my profile')) + else: + r += htmltext('

          ') + r += _('FIXME will only be used for this purpose etc.') + r += htmltext('

          ') + form.add(EmailWidget, 'email', title = _('Email'), required = True) + form.add_submit('submit', _('Subscribe')) + + form.add_submit('cancel', _('Cancel')) + + if form.get_submit() == 'cancel': + return redirect('subscribe') + + if form.is_submitted() and not form.has_errors(): + s = self.email_submit(form) + if s is not False: + return s + else: + r += form.render() + + return r.getvalue() + + def email_submit(self, form): + sub = self._get_announce_subscription() + if not sub: + sub = AnnounceSubscription() + + if get_request().user: + sub.user_id = get_request().user.id + + if form.get_widget('email'): + sub.email = form.get_widget('email').parse() + elif get_request().user.email: + sub.email = get_request().user.email + + if not get_request().user: + sub.enabled = False + + sub.store() + + if get_request().user: + r = TemplateIO(html=True) + root_url = get_publisher().get_root_url() + r += htmltext('

          ') + r += _('You have been subscribed to the announces.') + r += htmltext('

          ') + if not get_response().iframe_mode: + r += htmltext('%s') % (root_url, _('Back Home')) + return r.getvalue() + + # asking email confirmation before subscribing someone + token = Token(3 * 86400) + token.type = 'announces-subscription-confirmation' + token.subscription_id = sub.id + token.store() + data = { + 'confirm_url': get_request().get_url() + 'confirm?t=%s&a=cfm' % token.id, + 'cancel_url': get_request().get_url() + 'confirm?t=%s&a=cxl' % token.id, + 'time': misc.localstrftime(time.localtime(token.expiration)), + } + + emails.custom_ezt_email('announces-subscription-confirmation', + data, sub.email, exclude_current_user = False) + + r = TemplateIO(html=True) + root_url = get_publisher().get_root_url() + r += htmltext('

          ') + r += _('You have been sent an email for confirmation') + r += htmltext('

          ') + if not get_response().iframe_mode: + r += htmltext('%s') % (root_url, _('Back Home')) + return r.getvalue() + + def emailconfirm(self): + tokenv = get_request().form.get('t') + action = get_request().form.get('a') + + root_url = get_publisher().get_root_url() + + try: + token = Token.get(tokenv) + except KeyError: + return template.error_page( + _('The token you submitted does not exist, has expired, or has been cancelled.'), + continue_to = (root_url, _('home page'))) + + if token.type != 'announces-subscription-confirmation': + return template.error_page( + _('The token you submitted is not appropriate for the requested task.'), + continue_to = (root_url, _('home page'))) + + sub = AnnounceSubscription.get(token.subscription_id) + + if action == 'cxl': + r = TemplateIO(html=True) + root_url = get_publisher().get_root_url() + template.html_top(_('Email Subscription')) + r += htmltext('

          %s

          ') % _('Request Cancelled') + r += htmltext('

          %s

          ') % _('The request for subscription has been cancelled.') + r += htmltext('

          ') + r += htmltext(_('Continue to home page') % root_url) + r += htmltext('

          ') + token.remove_self() + sub.remove_self() + return r.getvalue() + + if action == 'cfm': + token.remove_self() + sub.enabled = True + sub.store() + r = TemplateIO(html=True) + root_url = get_publisher().get_root_url() + template.html_top(_('Email Subscription')) + r += htmltext('

          %s

          ') % _('Subscription Confirmation') + r += htmltext('

          %s

          ') % _('Your subscription to announces is now effective.') + r += htmltext('

          ') + r += htmltext(_('Continue to home page') % root_url) + r += htmltext('

          ') + return r.getvalue() + + def atom(self): + response = get_response() + response.set_content_type('application/atom+xml') + + from pyatom import pyatom + xmldoc = pyatom.XMLDoc() + feed = pyatom.Feed() + xmldoc.root_element = feed + feed.title = get_cfg('misc', {}).get('sitename') or 'Au Quotidien' + feed.id = get_request().get_url() + + author_email = get_cfg('emails', {}).get('reply_to') + if not author_email: + author_email = get_cfg('emails', {}).get('from') + if author_email: + feed.authors.append(pyatom.Author(author_email)) + + announces = Announce.get_published_announces() + + if announces and announces[0].modification_time: + feed.updated = misc.format_time(announces[0].modification_time, + '%(year)s-%(month)02d-%(day)02dT%(hour)02d:%(minute)02d:%(second)02dZ', + gmtime = True) + feed.links.append(pyatom.Link(get_request().get_url(1) + '/')) + + for item in announces: + entry = item.get_atom_entry() + if entry: + feed.entries.append(entry) + + return str(feed) + + def email_unsubscribe(self): + sub = self._get_announce_subscription() + + form = Form(enctype='multipart/form-data') + if not sub: + form.add(EmailWidget, 'email', title = _('Email'), required = True) + + form.add_submit('submit', _('Unsubscribe')) + form.add_submit('cancel', _('Cancel')) + + if form.get_submit() == 'cancel': + return redirect('..') + + get_response().breadcrumb.append(('email', _('Email Unsubscription'))) + template.html_top() + r = TemplateIO(html=True) + + if form.is_submitted() and not form.has_errors(): + if sub: + sub.remove("email") + else: + email = form.get_widget('email').parse() + for s in AnnounceSubscription.select(): + if s.email == email: + s.remove("email") + + root_url = get_publisher().get_root_url() + r += htmltext('

          ') + r += _('You have been unsubscribed from announces') + r += htmltext('

          ') + if not get_response().iframe_mode: + r += htmltext('%s') % (root_url, _('Back Home')) + + else: + r += htmltext('

          ') + r += _('Do you want to stop receiving announces by email?') + r += htmltext('

          ') + r += form.render() + + return r.getvalue() + + def _q_lookup(self, component): + try: + announce = Announce.get(component) + except KeyError: + raise errors.TraversalError() + + if announce.hidden: + raise errors.TraversalError() + + get_response().breadcrumb.append((str(announce.id), announce.title)) + return AnnounceDirectory(announce) + +OldRegisterDirectory = wcs.root.RegisterDirectory + +class AlternateRegisterDirectory(OldRegisterDirectory): + def _q_traverse(self, path): + get_response().filter['bigdiv'] = 'new_member' + return OldRegisterDirectory._q_traverse(self, path) + + def _q_index(self): + get_logger().info('register') + ident_methods = get_cfg('identification', {}).get('methods', []) + + if len(ident_methods) == 0: + idps = get_cfg('idp', {}) + if len(idps) == 0: + return template.error_page(_('Authentication subsystem is not yet configured.')) + ident_methods = ['idp'] # fallback to old behaviour; liberty. + + if len(ident_methods) == 1: + method = ident_methods[0] + else: + method = 'password' + + return qommon.ident.register(method) + +OldLoginDirectory = wcs.root.LoginDirectory + +class AlternateLoginDirectory(OldLoginDirectory): + def _q_traverse(self, path): + get_response().filter['bigdiv'] = 'member' + return OldLoginDirectory._q_traverse(self, path) + + def _q_index(self): + get_logger().info('login') + ident_methods = get_cfg('identification', {}).get('methods', []) + + if len(ident_methods) > 1 and 'idp' in ident_methods: + # if there is more than one identification method, and there is a + # possibility of SSO, if we got there as a consequence of an access + # unauthorized url on admin/ or backoffice/, then idp auth method + # is chosen forcefully. + after_url = get_session().after_url + if after_url: + root_url = get_publisher().get_root_url() + after_path = urlparse.urlparse(after_url)[2] + after_path = after_path[len(root_url):] + if after_path.startswith(str('admin')) or \ + after_path.startswith(str('backoffice')): + ident_methods = ['idp'] + + # don't display authentication system choice + if len(ident_methods) == 1: + method = ident_methods[0] + try: + return qommon.ident.login(method) + except KeyError: + get_logger().error('failed to login with method %s' % method) + return errors.TraversalError() + + if sorted(ident_methods) == ['idp', 'password']: + r = TemplateIO(html=True) + get_response().breadcrumb.append(('login', _('Login'))) + identities_cfg = get_cfg('identities', {}) + form = Form(enctype = 'multipart/form-data', id = 'login-form', use_tokens = False) + if identities_cfg.get('email-as-username', False): + form.add(StringWidget, 'username', title = _('Email'), size=25, required=True) + else: + form.add(StringWidget, 'username', title = _('Username'), size=25, required=True) + form.add(PasswordWidget, 'password', title = _('Password'), size=25, required=True) + form.add_submit('submit', _('Connect')) + if form.is_submitted() and not form.has_errors(): + tmp = qommon.ident.password.MethodDirectory().login_submit(form) + if not form.has_errors(): + return tmp + + r += htmltext('
          ') + r += get_session().display_message() + r += form.render() + + base_url = get_publisher().get_root_url() + r += htmltext('

          %s

          ') % ( + base_url, _('Forgotten password ?')) + + r += htmltext('
          ') + + # XXX: this part only supports a single IdP + r += htmltext('
          ') + r += TextsDirectory.get_html_text('aq-sso-text') + form = Form(enctype='multipart/form-data', + action = '%sident/idp/login' % base_url) + form.add_hidden('method', 'idp') + for kidp, idp in get_cfg('idp', {}).items(): + p = lasso.Provider(lasso.PROVIDER_ROLE_IDP, + misc.get_abs_path(idp['metadata']), + misc.get_abs_path(idp.get('publickey')), None) + form.add_hidden('idp', p.providerId) + break + form.add_submit('submit', _('Connect')) + + r += form.render() + r += htmltext('
          ') + + get_request().environ['REQUEST_METHOD'] = 'GET' + + r += htmltext("""""") + return r.getvalue() + else: + return OldLoginDirectory._q_index(self) + + +OldIdentDirectory = wcs.root.IdentDirectory +class AlternateIdentDirectory(OldIdentDirectory): + def _q_traverse(self, path): + get_response().filter['bigdiv'] = 'member' + return OldIdentDirectory._q_traverse(self, path) + + +class AlternateRootDirectory(OldRootDirectory): + _q_exports = ['', 'admin', 'backoffice', 'forms', 'login', 'logout', + 'liberty', 'token', 'saml', 'register', 'ident', 'afterjobs', + ('informations-editeur', 'informations_editeur'), 'index2', + ('announces', 'announces_dir'), + 'accessibility', 'contact', 'help', + 'myspace', 'services', 'agenda', 'categories', 'user', + ('tmp-upload', 'tmp_upload'), 'json', '__version__', + 'themes', 'pages', 'payment', 'invoices', 'accesscode', 'roles', + 'msp'] + + admin = admin.AdminRootDirectory() + announces_dir = AnnouncesDirectory() + register = AlternateRegisterDirectory() + login = AlternateLoginDirectory() + ident = AlternateIdentDirectory() + myspace = MyspaceDirectory() + agenda = AgendaDirectory() + saml = Saml2Directory() + payment = PublicPaymentDirectory() + invoices = InvoicesDirectory() + msp = msp_ui.MSPDirectory() + + def get_substitution_variables(self): + d = {} + def print_links(fd): + fd.write(str(self.links())) + d['links'] = print_links + return d + + def _q_traverse(self, path): + if get_publisher().has_site_option('drupal'): + drupal.try_auth() + if get_publisher().has_site_option('ezldap'): + ezldap_ui.try_auth(self) + + session = get_session() + if session: + get_request().user = session.get_user() + else: + get_request().user = None + + get_publisher().substitutions.feed(get_request().user) + + response = get_response() + if not hasattr(response, 'filter'): + response.filter = {} + + response.filter['gauche'] = self.box_side(path) + response.filter['keywords'] = template.get_current_theme().get('keywords') + get_publisher().substitutions.feed(self) + + response.breadcrumb = [ ('', _('Home')) ] + + if not self.admin: + self.admin = get_publisher().admin_directory_class() + + if not self.backoffice: + self.backoffice = get_publisher().backoffice_directory_class() + + try: + return Directory._q_traverse(self, path) + except errors.TraversalError, e: + try: + f = FormDef.get_by_urlname(path[0]) + except KeyError: + pass + else: + base_url = get_publisher().get_root_url() + + uri_rest = get_request().environ.get('REQUEST_URI') + if not uri_rest: + uri_rest = get_request().get_path() + if uri_rest.startswith(base_url): + uri_rest = uri_rest[len(base_url):] + elif uri_rest.startswith('/'): + # dirty hack, ezldap reverseproxy uses a fake base_url + uri_rest = uri_rest[1:] + if f.category_id: + return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest)) + + raise e + + + def _q_lookup(self, component): + if component == 'qo': + dirname = os.path.join(get_publisher().data_dir, 'qommon') + return StaticDirectory(dirname, follow_symlinks = True) + + if component == 'aq': + dirname = os.path.join(get_publisher().data_dir, 'qommon', 'auquotidien') + return StaticDirectory(dirname, follow_symlinks = True) + + if component in ('css','images'): + return OldRootDirectory._q_lookup(self, component) + + # is this a category ? + try: + category = Category.get_by_urlname(component) + except KeyError: + pass + else: + return FormsRootDirectory(category) + + # is this a formdef ? + try: + formdef = FormDef.get_by_urlname(component) + except KeyError: + pass + else: + if formdef.category_id is None: + get_response().filter['bigdiv'] = 'rub_service' + return FormsRootDirectory()._q_lookup(component) + # if there is category, let it fall back to raise TraversalError, + # it will get caught in _q_traverse that will redirect it to an + # URL embedding the category + + return None + + def json(self): + return FormsRootDirectory().json() + + def categories(self): + return FormsRootDirectory().categories() + + def _q_index(self): + if get_request().is_json(): + return FormsRootDirectory().json() + + root_url = get_publisher().get_root_url() + if get_request().user and get_request().user.anonymous and get_request().user.lasso_dump: + return redirect('%smyspace/new' % root_url) + + if get_response().iframe_mode: + # never display home page in an iframe + return redirect('%sservices' % root_url) + + template.html_top() + r = TemplateIO(html=True) + get_response().filter['is_index'] = True + + if not 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []): + t = TextsDirectory.get_html_text('aq-home-page') + if not t: + if get_request().user: + t = TextsDirectory.get_html_text('welcome-logged') + else: + t = TextsDirectory.get_html_text('welcome-unlogged') + if t: + r += htmltext('
          ') + r += t + r += htmltext('
          ') + + r += htmltext('
          ') + r += self.box_services(position='1st') + r += htmltext('
          ') + r += htmltext('
          ') + r += self.myspace_snippet() + r += self.box_services(position='2nd') + r += self.consultations() + r += self.announces() + r += htmltext('
          ') + + user = get_request().user + if user and user.can_go_in_backoffice(): + get_response().filter['backoffice'] = True + + return r.getvalue() + + def services(self): + template.html_top() + get_response().filter['bigdiv'] = 'rub_service' + return self.box_services(level = 2) + + def box_services(self, level=3, position=None): + ## Services + if get_request().user and get_request().user.roles: + accepted_roles = get_request().user.roles + else: + accepted_roles = [] + + cats = Category.select(order_by = 'name') + cats = [x for x in cats if x.url_name != 'consultations'] + Category.sort_by_position(cats) + + all_formdefs = FormDef.select(lambda x: not x.is_disabled() or x.disabled_redirection, + order_by = 'name') + + if position: + t = self.display_list_of_formdefs( + [x for x in cats if x.get_homepage_position() == position], + all_formdefs, accepted_roles) + else: + t = self.display_list_of_formdefs(cats, all_formdefs, accepted_roles) + + if not t: + return + + r = TemplateIO(html=True) + + if position == '2nd': + r += htmltext('
          ') + else: + r += htmltext('
          ') + if level == 2: + r += htmltext('

          %s

          ') % _('Services') + else: + r += htmltext('

          %s

          ') % _('Services') + + if get_response().iframe_mode: + if get_request().user: + message = TextsDirectory.get_html_text('welcome-logged') + else: + message = TextsDirectory.get_html_text('welcome-unlogged') + + if message: + r += htmltext('
          ') + r += message + r += htmltext('
          ') + elif 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []): + homepage_text = TextsDirectory.get_html_text('aq-home-page') + if homepage_text: + r += htmltext('
          ') + r += homepage_text + r += htmltext('
          ') + + r += htmltext('
            ') + r += t + r += htmltext('
          ') + + r += htmltext('
          ') + return r.getvalue() + + def display_list_of_formdefs(self, cats, all_formdefs, accepted_roles): + r = TemplateIO(html=True) + for category in cats: + if category.url_name == 'consultations': + self.consultations_category = category + continue + formdefs = [x for x in all_formdefs if x.category_id == category.id] + formdefs_advertise = [] + + for formdef in formdefs[:]: + if formdef.is_disabled(): # is a redirection + continue + if not formdef.roles: + continue + if not get_request().user: + if formdef.always_advertise: + formdefs_advertise.append(formdef) + formdefs.remove(formdef) + continue + if logged_users_role().id in formdef.roles: + continue + for q in accepted_roles: + if q in formdef.roles: + break + else: + if formdef.always_advertise: + formdefs_advertise.append(formdef) + formdefs.remove(formdef) + + if not formdefs and not formdefs_advertise: + continue + + r += htmltext('
        3. ') + r += htmltext('') + r += htmltext('') % category.url_name + r += category.name + r += htmltext('\n') + if category.description: + if category.description[0] == '<': + r += htmltext(category.description) + else: + r += htmltext('

          ') + r += category.description + r += htmltext('

          ') + r += htmltext('
            ') + limit = category.get_limit() + for formdef in formdefs[:limit]: + r += htmltext('
          • ') + r += htmltext('%s') % (category.url_name, formdef.url_name, formdef.name) + r += htmltext('
          • \n') + if len(formdefs) < limit: + for formdef in formdefs_advertise[:limit-len(formdefs)]: + r += htmltext('
          • ') + r += htmltext('%s') % (category.url_name, formdef.url_name, formdef.name) + r += ' (%s)' % _('authentication required') + r += htmltext('
          • \n') + if (len(formdefs)+len(formdefs_advertise)) > limit: + r += htmltext('
          • %s
          • ') % (category.url_name, + _('Access to all forms of the "%s" category') % category.name, + _('Access to all forms in this category')) + r += htmltext('
          ') + r += htmltext('
        4. \n') + + return r.getvalue() + + def consultations(self): + cats = [x for x in Category.select() if x.url_name == 'consultations'] + if not cats: + return + consultations_category = cats[0] + formdefs = FormDef.select(lambda x: ( + x.category_id == consultations_category.id and + (not x.is_disabled() or x.disabled_redirection)), + order_by = 'name') + if not formdefs: + return + ## Consultations + r = TemplateIO(html=True) + r += htmltext('
          ') + r += htmltext('

          %s

          ') % _('Consultations') + if consultations_category.description: + if consultations_category.description[0] == '<': + r += htmltext(consultations_category.description) + else: + r += htmltext('

          ') + r += consultations_category.description + r += htmltext('

          ') + r += htmltext('
            ') + for formdef in formdefs: + r += htmltext('
          • ') + r += htmltext('%s') % (consultations_category.url_name, + formdef.url_name, formdef.name) + r += htmltext('
          • ') + r += htmltext('
          ') + r += htmltext('
          ') + return r.getvalue() + + def box_side(self, path): + r = TemplateIO(html=True) + r += htmltext('') + return r.getvalue() + + def has_anonymous_access_codes(self): + for workflow in Workflow.select(): + for wfstatus in workflow.possible_status: + for wfitem in wfstatus.items: + if wfitem.key == 'create-anonymous-access-code': + return True + return False + + def accesscode(self): + code = get_request().form.get('code') + if not code: + return redirect(get_publisher().get_root_url()) + try: + token = Token.get(code) + except KeyError: + return redirect(get_publisher().get_root_url()) + if token.type != 'anonymous-access-code': + return redirect(get_publisher().get_root_url()) + formdef_urlname, formdata_id = token.formdata_reference + try: + formdata = FormDef.get_by_urlname(formdef_urlname).data_class().get(formdata_id) + except KeyError: + return redirect(get_publisher().get_root_url()) + session = get_session() + if not hasattr(session, '_wf_anonymous_access_authorized'): + session._wf_anonymous_access_authorized = [] + session._wf_anonymous_access_authorized.append(formdata.get_url()) + return redirect(formdata.get_url() + 'access/') + + def links(self): + links = Link.select() + if not links: + return + + Link.sort_by_position(links) + + r = TemplateIO(html=True) + + r += htmltext('') + return r.getvalue() + + def announces(self): + announces = Announce.get_published_announces() + if not announces: + return + + r = TemplateIO(html=True) + r += htmltext('
          ') + r += htmltext('

          %s

          ') % _('Announces to citizens') + for item in announces[:3]: + r += htmltext('
          ') + r += htmltext('

          ') + if item.publication_time: + r += time.strftime(misc.date_format(), item.publication_time) + r += ' - ' + r += item.title + r += htmltext('

          ') + r += htmltext('

          ') + r += item.text + r += htmltext('

          ') + r += htmltext('
          ') + + r += htmltext('') + r += htmltext('
          ') + return r.getvalue() + + def myspace_snippet(self): + r = TemplateIO(html=True) + r += htmltext('
          ') + r += htmltext('

          %s

          ') % _('My Space') + r += htmltext('
            ') + if get_request().user and not get_request().user.anonymous: + r += htmltext('
          • %s
          • ') % _('Access to your personal space') + r += htmltext('
          • %s
          • ') % _('Logout') + else: + r += htmltext('
          • %s
          • ') % _('Registration') + r += htmltext('
          • %s
          • ') % _('Login') + r += htmltext('
          ') + r += htmltext('
          ') + return r.getvalue() + + def page_view(self, key, title, urlname = None): + if not urlname: + urlname = key[3:].replace(str('_'), str('-')) + get_response().breadcrumb.append((urlname, title)) + template.html_top(title) + r = TemplateIO(html=True) + r += htmltext('
          ') + r += htmltext(TextsDirectory.get_html_text(key)) + r += htmltext('
          ') + return r.getvalue() + + def informations_editeur(self): + get_response().filter['bigdiv'] = 'info' + return self.page_view('aq-editor-info', _('Editor Informations'), + urlname = 'informations_editeur') + + def accessibility(self): + get_response().filter['bigdiv'] = 'accessibility' + return self.page_view('aq-accessibility', _('Accessibility Statement')) + + def contact(self): + get_response().filter['bigdiv'] = 'contact' + return self.page_view('aq-contact', _('Contact')) + + def help(self): + get_response().filter['bigdiv'] = 'help' + return self.page_view('aq-help', _('Help')) + + +from qommon.publisher import get_publisher_class +get_publisher_class().root_directory_class = AlternateRootDirectory +get_publisher_class().after_login_url = 'myspace/' +get_publisher_class().use_sms_feature = True + +# help links +get_publisher_class().backoffice_help_url = { + 'fr': 'https://doc.entrouvert.org/au-quotidien/stable/guide-gestionnaire.html', +} +get_publisher_class().admin_help_url = { + 'fr': 'https://doc.entrouvert.org/auquotidien/dev/', +} + + +EmailsDirectory.register('announces-subscription-confirmation', + N_('Confirmation of Announces Subscription'), + N_('Available variables: change_url, cancel_url, time, sitename'), + default_subject = N_('Announce Subscription Request'), + default_body = N_("""\ +You have (or someone impersonating you has) requested to subscribe to +announces from [sitename]. To confirm this request, visit the +following link: + +[confirm_url] + +If you are not the person who made this request, or you wish to cancel +this request, visit the following link: + +[cancel_url] + +If you do nothing, the request will lapse after 3 days (precisely on +[time]). +""")) + + +TextsDirectory.register('aq-announces-subscription', + N_('Text on announces subscription page'), + default = N_('''\ +

          +FIXME +'

          ''')) + +TextsDirectory.register('aq-sms-demo', + N_('Text when subscribing to announces SMS and configured as demo'), + default = N_(''' +

          +Receiving announces by SMS is not possible in this demo +

          ''')) + +TextsDirectory.register('aq-editor-info', N_('Editor Informations')) +TextsDirectory.register('aq-accessibility', N_('Accessibility Statement')) +TextsDirectory.register('aq-contact', N_('Contact Information')) +TextsDirectory.register('aq-help', N_('Help')) +TextsDirectory.register('aq-sso-text', N_('Connecting with Identity Provider'), + default = N_('''

          Connecting with Identity Provider

          +

          You can also use your identity provider to connect. +

          ''')) + +TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg = True) diff --git a/extra/modules/strongbox_ui.ptl b/extra/modules/strongbox_ui.ptl deleted file mode 100644 index f19c87e..0000000 --- a/extra/modules/strongbox_ui.ptl +++ /dev/null @@ -1,261 +0,0 @@ -import time - -from quixote import get_request, get_response, get_session, redirect -from quixote.directory import Directory, AccessControlled - -import wcs -import wcs.admin.root -from wcs.backoffice.menu import * - -from qommon import errors, misc -from qommon.form import * -from qommon.strftime import strftime - -from strongbox import StrongboxType, StrongboxItem - - - -class StrongboxTypeDirectory(Directory): - _q_exports = ['', 'edit', 'delete'] - - def __init__(self, strongboxtype): - self.strongboxtype = strongboxtype - - def _q_index [html] (self): - html_top('strongbox', title = _('Item Type: %s') % self.strongboxtype.label) - '

          %s

          ' % _('Item Type: %s') % self.strongboxtype.label - get_response().filter['sidebar'] = self.get_sidebar() - get_session().display_message() - - if self.strongboxtype.validation_months: - '
          ' - '
            ' - '
          • ' - _('Number of months of validity:') - ' ' - self.strongboxtype.validation_months - '
          • ' - '
          ' - '
          ' - - def get_sidebar [html] (self): - '
            ' - '
          • %s
          • ' % _('Edit') - '
          • %s
          • ' % _('Delete') - '
          ' - - def edit [html] (self): - form = self.form() - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - self.submit(form) - return redirect('..') - - html_top('strongbox', title = _('Edit Item Type: %s') % self.strongboxtype.label) - '

          %s

          ' % _('Edit Item Type: %s') % self.strongboxtype.label - form.render() - - - def form(self): - form = Form(enctype='multipart/form-data') - form.add(StringWidget, 'label', title = _('Label'), required = True, - value = self.strongboxtype.label) - form.add(IntWidget, 'validation_months', title=_('Number of months of validity'), - value=self.strongboxtype.validation_months, - hint=_('Use 0 if there is no expiration')) - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - return form - - def submit(self, form): - for k in ('label', 'validation_months'): - widget = form.get_widget(k) - if widget: - setattr(self.strongboxtype, k, widget.parse()) - self.strongboxtype.store() - - def delete [html] (self): - form = Form(enctype='multipart/form-data') - form.widgets.append(HtmlWidget('

          %s

          ' % _( - 'You are about to irrevocably delete this item type.'))) - form.add_submit('submit', _('Submit')) - form.add_submit('cancel', _('Cancel')) - if form.get_submit() == 'cancel': - return redirect('..') - if not form.is_submitted() or form.has_errors(): - get_response().breadcrumb.append(('delete', _('Delete'))) - html_top('strongbox', title = _('Delete Item Type')) - '

          %s

          ' % _('Deleting Item Type: %s') % self.strongboxtype.label - form.render() - else: - self.strongboxtype.remove_self() - return redirect('..') - - -class StrongboxTypesDirectory(Directory): - _q_exports = ['', 'new'] - - def _q_traverse(self, path): - get_response().breadcrumb.append(('types/', _('Item Types'))) - return Directory._q_traverse(self, path) - - def _q_index [html] (self): - return redirect('..') - - def new [html] (self): - type_ui = StrongboxTypeDirectory(StrongboxType()) - - form = type_ui.form() - if form.get_submit() == 'cancel': - return redirect('.') - - if form.is_submitted() and not form.has_errors(): - type_ui.submit(form) - return redirect('%s/' % type_ui.strongboxtype.id) - - get_response().breadcrumb.append(('new', _('New Item Type'))) - html_top('strongbox', title = _('New Item Type')) - '

          %s

          ' % _('New Item Type') - form.render() - - def _q_lookup(self, component): - try: - strongboxtype = StrongboxType.get(component) - except KeyError: - raise errors.TraversalError() - get_response().breadcrumb.append((str(strongboxtype.id), strongboxtype.label)) - return StrongboxTypeDirectory(strongboxtype) - - -class StrongboxDirectory(AccessControlled, Directory): - _q_exports = ['', 'types', 'add', 'add_to'] - label = N_('Strongbox') - - types = StrongboxTypesDirectory() - - def _q_access(self): - user = get_request().user - if not user: - raise errors.AccessUnauthorizedError() - admin_role = get_cfg('aq-permissions', {}).get('strongbox', None) - if not (user.is_admin or admin_role in (user.roles or [])): - raise errors.AccessForbiddenError( - public_msg = _('You are not allowed to access Strongbox Management'), - location_hint = 'backoffice') - - get_response().breadcrumb.append(('strongbox/', _('Strongbox'))) - - - def _q_index [html] (self): - html_top('strongbox', _('Strongbox')) - - '
            ' - '
          • %s
          • ' % _('New Item Type') - '
          ' - - get_session().display_message() - - '
          ' - '
          ' - '

          %s

          ' % _('Propose a file to a user') - form = Form(enctype='multipart/form-data') - form.add(StringWidget, 'q', title = _('User'), required=True) - form.add_submit('search', _('Search')) - form.render() - if form.is_submitted() and not form.has_errors(): - q = form.get_widget('q').parse() - users = self.search_for_users(q) - if users: - if len(users) == 1: - return redirect('add_to?user_id=%s' % users[0].id) - if len(users) < 50: - _('(first 50 users only)') - '
            ' - for u in users: - '
          • %s
          • ' % (u.id, u.display_name) - '
          ' - else: - _('No user found.') - '
          ' - '
          ' - - '
          ' - '
          ' - types = StrongboxType.select() - '

          %s

          ' % _('Item Types') - if not types: - '

          ' - _('There is no item types defined at the moment.') - '

          ' - - '
            ' - for l in types: - type_id = l.id - '
          • ' % type_id - '%s' % (type_id, l.label) - '
          • ' - '
          ' - '
          ' - '
          ' - - def search_for_users(self, q): - if hasattr(get_publisher().user_class, 'search'): - return get_publisher().user_class.search(q) - if q: - users = [x for x in get_publisher().user_class.select() - if q in (x.name or '') or q in (x.email or '')] - return users - else: - return [] - - def get_form(self): - types = [(x.id, x.label) for x in StrongboxType.select()] - form = Form(action='add', enctype='multipart/form-data') - form.add(StringWidget, 'description', title=_('Description'), size=60) - form.add(FileWidget, 'file', title=_('File'), required=True) - form.add(SingleSelectWidget, 'type_id', title=_('Document Type'), - options = [(None, _('Not specified'))] + types) - form.add(DateWidget, 'date_time', title = _('Document Date')) - form.add_submit('submit', _('Upload')) - return form - - def add(self): - form = self.get_form() - form.add(StringWidget, 'user_id', title=_('User')) - if not form.is_submitted(): - return redirect('.') - - sffile = StrongboxItem() - sffile.user_id = form.get_widget('user_id').parse() - sffile.description = form.get_widget('description').parse() - sffile.proposed_time = time.localtime() - sffile.proposed_id = get_request().user.id - sffile.type_id = form.get_widget('type_id').parse() - v = form.get_widget('date_time').parse() - sffile.set_expiration_time_from_date(v) - sffile.store() - sffile.set_file(form.get_widget('file').parse()) - sffile.store() - return redirect('.') - - def add_to [html] (self): - form = Form(enctype='multipart/form-data', action='add_to') - form.add(StringWidget, 'user_id', title = _('User'), required=True) - try: - user_id = form.get_widget('user_id').parse() - user = get_publisher().user_class.get(user_id) - except: - return redirect('.') - if not user: - return redirect('.') - get_request().form = {} - get_request().environ['REQUEST_METHOD'] = 'GET' - - html_top('strongbox', _('Strongbox')) - '

          %s %s

          ' % (_('Propose a file to:'), user.display_name) - form = self.get_form() - form.add(HiddenWidget, 'user_id', title=_('User'), value=user.id) - form.render() - diff --git a/extra/modules/strongbox_ui.py b/extra/modules/strongbox_ui.py new file mode 100644 index 0000000..6d16705 --- /dev/null +++ b/extra/modules/strongbox_ui.py @@ -0,0 +1,274 @@ +import time + +from quixote import get_request, get_response, get_session, redirect +from quixote.directory import Directory, AccessControlled +from quixote.html import TemplateIO, htmltext + +import wcs +import wcs.admin.root +from wcs.backoffice.menu import * + +from qommon import errors, misc +from qommon.form import * +from qommon.strftime import strftime + +from strongbox import StrongboxType, StrongboxItem + + + +class StrongboxTypeDirectory(Directory): + _q_exports = ['', 'edit', 'delete'] + + def __init__(self, strongboxtype): + self.strongboxtype = strongboxtype + + def _q_index(self): + html_top('strongbox', title = _('Item Type: %s') % self.strongboxtype.label) + r += htmltext('

          %s

          ') % _('Item Type: %s') % self.strongboxtype.label + get_response().filter['sidebar'] = self.get_sidebar() + r += get_session().display_message() + + if self.strongboxtype.validation_months: + r += htmltext('
          ') + r += htmltext('
            ') + r += htmltext('
          • ') + r += _('Number of months of validity:') + r += ' ' + r += self.strongboxtype.validation_months + r += htmltext('
          • ') + r += htmltext('
          ') + r += htmltext('
          ') + + return r.getvalue() + + def get_sidebar(self): + r = TemplateIO(html=True) + r += htmltext('
            ') + r += htmltext('
          • %s
          • ') % _('Edit') + r += htmltext('
          • %s
          • ') % _('Delete') + r += htmltext('
          ') + return r.getvalue() + + def edit(self): + form = self.form() + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + self.submit(form) + return redirect('..') + + html_top('strongbox', title = _('Edit Item Type: %s') % self.strongboxtype.label) + r = TemplateIO(html=True) + r += htmltext('

          %s

          ') % _('Edit Item Type: %s') % self.strongboxtype.label + r += form.render() + return r.getvalue() + + def form(self): + form = Form(enctype='multipart/form-data') + form.add(StringWidget, 'label', title = _('Label'), required = True, + value = self.strongboxtype.label) + form.add(IntWidget, 'validation_months', title=_('Number of months of validity'), + value=self.strongboxtype.validation_months, + hint=_('Use 0 if there is no expiration')) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + return form + + def submit(self, form): + for k in ('label', 'validation_months'): + widget = form.get_widget(k) + if widget: + setattr(self.strongboxtype, k, widget.parse()) + self.strongboxtype.store() + + def delete(self): + form = Form(enctype='multipart/form-data') + form.widgets.append(HtmlWidget('

          %s

          ' % _( + 'You are about to irrevocably delete this item type.'))) + form.add_submit('submit', _('Submit')) + form.add_submit('cancel', _('Cancel')) + if form.get_submit() == 'cancel': + return redirect('..') + if not form.is_submitted() or form.has_errors(): + get_response().breadcrumb.append(('delete', _('Delete'))) + html_top('strongbox', title = _('Delete Item Type')) + r = TemplateIO(html=True) + r += htmltext('

          %s

          ') % _('Deleting Item Type: %s') % self.strongboxtype.label + r += form.render() + return r.getvalue() + else: + self.strongboxtype.remove_self() + return redirect('..') + + +class StrongboxTypesDirectory(Directory): + _q_exports = ['', 'new'] + + def _q_traverse(self, path): + get_response().breadcrumb.append(('types/', _('Item Types'))) + return Directory._q_traverse(self, path) + + def _q_index(self): + return redirect('..') + + def new(self): + type_ui = StrongboxTypeDirectory(StrongboxType()) + + form = type_ui.form() + if form.get_submit() == 'cancel': + return redirect('.') + + if form.is_submitted() and not form.has_errors(): + type_ui.submit(form) + return redirect('%s/' % type_ui.strongboxtype.id) + + get_response().breadcrumb.append(('new', _('New Item Type'))) + html_top('strongbox', title = _('New Item Type')) + r = TemplateIO(html=True) + r += htmltext('

          %s

          ') % _('New Item Type') + r += form.render() + return r.getvalue() + + def _q_lookup(self, component): + try: + strongboxtype = StrongboxType.get(component) + except KeyError: + raise errors.TraversalError() + get_response().breadcrumb.append((str(strongboxtype.id), strongboxtype.label)) + return StrongboxTypeDirectory(strongboxtype) + + +class StrongboxDirectory(AccessControlled, Directory): + _q_exports = ['', 'types', 'add', 'add_to'] + label = N_('Strongbox') + + types = StrongboxTypesDirectory() + + def _q_access(self): + user = get_request().user + if not user: + raise errors.AccessUnauthorizedError() + admin_role = get_cfg('aq-permissions', {}).get('strongbox', None) + if not (user.is_admin or admin_role in (user.roles or [])): + raise errors.AccessForbiddenError( + public_msg = _('You are not allowed to access Strongbox Management'), + location_hint = 'backoffice') + + get_response().breadcrumb.append(('strongbox/', _('Strongbox'))) + + + def _q_index(self): + html_top('strongbox', _('Strongbox')) + r = TemplateIO(html=True) + + r += htmltext('
            ') + r += htmltext('
          • %s
          • ') % _('New Item Type') + r += htmltext('
          ') + + r += get_session().display_message() + + r += htmltext('
          ') + r += htmltext('
          ') + r += htmltext('

          %s

          ') % _('Propose a file to a user') + form = Form(enctype='multipart/form-data') + form.add(StringWidget, 'q', title = _('User'), required=True) + form.add_submit('search', _('Search')) + r += form.render() + if form.is_submitted() and not form.has_errors(): + q = form.get_widget('q').parse() + users = self.search_for_users(q) + if users: + if len(users) == 1: + return redirect('add_to?user_id=%s' % users[0].id) + if len(users) < 50: + r += _('(first 50 users only)') + r += htmltext('
            ') + for u in users: + r += htmltext('
          • %s
          • ') % (u.id, u.display_name) + r += htmltext('
          ') + else: + r += _('No user found.') + r += htmltext('
          ') + r += htmltext('
          ') + + r += htmltext('
          ') + r += htmltext('
          ') + types = StrongboxType.select() + r += htmltext('

          %s

          ') % _('Item Types') + if not types: + r += htmltext('

          ') + r += _('There is no item types defined at the moment.') + r += htmltext('

          ') + + r += htmltext('
            ') + for l in types: + type_id = l.id + r += htmltext('
          • ') % type_id + r += htmltext('%s') % (type_id, l.label) + r += htmltext('
          • ') + r += htmltext('
          ') + r += htmltext('
          ') + r += htmltext('
          ') + return r.getvalue() + + def search_for_users(self, q): + if hasattr(get_publisher().user_class, 'search'): + return get_publisher().user_class.search(q) + if q: + users = [x for x in get_publisher().user_class.select() + if q in (x.name or '') or q in (x.email or '')] + return users + else: + return [] + + def get_form(self): + types = [(x.id, x.label) for x in StrongboxType.select()] + form = Form(action='add', enctype='multipart/form-data') + form.add(StringWidget, 'description', title=_('Description'), size=60) + form.add(FileWidget, 'file', title=_('File'), required=True) + form.add(SingleSelectWidget, 'type_id', title=_('Document Type'), + options = [(None, _('Not specified'))] + types) + form.add(DateWidget, 'date_time', title = _('Document Date')) + form.add_submit('submit', _('Upload')) + return form + + def add(self): + form = self.get_form() + form.add(StringWidget, 'user_id', title=_('User')) + if not form.is_submitted(): + return redirect('.') + + sffile = StrongboxItem() + sffile.user_id = form.get_widget('user_id').parse() + sffile.description = form.get_widget('description').parse() + sffile.proposed_time = time.localtime() + sffile.proposed_id = get_request().user.id + sffile.type_id = form.get_widget('type_id').parse() + v = form.get_widget('date_time').parse() + sffile.set_expiration_time_from_date(v) + sffile.store() + sffile.set_file(form.get_widget('file').parse()) + sffile.store() + return redirect('.') + + def add_to(self): + form = Form(enctype='multipart/form-data', action='add_to') + form.add(StringWidget, 'user_id', title = _('User'), required=True) + try: + user_id = form.get_widget('user_id').parse() + user = get_publisher().user_class.get(user_id) + except: + return redirect('.') + if not user: + return redirect('.') + get_request().form = {} + get_request().environ['REQUEST_METHOD'] = 'GET' + + html_top('strongbox', _('Strongbox')) + r = TemplateIO(html=True) + r += htmltext('

          %s %s

          ') % (_('Propose a file to:'), user.display_name) + form = self.get_form() + form.add(HiddenWidget, 'user_id', title=_('User'), value=user.id) + r += form.render() + return r.getvalue() -- 1.9.2