This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
auquotidien/auquotidien/modules/root.py

1295 lines
48 KiB
Python

from quixote import get_publisher, get_response, get_request, redirect, get_session
from quixote.directory import Directory
from quixote.html import TemplateIO, htmltext
from wcs.qommon import _
from wcs.qommon.misc import get_variadic_url, simplify
import os
import re
import string
import urlparse
try:
import lasso
except ImportError:
pass
import wcs
import wcs.root
from wcs import qommon
from wcs.qommon import get_cfg, get_logger
from wcs.qommon import template
from wcs.qommon import errors
from wcs.qommon.form import *
from wcs.qommon import logger
from wcs.roles import logged_users_role
from wcs.qommon import emails
from wcs.qommon.sms import SMS
from wcs.categories import Category
from wcs.formdef import FormDef
from wcs.data_sources import NamedDataSource
from wcs.qommon.tokens import Token
from wcs.qommon.admin.emails import EmailsDirectory
from wcs.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
from . import admin
import wcs.forms.root
from wcs.workflows import Workflow
from wcs.forms.preview import PreviewDirectory
from .saml2 import Saml2Directory
OldRootDirectory = wcs.root.RootDirectory
import wcs.qommon.ident.password
import wcs.qommon.ident.idp
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 7
Category.get_limit = category_get_limit
Category.TEXT_ATTRIBUTES = ['name', 'url_name', 'description', 'homepage_position']
Category.INT_ATTRIBUTES = ['position', '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() and not x.formdef.is_disabled()]
if draft:
r += htmltext('<h4 id="drafts">%s</h4>') % _('My Current Drafts')
r += htmltext('<ul>')
for f in draft:
if f.formdef.category:
category_url = '%s' % f.formdef.category.url_name
else:
category_url = '.'
r += htmltext('<li><a href="%s%s/%s/%s">%s</a>, %s') % (base_url,
category_url,
f.formdef.url_name, f.id, f.formdef.name,
misc.localstrftime(f.receipt_time))
r += htmltext(' (<a href="%s%s/%s/%s?remove-draft">%s</a>)') % (base_url,
category_url,
f.formdef.url_name, f.id, _('delete'))
r += htmltext('</li>')
r += htmltext('</ul>')
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('<h4>%s</h4>') % _('My forms with status "%s"') % status_name
else:
r += htmltext('<h4>%s</h4>') % _('My forms with an unknown status') % status_name
r += htmltext('<ul>')
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('<li><a href="%s%s/%s/%s/">%s</a>, %s</li>') % (
base_url,
category_url,
f.formdef.url_name, f.id, f.formdef.name,
misc.localstrftime(f.receipt_time))
r += htmltext('</ul>')
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('<h3>%s%s</h3>') % (date_heading, self.announce.title)
r += htmltext('<p>')
r += self.announce.text
r += htmltext('</p>')
r += htmltext('<p>')
r += htmltext('<a href="../">%s</a>') % _('Back')
r += htmltext('</p>')
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('<ul id="announces-links">')
r += htmltext('<li><a href="subscribe">%s</a></li>') % _('Receiving those Announces')
r += htmltext('</ul>')
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('<div class="announce-item">\n')
r += htmltext('<h4>')
if item.publication_time:
r += time.strftime(misc.date_format(), item.publication_time)
r += ' - '
r += item.title
r += htmltext('</h4>\n')
r += htmltext('<p>\n')
r += item.text
r += htmltext('\n</p>\n')
r += htmltext('</div>\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("<p>%s</p>") % _("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('<p>')
r += _('You have been unsubscribed from announces')
r += htmltext('</p>')
r += htmltext('<a href="%s">%s</a>') % (root_url, _('Back Home'))
else:
r += htmltext('<p>')
r += _('Do you want to stop receiving announces by sms ?')
r += htmltext('</p>')
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('<ul id="announce-modes">')
if sub and sub.email:
r += htmltext(' <li>')
r += htmltext('<span id="par_mail">%s</span>') % _('Email (currently subscribed)')
r += htmltext(' <a href="email_unsubscribe" rel="popup">%s</a></li>') % _('Unsubscribe')
else:
r += htmltext(' <li><a href="email" id="par_mail" rel="popup">%s</a></li>') % _('Email')
if sub and sub.sms:
r += htmltext(' <li>')
if sub.enabled_sms:
r += htmltext('<span id="par_sms">%s</span>') % _('SMS %s (currently subscribed)') % sub.sms
else:
r += htmltext('<span id="par_sms">%s</span>') % _('SMS %s (currently not confirmed)') % sub.sms
r += htmltext(' <a href="smsconfirm" rel="popup">%s</a> ') % _('Confirmation')
r += htmltext(' <a href="sms_unsubscribe" rel="popup">%s</a></li>') % _('Unsubscribe')
elif get_cfg('sms', {}).get('mode', 'none') != 'none':
r += htmltext(' <li><a href="sms" id="par_sms">%s</a></li>') % _('SMS')
r += htmltext(' <li><a class="feed-link" href="atom" id="par_rss">%s</a>') % _('Feed')
r += htmltext('</ul>')
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('<p>')
r += _('You are logged in and your email is %s, ok to subscribe ?') % \
get_request().user.email
r += htmltext('</p>')
form.add_submit('submit', _('Subscribe'))
else:
r += htmltext('<p>')
r += _("You are logged in but there is no email address in your profile.")
r += htmltext('</p>')
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('<p>')
r += _('FIXME will only be used for this purpose etc.')
r += htmltext('</p>')
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('<p>')
r += _('You have been subscribed to the announces.')
r += htmltext('</p>')
r += htmltext('<a href="%s">%s</a>') % (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_template_email('announces-subscription-confirmation',
data, sub.email, exclude_current_user = False)
r = TemplateIO(html=True)
root_url = get_publisher().get_root_url()
r += htmltext('<p>')
r += _('You have been sent an email for confirmation')
r += htmltext('</p>')
r += htmltext('<a href="%s">%s</a>') % (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('<h1>%s</h1>') % _('Request Cancelled')
r += htmltext('<p>%s</p>') % _('The request for subscription has been cancelled.')
r += htmltext('<p>')
r += htmltext(_('Continue to <a href="%s">home page</a>') % root_url)
r += htmltext('</p>')
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('<h1>%s</h1>') % _('Subscription Confirmation')
r += htmltext('<p>%s</p>') % _('Your subscription to announces is now effective.')
r += htmltext('<p>')
r += htmltext(_('Continue to <a href="%s">home page</a>') % root_url)
r += htmltext('</p>')
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 'Publik'
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('<p>')
r += _('You have been unsubscribed from announces')
r += htmltext('</p>')
r += htmltext('<a href="%s">%s</a>') % (root_url, _('Back Home'))
else:
r += htmltext('<p>')
r += _('Do you want to stop receiving announces by email?')
r += htmltext('</p>')
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; saml.
if len(ident_methods) == 1:
method = ident_methods[0]
else:
method = 'password'
return wcs.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 get_request().form.get('ReturnUrl'):
get_request().form['next'] = get_request().form.pop('ReturnUrl')
if 'IsPassive' in get_request().form and 'idp' in ident_methods:
# if isPassive is given in query parameters, we restrict ourselves
# to saml login.
ident_methods = ['idp']
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_request().form.get('next')
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 wcs.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 = wcs.qommon.ident.password.MethodDirectory().login_submit(form)
if not form.has_errors():
return tmp
r += htmltext('<div id="login-password">')
r += get_session().display_message()
r += form.render()
base_url = get_publisher().get_root_url()
r += htmltext('<p><a href="%sident/password/forgotten">%s</a></p>') % (
base_url, _('Forgotten password ?'))
r += htmltext('</div>')
# XXX: this part only supports a single IdP
r += htmltext('<div id="login-sso">')
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('</div>')
get_request().environ['REQUEST_METHOD'] = 'GET'
r += htmltext("""<script type="text/javascript">
document.getElementById('login-form')['username'].focus();
</script>""")
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 AlternatePreviewDirectory(PreviewDirectory):
def _q_traverse(self, path):
get_response().filter['bigdiv'] = 'rub_service'
return super(AlternatePreviewDirectory, self)._q_traverse(path)
class AlternateRootDirectory(OldRootDirectory):
_q_exports = ['', 'admin', 'backoffice', 'forms', 'login', 'logout',
'saml', 'register', 'ident', 'afterjobs',
('informations-editeur', 'informations_editeur'),
('announces', 'announces_dir'),
'myspace', 'services', 'agenda', 'categories', 'user',
('tmp-upload', 'tmp_upload'), 'json', '__version__',
'themes', 'pages', 'payment', 'invoices', 'roles',
'api', 'code', 'fargo', 'tryauth', 'auth', 'preview',
('reload-top', 'reload_top'), 'static',
('i18n.js', 'i18n_js'), 'actions',]
announces_dir = AnnouncesDirectory()
register = AlternateRegisterDirectory()
login = AlternateLoginDirectory()
ident = AlternateIdentDirectory()
myspace = MyspaceDirectory()
agenda = AgendaDirectory()
saml = Saml2Directory()
payment = PublicPaymentDirectory()
invoices = InvoicesDirectory()
code = wcs.forms.root.TrackingCodesDirectory()
preview = AlternatePreviewDirectory()
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):
self.feed_substitution_parts()
# set app_label to Publik if none was specified (this is used in
# backoffice header top line)
if not get_publisher().get_site_option('app_label'):
if not get_publisher().site_options.has_section('options'):
get_publisher().site_options.add_section('options')
get_publisher().site_options.set('options', 'app_label', 'Publik')
response = get_response()
if not hasattr(response, 'filter'):
response.filter = {}
response.filter['auquotidien'] = True
if not path or (path[0] not in ('api', 'backoffice') and not get_request().is_json()):
# api & backoffice have no use for a side box
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:
# REQUEST_URI doesn't exist when using internal HTTP server
# (--http)
uri_rest = get_request().get_path()
if get_request().get_query():
uri_rest += '?' + get_request().get_query()
if uri_rest.startswith(base_url):
uri_rest = uri_rest[len(base_url):]
if f.category:
if f.category.url_name == f.url_name:
return FormsRootDirectory(f.category)._q_traverse(path[1:])
return redirect('%s%s/%s' % (base_url, f.category.url_name, uri_rest))
raise e
def _q_lookup(self, component):
# is this a category ?
try:
category = Category.get_by_urlname(component)
except KeyError:
category = None
# is this a formdef ?
try:
formdef = FormDef.get_by_urlname(component)
except KeyError:
if category:
return FormsRootDirectory(category)
else:
# if the form has no category, or the request is a POST, or the
# slug matches both a category and a formdef, directly call
# into FormsRootDirectory.
if formdef.category_id is None or get_request().get_method() == 'POST' or (
formdef and category):
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
if component in ('accessibility', 'contact', 'help'):
if TextsDirectory.get_html_text('aq-' + component):
return getattr(self, component)()
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)
redirect_url = get_cfg('misc', {}).get('homepage-redirect-url')
if redirect_url:
return redirect(misc.get_variadic_url(redirect_url,
get_publisher().substitutions.get_context_variables()))
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('<div id="home-page-intro">')
r += t
r += htmltext('</div>')
r += htmltext('<div id="centre">')
r += self.box_services(position='1st')
r += htmltext('</div>')
r += htmltext('<div id="droite">')
r += self.myspace_snippet()
r += self.box_services(position='2nd')
r += self.consultations()
r += self.announces()
r += htmltext('</div>')
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('<div id="services-2nd">')
else:
r += htmltext('<div id="services">')
if level == 2:
r += htmltext('<h2>%s</h2>') % _('Services')
else:
r += htmltext('<h3>%s</h3>') % _('Services')
if 'auquotidien-welcome-in-services' in get_response().filter.get('keywords', []):
homepage_text = TextsDirectory.get_html_text('aq-home-page')
if homepage_text:
r += htmltext('<div id="home-page-intro">')
r += homepage_text
r += htmltext('</div>')
r += htmltext('<ul>')
r += t
r += htmltext('</ul>')
r += htmltext('</div>')
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 str(x.category_id) == str(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
keywords = {}
for formdef in formdefs:
for keyword in formdef.keywords_list:
keywords[keyword] = True
r += htmltext('<li id="category-%s" data-keywords="%s">') % (
category.url_name, ' '.join(keywords))
r += htmltext('<strong>')
r += htmltext('<a href="%s/">') % category.url_name
r += category.name
r += htmltext('</a></strong>\n')
r += category.get_description_html_text()
r += htmltext('<ul>')
limit = category.get_limit()
for formdef in formdefs[:limit]:
r += htmltext('<li data-keywords="%s">') % ' '.join(formdef.keywords_list)
classes = []
if formdef.is_disabled() and formdef.disabled_redirection:
classes.append('redirection')
r += htmltext('<a class="%s" href="%s/%s/">%s</a>') % (
' '.join(classes), category.url_name, formdef.url_name, formdef.name)
r += htmltext('</li>\n')
if len(formdefs) < limit:
for formdef in formdefs_advertise[:limit-len(formdefs)]:
r += htmltext('<li class="required-authentication">')
r += htmltext('<a href="%s/%s/">%s</a>') % (category.url_name, formdef.url_name, formdef.name)
r += htmltext('<span> (%s)</span>') % _('authentication required')
r += htmltext('</li>\n')
if (len(formdefs)+len(formdefs_advertise)) > limit:
r += htmltext('<li class="all-forms"><a href="%s/" title="%s">%s</a></li>') % (category.url_name,
_('Access to all forms of the "%s" category') % category.name,
_('Access to all forms in this category'))
r += htmltext('</ul>')
r += htmltext('</li>\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: (
str(x.category_id) == str(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('<div id="consultations">')
r += htmltext('<h3>%s</h3>') % _('Consultations')
r += consultations_category.get_description_html_text()
r += htmltext('<ul>')
for formdef in formdefs:
r += htmltext('<li>')
r += htmltext('<a href="%s/%s/">%s</a>') % (consultations_category.url_name,
formdef.url_name, formdef.name)
r += htmltext('</li>')
r += htmltext('</ul>')
r += htmltext('</div>')
return r.getvalue()
def box_side(self, path):
r = TemplateIO(html=True)
root_url = get_publisher().get_root_url()
if (path == [''] and
'include-tracking-code-form' in get_response().filter.get('keywords', []) and
self.has_anonymous_access_codes()):
r += htmltext('<form id="follow-form" action="%scode/load">') % root_url
r += htmltext('<h3>%s</h3>') % _('Tracking code')
r += htmltext('<input size="12" name="code" placeholder="%s"/>') % _('ex: RPQDFVCD')
r += htmltext('<input type="submit" value="%s"/>') % _('Load')
r += htmltext('</form>')
if get_cfg('aq-permissions', {}).get('links'):
r += self.links()
cats = Category.select(order_by = 'name')
cats = [x for x in cats if x.url_name != 'consultations' and x.get_homepage_position() == 'side']
Category.sort_by_position(cats)
if cats:
r += htmltext('<div id="side-services">')
r += htmltext('<h3>%s</h3>') % _('Services')
r += htmltext('<ul>')
for cat in cats:
r += htmltext('<li><a href="%s/">%s</a></li>') % (cat.url_name, cat.name)
r += htmltext('</ul>')
r += htmltext('</div>')
if get_cfg('aq-permissions', {}).get('events') and Event.keys():
# if there are events, add a link to the agenda
tags = get_cfg('misc', {}).get('event_tags')
if not tags:
tags = get_default_event_tags()
r += htmltext('<h3 id="agenda-link"><a href="%sagenda/">%s</a></h3>') % (root_url, _('Agenda'))
if path and path[0] == 'agenda':
r += htmltext('<p class="tags">')
for tag in tags:
r += htmltext('<a href="%sagenda/tag/%s">%s</a> ') % (root_url, tag, tag)
r += htmltext('</p>')
r += self.agenda.display_remote_calendars()
r += htmltext('<p>')
r += htmltext(' <a href="%sagenda/filter">%s</a>') % (root_url, _('Advanced Filter'))
r += htmltext('</p>')
v = r.getvalue()
if v:
r = TemplateIO(html=True)
r += htmltext('<div id="sidebox">')
r += v
r += htmltext('</div>')
return r.getvalue()
return None
def has_anonymous_access_codes(self):
return any((x for x in FormDef.select() if x.enable_tracking_codes))
def links(self):
links = Link.select()
if not links:
return ''
Link.sort_by_position(links)
r = TemplateIO(html=True)
r += htmltext('<div id="links">')
if links[0].url:
# first link has an URL, so it's not a title, so we display a
# generic title
r += htmltext('<h3>%s</h3>') % _('Useful links')
has_ul = False
vars = get_publisher().substitutions.get_context_variables()
for link in links:
if not link.url:
# acting title
if has_ul:
r += htmltext('</ul>')
r += htmltext('<h3>%s</h3>') % link.title
r += htmltext('<ul>')
has_ul = True
else:
if not has_ul:
r += htmltext('<ul>')
has_ul = True
r += htmltext('<li class="link-%s"><a href="%s">%s</a></li>') % (
simplify(link.title), get_variadic_url(link.url, vars), link.title)
if has_ul:
r += htmltext('</ul>')
r += htmltext('</div>')
return r.getvalue()
def announces(self):
announces = Announce.get_published_announces()
if not announces:
return
r = TemplateIO(html=True)
r += htmltext('<div id="announces">')
r += htmltext('<h3>%s</h3>') % _('Announces to citizens')
for item in announces[:3]:
r += htmltext('<div class="announce-item">')
r += htmltext('<h4>')
if item.publication_time:
r += time.strftime(misc.date_format(), item.publication_time)
r += ' - '
r += item.title
r += htmltext('</h4>')
r += htmltext('<p>')
r += item.text
r += htmltext('</p>')
r += htmltext('</div>')
r += htmltext('<ul id="announces-links">')
r += htmltext('<li><a href="announces/subscribe">%s</a></li>') % _('Receiving those Announces')
r += htmltext('<li><a href="announces/">%s</a></li>') % _('Previous Announces')
r += htmltext('</ul>')
r += htmltext('</div>')
return r.getvalue()
def myspace_snippet(self):
r = TemplateIO(html=True)
r += htmltext('<div id="myspace">')
r += htmltext('<h3>%s</h3>') % _('My Space')
r += htmltext('<ul>')
if get_request().user and not get_request().user.anonymous:
r += htmltext(' <li><a href="myspace/" id="member">%s</a></li>') % _('Access to your personal space')
r += htmltext(' <li><a href="logout" id="logout">%s</a></li>') % _('Logout')
else:
r += htmltext(' <li><a href="register/" id="inscr">%s</a></li>') % _('Registration')
r += htmltext(' <li><a href="login/" id="login">%s</a></li>') % _('Login')
r += htmltext('</ul>')
r += htmltext('</div>')
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('<div class="article">')
r += htmltext(TextsDirectory.get_html_text(key))
r += htmltext('</div>')
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
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_('''\
<p>
FIXME
'</p>'''))
TextsDirectory.register('aq-sms-demo',
N_('Text when subscribing to announces SMS and configured as demo'),
default = N_('''
<p>
Receiving announces by SMS is not possible in this demo
</p>'''))
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_('''<h3>Connecting with Identity Provider</h3>
<p>You can also use your identity provider to connect.
</p>'''))
TextsDirectory.register('aq-home-page', N_('Home Page'), wysiwyg = True)