Revision 2bac98e9
Added by Frédéric Péters over 12 years ago
| extra/modules/abelium_domino_ui.ptl | ||
|---|---|---|
|
from quixote import get_publisher, redirect, get_request
|
||
|
from quixote.directory import Directory, AccessControlled
|
||
|
|
||
|
from qommon import get_cfg, get_logger
|
||
|
from qommon.form import Form, StringWidget, CheckboxWidget, SingleSelectWidget
|
||
|
from qommon.admin.menu import html_top
|
||
|
from quixote.html import htmltext
|
||
|
|
||
|
from payments import Regie
|
||
|
|
||
|
|
||
|
# constants
|
||
|
ABELIUM_DOMINO = 'abelium_domino'
|
||
|
ACTIVATED = 'activated'
|
||
|
WSDL_URL = 'wsdl_url'
|
||
|
SERVICE_URL = 'service_url'
|
||
|
DOMAIN = 'domain'
|
||
|
LOGIN = 'login'
|
||
|
PASSWORD = 'password'
|
||
|
INVOICE_REGIE = 'invoice_regie'
|
||
|
|
||
|
try:
|
||
|
import abelium_domino_ws
|
||
|
except ImportError, e:
|
||
|
abelium_domino_ws = None
|
||
|
import_error = e
|
||
|
|
||
|
def get_abelium_cfg(publisher):
|
||
|
if not publisher:
|
||
|
publisher = get_publisher()
|
||
|
return publisher.cfg.get(ABELIUM_DOMINO, {})
|
||
|
|
||
|
def is_activated(publisher=None):
|
||
|
cfg = get_abelium_cfg(publisher)
|
||
|
return cfg.get(ACTIVATED, False) and abelium_domino_ws is not None
|
||
|
|
||
|
def get_client(publisher=None):
|
||
|
publisher = publisher or get_publisher()
|
||
|
|
||
|
cfg = get_abelium_cfg(publisher)
|
||
|
try:
|
||
|
publisher._ws_cache = abelium_domino_ws.DominoWs(
|
||
|
url=cfg.get(WSDL_URL, ''),
|
||
|
domain=cfg.get(DOMAIN,''),
|
||
|
login=cfg.get(LOGIN, ''),
|
||
|
password=cfg.get(PASSWORD, ''),
|
||
|
location=cfg.get(SERVICE_URL),
|
||
|
logger=get_logger())
|
||
|
except IOError:
|
||
|
return None
|
||
|
return publisher._ws_cache
|
||
|
|
||
|
def get_family(user, publisher=None):
|
||
|
family = None
|
||
|
if user is None:
|
||
|
return None
|
||
|
client = get_client(publisher)
|
||
|
if not client:
|
||
|
return None
|
||
|
if hasattr(user, 'abelium_domino_code_famille'):
|
||
|
family = client.get_family_by_code_interne(
|
||
|
user.abelium_domino_code_famille)
|
||
|
if family is None and user.email:
|
||
|
family = client.get_family_by_mail(user.email)
|
||
|
return family
|
||
|
|
||
|
def get_invoice_regie(publisher=None):
|
||
|
cfg = get_abelium_cfg(publisher)
|
||
|
regie_id = cfg.get(INVOICE_REGIE)
|
||
|
if not regie_id:
|
||
|
return None
|
||
|
return Regie.get(regie_id, ignore_errors=True)
|
||
|
|
||
|
class AbeliumDominoDirectory(Directory):
|
||
|
_q_exports = [ '' , 'debug' ]
|
||
|
label = N_('Domino')
|
||
|
|
||
|
def debug [html] (self):
|
||
|
from abelium_domino_vars import SESSION_CACHE
|
||
|
html_top(ABELIUM_DOMINO)
|
||
|
'<form method="post"><button>Lancer le cron</button></form>'
|
||
|
if get_request().get_method() == 'POST':
|
||
|
try:
|
||
|
from abelium_domino_synchro import synchronize_domino
|
||
|
synchronize_domino(get_publisher())
|
||
|
except Exception, e:
|
||
|
htmltext('<pre>%s</pre>') % repr(e)
|
||
|
'<p>code interne: %s</p>' % getattr(get_request().user, str('abelium_domino_code_famille'), None)
|
||
|
'<dl>'
|
||
|
context = get_publisher().substitutions.get_context_variables()
|
||
|
for var in sorted(context.keys()):
|
||
|
value = context[var]
|
||
|
if value:
|
||
|
'<dt>%s</dt>' % var
|
||
|
'<dd>%s</dt>' % value
|
||
|
'</dl>'
|
||
|
delattr(get_request().session, SESSION_CACHE)
|
||
|
|
||
|
def _q_index [html] (self):
|
||
|
publisher = get_publisher()
|
||
|
cfg = get_cfg(ABELIUM_DOMINO, {})
|
||
|
form = self.form(cfg)
|
||
|
|
||
|
title = _('Abelium Domino')
|
||
|
html_top(ABELIUM_DOMINO, title = title)
|
||
|
'<h2>%s</h2>' % title
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
if form.get_widget('cancel').parse():
|
||
|
return redirect('..')
|
||
|
if form.get_widget('submit').parse():
|
||
|
for name in [f[0] for f in self.form_desc] + [INVOICE_REGIE]:
|
||
|
widget = form.get_widget(name)
|
||
|
if widget:
|
||
|
cfg[name] = widget.parse()
|
||
|
publisher.cfg[ABELIUM_DOMINO] = cfg
|
||
|
publisher.write_cfg()
|
||
|
return redirect('.')
|
||
|
|
||
|
if abelium_domino_ws:
|
||
|
form.render()
|
||
|
else:
|
||
|
message = _('The Abelium Domino module is not '
|
||
|
'activated because of this error when '
|
||
|
'loading it: %r') % import_error
|
||
|
'<p class="errornotice">%s</p>' % message
|
||
|
'<dl style="display: none">'
|
||
|
context = get_publisher().substitutions.get_context_variables()
|
||
|
for var in sorted(context.keys()):
|
||
|
value = context[var]
|
||
|
if value:
|
||
|
'<dt>%s</dt>' % var
|
||
|
'<dd>%s</dt>' % value
|
||
|
'</dl>'
|
||
|
|
||
|
form_desc = (
|
||
|
# name, required, title, kind
|
||
|
(ACTIVATED, False, _('Activated'), bool),
|
||
|
(WSDL_URL, True, _('WSDL URL'), str),
|
||
|
(SERVICE_URL, False, _('Service URL'), str),
|
||
|
(DOMAIN, True, _('Domain'), str),
|
||
|
(LOGIN, True, _('Login'), str),
|
||
|
(PASSWORD, True, _('Password'), str),
|
||
|
)
|
||
|
|
||
|
|
||
|
def form(self, initial_value={}):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
kinds = { str: StringWidget, bool: CheckboxWidget }
|
||
|
for name, required, title, kind in self.form_desc:
|
||
|
widget = kinds[kind]
|
||
|
form.add(widget, name, required=required, title=title,
|
||
|
value=initial_value.get(name, ''))
|
||
|
options = [(regie.id, regie.label) \
|
||
|
for regie in Regie.values()]
|
||
|
options.insert(0, (None, _('None')))
|
||
|
form.add(SingleSelectWidget, INVOICE_REGIE,
|
||
|
title=_('Regie which will receive payments'),
|
||
|
value=initial_value.get(INVOICE_REGIE),
|
||
|
options=options)
|
||
|
|
||
|
form.add_submit('submit', _('Submit'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
|
||
|
return form
|
||
| extra/modules/abelium_domino_ui.py | ||
|---|---|---|
|
from quixote import get_publisher, redirect, get_request
|
||
|
from quixote.directory import Directory, AccessControlled
|
||
|
from quixote.html import TemplateIO, htmltext
|
||
|
|
||
|
from qommon import get_cfg, get_logger
|
||
|
from qommon.form import Form, StringWidget, CheckboxWidget, SingleSelectWidget
|
||
|
from qommon.admin.menu import html_top
|
||
|
from quixote.html import htmltext
|
||
|
|
||
|
from payments import Regie
|
||
|
|
||
|
|
||
|
# constants
|
||
|
ABELIUM_DOMINO = 'abelium_domino'
|
||
|
ACTIVATED = 'activated'
|
||
|
WSDL_URL = 'wsdl_url'
|
||
|
SERVICE_URL = 'service_url'
|
||
|
DOMAIN = 'domain'
|
||
|
LOGIN = 'login'
|
||
|
PASSWORD = 'password'
|
||
|
INVOICE_REGIE = 'invoice_regie'
|
||
|
|
||
|
try:
|
||
|
import abelium_domino_ws
|
||
|
except ImportError, e:
|
||
|
abelium_domino_ws = None
|
||
|
import_error = e
|
||
|
|
||
|
def get_abelium_cfg(publisher):
|
||
|
if not publisher:
|
||
|
publisher = get_publisher()
|
||
|
return publisher.cfg.get(ABELIUM_DOMINO, {})
|
||
|
|
||
|
def is_activated(publisher=None):
|
||
|
cfg = get_abelium_cfg(publisher)
|
||
|
return cfg.get(ACTIVATED, False) and abelium_domino_ws is not None
|
||
|
|
||
|
def get_client(publisher=None):
|
||
|
publisher = publisher or get_publisher()
|
||
|
|
||
|
cfg = get_abelium_cfg(publisher)
|
||
|
try:
|
||
|
publisher._ws_cache = abelium_domino_ws.DominoWs(
|
||
|
url=cfg.get(WSDL_URL, ''),
|
||
|
domain=cfg.get(DOMAIN,''),
|
||
|
login=cfg.get(LOGIN, ''),
|
||
|
password=cfg.get(PASSWORD, ''),
|
||
|
location=cfg.get(SERVICE_URL),
|
||
|
logger=get_logger())
|
||
|
except IOError:
|
||
|
return None
|
||
|
return publisher._ws_cache
|
||
|
|
||
|
def get_family(user, publisher=None):
|
||
|
family = None
|
||
|
if user is None:
|
||
|
return None
|
||
|
client = get_client(publisher)
|
||
|
if not client:
|
||
|
return None
|
||
|
if hasattr(user, 'abelium_domino_code_famille'):
|
||
|
family = client.get_family_by_code_interne(
|
||
|
user.abelium_domino_code_famille)
|
||
|
if family is None and user.email:
|
||
|
family = client.get_family_by_mail(user.email)
|
||
|
return family
|
||
|
|
||
|
def get_invoice_regie(publisher=None):
|
||
|
cfg = get_abelium_cfg(publisher)
|
||
|
regie_id = cfg.get(INVOICE_REGIE)
|
||
|
if not regie_id:
|
||
|
return None
|
||
|
return Regie.get(regie_id, ignore_errors=True)
|
||
|
|
||
|
class AbeliumDominoDirectory(Directory):
|
||
|
_q_exports = [ '' , 'debug' ]
|
||
|
label = N_('Domino')
|
||
|
|
||
|
def debug(self):
|
||
|
from abelium_domino_vars import SESSION_CACHE
|
||
|
html_top(ABELIUM_DOMINO)
|
||
|
r = TemplateIO(html=True)
|
||
|
r += htmltext('<form method="post"><button>Lancer le cron</button></form>')
|
||
|
if get_request().get_method() == 'POST':
|
||
|
try:
|
||
|
from abelium_domino_synchro import synchronize_domino
|
||
|
synchronize_domino(get_publisher())
|
||
|
except Exception, e:
|
||
|
r += htmltext('<pre>%s</pre>') % repr(e)
|
||
|
r += htmltext('<p>code interne: %s</p>') % getattr(get_request().user, str('abelium_domino_code_famille'), None)
|
||
|
r += htmltext('<dl>')
|
||
|
context = get_publisher().substitutions.get_context_variables()
|
||
|
for var in sorted(context.keys()):
|
||
|
value = context[var]
|
||
|
if value:
|
||
|
r += htmltext('<dt>%s</dt>') % var
|
||
|
r += htmltext('<dd>%s</dt>') % value
|
||
|
r += htmltext('</dl>')
|
||
|
delattr(get_request().session, SESSION_CACHE)
|
||
|
|
||
|
def _q_index(self):
|
||
|
publisher = get_publisher()
|
||
|
cfg = get_cfg(ABELIUM_DOMINO, {})
|
||
|
form = self.form(cfg)
|
||
|
|
||
|
title = _('Abelium Domino')
|
||
|
html_top(ABELIUM_DOMINO, title = title)
|
||
|
r = TemplateIO(html=True)
|
||
|
r += htmltext('<h2>%s</h2>') % title
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
if form.get_widget('cancel').parse():
|
||
|
return redirect('..')
|
||
|
if form.get_widget('submit').parse():
|
||
|
for name in [f[0] for f in self.form_desc] + [INVOICE_REGIE]:
|
||
|
widget = form.get_widget(name)
|
||
|
if widget:
|
||
|
cfg[name] = widget.parse()
|
||
|
publisher.cfg[ABELIUM_DOMINO] = cfg
|
||
|
publisher.write_cfg()
|
||
|
return redirect('.')
|
||
|
|
||
|
if abelium_domino_ws:
|
||
|
r += form.render()
|
||
|
else:
|
||
|
message = _('The Abelium Domino module is not '
|
||
|
'activated because of this error when '
|
||
|
'loading it: %r') % import_error
|
||
|
r += htmltext('<p class="errornotice">%s</p>') % message
|
||
|
r += htmltext('<dl style="display: none">')
|
||
|
context = get_publisher().substitutions.get_context_variables()
|
||
|
for var in sorted(context.keys()):
|
||
|
value = context[var]
|
||
|
if value:
|
||
|
r += htmltext('<dt>%s</dt>') % var
|
||
|
r += htmltext('<dd>%s</dt>') % value
|
||
|
r += htmltext('</dl>')
|
||
|
return r.getvalue()
|
||
|
|
||
|
form_desc = (
|
||
|
# name, required, title, kind
|
||
|
(ACTIVATED, False, _('Activated'), bool),
|
||
|
(WSDL_URL, True, _('WSDL URL'), str),
|
||
|
(SERVICE_URL, False, _('Service URL'), str),
|
||
|
(DOMAIN, True, _('Domain'), str),
|
||
|
(LOGIN, True, _('Login'), str),
|
||
|
(PASSWORD, True, _('Password'), str),
|
||
|
)
|
||
|
|
||
|
|
||
|
def form(self, initial_value={}):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
kinds = { str: StringWidget, bool: CheckboxWidget }
|
||
|
for name, required, title, kind in self.form_desc:
|
||
|
widget = kinds[kind]
|
||
|
form.add(widget, name, required=required, title=title,
|
||
|
value=initial_value.get(name, ''))
|
||
|
options = [(regie.id, regie.label) \
|
||
|
for regie in Regie.values()]
|
||
|
options.insert(0, (None, _('None')))
|
||
|
form.add(SingleSelectWidget, INVOICE_REGIE,
|
||
|
title=_('Regie which will receive payments'),
|
||
|
value=initial_value.get(INVOICE_REGIE),
|
||
|
options=options)
|
||
|
|
||
|
form.add_submit('submit', _('Submit'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
|
||
|
return form
|
||
| extra/modules/backoffice.ptl | ||
|---|---|---|
|
import os
|
||
|
|
||
|
from quixote import get_publisher, redirect
|
||
|
from quixote.directory import Directory
|
||
|
|
||
|
from qommon.publisher import get_publisher_class
|
||
|
|
||
|
import wcs.backoffice.root
|
||
|
import wcs.root
|
||
|
from wcs.admin.menu import *
|
||
|
from wcs.categories import Category
|
||
|
from wcs.formdef import FormDef
|
||
|
|
||
|
from qommon import get_cfg
|
||
|
from qommon.form import *
|
||
|
|
||
|
class BackofficeRootDirectory(wcs.backoffice.root.RootDirectory):
|
||
|
_q_exports = ['', 'home']
|
||
|
|
||
|
items = [
|
||
|
('home', N_('Pending Forms')),
|
||
|
('/', N_('WCS Form Server'))]
|
||
|
|
||
|
def _q_index [html] (self):
|
||
|
return self.home()
|
||
|
|
||
|
def home [html] (self):
|
||
|
'<p>'
|
||
|
_('Welcome on Au Quotidien back office interface')
|
||
|
'</p>'
|
||
|
wcs.backoffice.root.RootDirectory.forms(self)
|
||
|
from qommon.backoffice.menu import html_top
|
||
|
html_top('home', _('Pending Forms'))
|
||
|
get_response().breadcrumb[-1:] = [('home', _('Pending Forms'))]
|
||
|
|
||
|
def _q_lookup(self, component):
|
||
|
return FormPage(component)
|
||
|
|
||
|
class FormPage(wcs.backoffice.root.FormPage):
|
||
|
def get_fields_from_query(self):
|
||
|
field_ids = [x for x in get_request().form.keys()]
|
||
|
fields = wcs.backoffice.root.FormPage.get_fields_from_query(self)
|
||
|
if not field_ids:
|
||
|
# do not display the user-label field by default, as it will most
|
||
|
# most often be redundant (the formdata itself will have a name
|
||
|
# field)
|
||
|
return [x for x in fields if x.id != 'user-label']
|
||
|
return fields
|
||
|
|
||
|
get_publisher_class().backoffice_directory_class = BackofficeRootDirectory
|
||
|
|
||
| extra/modules/backoffice.py | ||
|---|---|---|
|
import os
|
||
|
|
||
|
from quixote import get_publisher, redirect
|
||
|
from quixote.directory import Directory
|
||
|
from quixote.html import TemplateIO, htmltext
|
||
|
|
||
|
from qommon.publisher import get_publisher_class
|
||
|
|
||
|
import wcs.backoffice.root
|
||
|
import wcs.root
|
||
|
from wcs.admin.menu import *
|
||
|
from wcs.categories import Category
|
||
|
from wcs.formdef import FormDef
|
||
|
|
||
|
from qommon import get_cfg
|
||
|
from qommon.form import *
|
||
|
|
||
|
class BackofficeRootDirectory(wcs.backoffice.root.RootDirectory):
|
||
|
_q_exports = ['', 'home']
|
||
|
|
||
|
items = [
|
||
|
('home', N_('Pending Forms')),
|
||
|
('/', N_('WCS Form Server'))]
|
||
|
|
||
|
def _q_index(self):
|
||
|
return self.home()
|
||
|
|
||
|
def home(self):
|
||
|
r = TemplateIO(html=True)
|
||
|
r += htmltext('<p>')
|
||
|
r += _('Welcome on Au Quotidien back office interface')
|
||
|
r += htmltext('</p>')
|
||
|
r += wcs.backoffice.root.RootDirectory.forms(self)
|
||
|
from qommon.backoffice.menu import html_top
|
||
|
html_top('home', _('Pending Forms'))
|
||
|
get_response().breadcrumb[-1:] = [('home', _('Pending Forms'))]
|
||
|
return r.getvalue()
|
||
|
|
||
|
def _q_lookup(self, component):
|
||
|
return FormPage(component)
|
||
|
|
||
|
class FormPage(wcs.backoffice.root.FormPage):
|
||
|
def get_fields_from_query(self):
|
||
|
field_ids = [x for x in get_request().form.keys()]
|
||
|
fields = wcs.backoffice.root.FormPage.get_fields_from_query(self)
|
||
|
if not field_ids:
|
||
|
# do not display the user-label field by default, as it will most
|
||
|
# most often be redundant (the formdata itself will have a name
|
||
|
# field)
|
||
|
return [x for x in fields if x.id != 'user-label']
|
||
|
return fields
|
||
|
|
||
|
get_publisher_class().backoffice_directory_class = BackofficeRootDirectory
|
||
|
|
||
| extra/modules/categories_admin.ptl | ||
|---|---|---|
|
# w.c.s. - web application for online forms
|
||
|
# Copyright (C) 2005-2010 Entr'ouvert
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License as published by
|
||
|
# the Free Software Foundation; either version 2 of the License, or
|
||
|
# (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
from quixote import redirect
|
||
|
from quixote.directory import Directory
|
||
|
|
||
|
from qommon import misc
|
||
|
from wcs.categories import Category
|
||
|
from qommon.form import *
|
||
|
from qommon.admin.menu import html_top, command_icon, error_page
|
||
|
import wcs.admin.categories
|
||
|
|
||
|
class CategoryUI:
|
||
|
def __init__(self, category):
|
||
|
self.category = category
|
||
|
if self.category is None:
|
||
|
self.category = Category()
|
||
|
|
||
|
def get_form(self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
form.add(StringWidget, 'name', title = _('Category Name'), required = True, size=30,
|
||
|
value = self.category.name)
|
||
|
form.add(TextWidget, 'description', title = _('Description'), cols = 80, rows = 10,
|
||
|
value = self.category.description)
|
||
|
form.add(SingleSelectWidget, 'homepage_position',
|
||
|
title=_('Position on homepage'),
|
||
|
value=self.category.get_homepage_position(),
|
||
|
options = [('1st', _('First Column')),
|
||
|
('2nd', _('Second Column')),
|
||
|
('side', _('Sidebar')),
|
||
|
('none', _('None'))])
|
||
|
form.add(IntWidget, 'limit',
|
||
|
title=_('Limit number of items displayed on homepage'),
|
||
|
value=self.category.get_limit())
|
||
|
|
||
|
form.add_submit('submit', _('Submit'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
return form
|
||
|
|
||
|
def submit_form(self, form):
|
||
|
if self.category.id:
|
||
|
self.category.name = form.get_widget('name').parse()
|
||
|
category = self.category
|
||
|
else:
|
||
|
category = Category(name = form.get_widget('name').parse())
|
||
|
|
||
|
name = form.get_widget('name').parse()
|
||
|
category_names = [x.name for x in Category.select() if x.id != category.id]
|
||
|
if name in category_names:
|
||
|
form.get_widget('name').set_error(_('This name is already used'))
|
||
|
raise ValueError()
|
||
|
|
||
|
category.description = form.get_widget('description').parse()
|
||
|
category.homepage_position = form.get_widget('homepage_position').parse()
|
||
|
category.limit = form.get_widget('limit').parse()
|
||
|
|
||
|
category.store()
|
||
|
|
||
|
|
||
|
|
||
|
class CategoryPage(wcs.admin.categories.CategoryPage):
|
||
|
def __init__(self, component):
|
||
|
self.category = Category.get(component)
|
||
|
self.category_ui = CategoryUI(self.category)
|
||
|
get_response().breadcrumb.append((component + '/', self.category.name))
|
||
|
|
||
|
|
||
|
class CategoriesDirectory(wcs.admin.categories.CategoriesDirectory):
|
||
|
label = N_('Categories')
|
||
|
|
||
|
def new [html] (self):
|
||
|
get_response().breadcrumb.append( ('categories/', _('Categories')) )
|
||
|
get_response().breadcrumb.append( ('new', _('New')) )
|
||
|
category_ui = CategoryUI(None)
|
||
|
form = category_ui.get_form()
|
||
|
if form.get_widget('cancel').parse():
|
||
|
return redirect('.')
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
try:
|
||
|
category_ui.submit_form(form)
|
||
|
except ValueError:
|
||
|
pass
|
||
|
else:
|
||
|
return redirect('.')
|
||
|
|
||
|
html_top('categories', title = _('New Category'))
|
||
|
'<h2>%s</h2>' % _('New Category')
|
||
|
form.render()
|
||
|
|
||
|
def _q_lookup(self, component):
|
||
|
get_response().breadcrumb.append( ('categories/', _('Categories')) )
|
||
|
return CategoryPage(component)
|
||
|
|
||
|
from admin import AdminRootDirectory
|
||
|
AdminRootDirectory.register_page('categories', CategoriesDirectory())
|
||
| extra/modules/categories_admin.py | ||
|---|---|---|
|
# w.c.s. - web application for online forms
|
||
|
# Copyright (C) 2005-2010 Entr'ouvert
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License as published by
|
||
|
# the Free Software Foundation; either version 2 of the License, or
|
||
|
# (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
from quixote import redirect
|
||
|
from quixote.directory import Directory
|
||
|
from quixote.html import TemplateIO, htmltext
|
||
|
|
||
|
from qommon import misc
|
||
|
from wcs.categories import Category
|
||
|
from qommon.form import *
|
||
|
from qommon.admin.menu import html_top, command_icon, error_page
|
||
|
import wcs.admin.categories
|
||
|
|
||
|
class CategoryUI:
|
||
|
def __init__(self, category):
|
||
|
self.category = category
|
||
|
if self.category is None:
|
||
|
self.category = Category()
|
||
|
|
||
|
def get_form(self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
form.add(StringWidget, 'name', title = _('Category Name'), required = True, size=30,
|
||
|
value = self.category.name)
|
||
|
form.add(TextWidget, 'description', title = _('Description'), cols = 80, rows = 10,
|
||
|
value = self.category.description)
|
||
|
form.add(SingleSelectWidget, 'homepage_position',
|
||
|
title=_('Position on homepage'),
|
||
|
value=self.category.get_homepage_position(),
|
||
|
options = [('1st', _('First Column')),
|
||
|
('2nd', _('Second Column')),
|
||
|
('side', _('Sidebar')),
|
||
|
('none', _('None'))])
|
||
|
form.add(IntWidget, 'limit',
|
||
|
title=_('Limit number of items displayed on homepage'),
|
||
|
value=self.category.get_limit())
|
||
|
|
||
|
form.add_submit('submit', _('Submit'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
return form
|
||
|
|
||
|
def submit_form(self, form):
|
||
|
if self.category.id:
|
||
|
self.category.name = form.get_widget('name').parse()
|
||
|
category = self.category
|
||
|
else:
|
||
|
category = Category(name = form.get_widget('name').parse())
|
||
|
|
||
|
name = form.get_widget('name').parse()
|
||
|
category_names = [x.name for x in Category.select() if x.id != category.id]
|
||
|
if name in category_names:
|
||
|
form.get_widget('name').set_error(_('This name is already used'))
|
||
|
raise ValueError()
|
||
|
|
||
|
category.description = form.get_widget('description').parse()
|
||
|
category.homepage_position = form.get_widget('homepage_position').parse()
|
||
|
category.limit = form.get_widget('limit').parse()
|
||
|
|
||
|
category.store()
|
||
|
|
||
|
|
||
|
|
||
|
class CategoryPage(wcs.admin.categories.CategoryPage):
|
||
|
def __init__(self, component):
|
||
|
self.category = Category.get(component)
|
||
|
self.category_ui = CategoryUI(self.category)
|
||
|
get_response().breadcrumb.append((component + '/', self.category.name))
|
||
|
|
||
|
|
||
|
class CategoriesDirectory(wcs.admin.categories.CategoriesDirectory):
|
||
|
label = N_('Categories')
|
||
|
|
||
|
def new(self):
|
||
|
get_response().breadcrumb.append( ('categories/', _('Categories')) )
|
||
|
get_response().breadcrumb.append( ('new', _('New')) )
|
||
|
category_ui = CategoryUI(None)
|
||
|
form = category_ui.get_form()
|
||
|
if form.get_widget('cancel').parse():
|
||
|
return redirect('.')
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
try:
|
||
|
category_ui.submit_form(form)
|
||
|
except ValueError:
|
||
|
pass
|
||
|
else:
|
||
|
return redirect('.')
|
||
|
|
||
|
html_top('categories', title = _('New Category'))
|
||
|
r = TemplateIO(html=True)
|
||
|
r += htmltext('<h2>%s</h2>') % _('New Category')
|
||
|
r += form.render()
|
||
|
return r.getvalue()
|
||
|
|
||
|
def _q_lookup(self, component):
|
||
|
get_response().breadcrumb.append( ('categories/', _('Categories')) )
|
||
|
return CategoryPage(component)
|
||
|
|
||
|
from admin import AdminRootDirectory
|
||
|
AdminRootDirectory.register_page('categories', CategoriesDirectory())
|
||
| extra/modules/ezldap_ui.ptl | ||
|---|---|---|
|
'''
|
||
|
Module to provide the kind of interaction needed for a eZ publish website
|
||
|
acting as reverse proxy and passing in an HTTP header a pointer to an LDAP
|
||
|
entry.
|
||
|
'''
|
||
|
|
||
|
try:
|
||
|
import ldap
|
||
|
import ldap.modlist
|
||
|
except ImportError:
|
||
|
ldap = None
|
||
|
|
||
|
import base64
|
||
|
import datetime
|
||
|
import random
|
||
|
import string
|
||
|
import urllib
|
||
|
from urlparse import urlparse
|
||
|
try:
|
||
|
from hashlib import sha1 as sha
|
||
|
except ImportError:
|
||
|
from sha import sha
|
||
|
|
||
|
from quixote import get_publisher, get_request, redirect, get_session, get_session_manager
|
||
|
from quixote.directory import Directory
|
||
|
|
||
|
from qommon import get_cfg, template, emails
|
||
|
from qommon import misc
|
||
|
from qommon.admin.emails import EmailsDirectory
|
||
|
from qommon.admin.texts import TextsDirectory
|
||
|
from qommon.form import *
|
||
|
from qommon.ident.password import check_password, make_password
|
||
|
from qommon import errors
|
||
|
|
||
|
from wcs.formdef import FormDef
|
||
|
from wcs.users import User
|
||
|
|
||
|
from myspace import MyspaceDirectory
|
||
|
from payments import is_payment_supported
|
||
|
|
||
|
import ezldap
|
||
|
|
||
|
def make_token():
|
||
|
'''Create a token, checking it does not already exist in the ldap base.'''
|
||
|
r = random.SystemRandom()
|
||
|
ldap_basedn = get_cfg('misc', {}).get('aq-ezldap-basedn')
|
||
|
ldap_conn = ezldap.get_ldap_conn()
|
||
|
while True:
|
||
|
token = ''.join([r.choice(string.letters + string.digits) for x in range(16)])
|
||
|
ldap_result_id = ldap_conn.search(ldap_basedn, ldap.SCOPE_SUBTREE,
|
||
|
filterstr='token=%s' % token)
|
||
|
result_type, result_data = ldap_conn.result(ldap_result_id, all=0)
|
||
|
if result_data == []:
|
||
|
break
|
||
|
return token
|
||
|
|
||
|
def add_token(token, dn, action, exp_days=3):
|
||
|
''' Add a token in the LDAP '''
|
||
|
expdate = datetime.datetime.utcnow() + datetime.timedelta(exp_days)
|
||
|
ldap_conn = ezldap.get_ldap_conn()
|
||
|
# add token into the LDAP
|
||
|
mod_list = [(ldap.MOD_REPLACE, 'token', token),
|
||
|
(ldap.MOD_REPLACE, 'tokenAction', action),
|
||
|
(ldap.MOD_REPLACE, 'tokenExpiration', expdate.strftime('%Y%m%d%H%M%SZ'))]
|
||
|
ldap_conn.modify_s(dn, mod_list)
|
||
|
return token
|
||
|
|
||
|
def email_to_admins(dn, template):
|
||
|
admins = [x for x in get_publisher().user_class.select() if x.is_admin]
|
||
|
if not admins:
|
||
|
return
|
||
|
admin_emails = [x.email for x in admins if x.email]
|
||
|
|
||
|
user = ezldap.EzLdapUser(dn)
|
||
|
data = {
|
||
|
'hostname': get_request().get_server(),
|
||
|
'username': user.email,
|
||
|
'email_as_username': 'True',
|
||
|
'name': user.display_name,
|
||
|
'email': user.email,
|
||
|
}
|
||
|
get_publisher().substitutions.feed(user)
|
||
|
|
||
|
emails.custom_ezt_email(template, data,
|
||
|
admin_emails, fire_and_forget = True)
|
||
|
|
||
|
|
||
|
class EzMyspaceDirectory(MyspaceDirectory):
|
||
|
|
||
|
def __init__(self):
|
||
|
self._q_exports.extend(['change_email'])
|
||
|
|
||
|
def _index_buttons [html] (self, form_data):
|
||
|
super(EzMyspaceDirectory, self)._index_buttons(form_data)
|
||
|
'<p class="command"><a href="change_email">%s</a></p>' % _('Change my email')
|
||
|
'<br />'
|
||
|
'<br />'
|
||
|
'<p>'
|
||
|
_('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.')
|
||
|
' <strong><a href="remove">%s</a></strong>.' % _('Delete My Account')
|
||
|
'</p>'
|
||
|
|
||
|
def _my_profile [html] (self, user_formdef, user):
|
||
|
'<h3 id="my-profile">%s</h3>' % _('My Profile')
|
||
|
|
||
|
TextsDirectory.get_html_text('top-of-profile')
|
||
|
|
||
|
if user.form_data:
|
||
|
get_publisher().substitutions.feed(get_request().user)
|
||
|
data = get_publisher().substitutions.get_context_variables()
|
||
|
TextsDirectory.get_html_text('aq-profile-presentation', data)
|
||
|
else:
|
||
|
'<p>%s</p>' % _('Empty profile')
|
||
|
|
||
|
def submit_password(self, new_password):
|
||
|
userPassword = ['{sha}%s' % base64.encodestring(sha(new_password).digest()).strip()]
|
||
|
mod_list = [(ldap.MOD_REPLACE, 'userPassword', userPassword)]
|
||
|
ezldap.get_ldap_conn().modify_s(get_session().user, mod_list)
|
||
|
|
||
|
def change_email [html] (self):
|
||
|
form = Form(enctype = 'multipart/form-data')
|
||
|
form.add(ezldap.EzEmailWidget, 'new_email', title = _('New email'),
|
||
|
required=True, size=30)
|
||
|
form.add_submit('submit', _('Submit Request'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
if form.get_submit() == 'cancel':
|
||
|
return redirect('.')
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
new_email = form.get_widget('new_email').parse()
|
||
|
self.change_email_submit(new_email)
|
||
|
template.html_top(_('Change email'))
|
||
|
TextsDirectory.get_html_text('aq-change-email-token-sent') % { 'new_email': new_email }
|
||
|
else:
|
||
|
template.html_top(_('Change email'))
|
||
|
TextsDirectory.get_html_text('aq-change-email')
|
||
|
form.render()
|
||
|
|
||
|
def change_email_submit(self, new_email):
|
||
|
data = {}
|
||
|
|
||
|
token = make_token()
|
||
|
req = get_request()
|
||
|
base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
|
||
|
urllib.quote(req.environ.get('SCRIPT_NAME')))
|
||
|
data['change_url'] = base_url + '/token/?token=%s' % token
|
||
|
data['cancel_url'] = base_url + '/token/cancel?token=%s' % token
|
||
|
data['new_email'] = new_email
|
||
|
data['current_email'] = req.user.email
|
||
|
|
||
|
# add the token in the LDAP
|
||
|
add_token(token, get_session().user, "change_email;%s" % new_email)
|
||
|
# send mail
|
||
|
emails.custom_ezt_email('aq-change-email-request', data,
|
||
|
data['new_email'], fire_and_forget = True)
|
||
|
|
||
|
def remove [html] (self):
|
||
|
user = get_request().user
|
||
|
if not user or user.anonymous:
|
||
|
raise errors.AccessUnauthorizedError()
|
||
|
form = Form(enctype = 'multipart/form-data')
|
||
|
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():
|
||
|
self.remove_email_submit()
|
||
|
template.html_top(_('Removing Account'))
|
||
|
TextsDirectory.get_html_text('aq-remove-token-sent') % { 'email': user.email }
|
||
|
else:
|
||
|
template.html_top(_('Removing Account'))
|
||
|
TextsDirectory.get_html_text('aq-remove-account')
|
||
|
form.render()
|
||
|
|
||
|
def remove_email_submit(self):
|
||
|
data = {}
|
||
|
token = make_token()
|
||
|
req = get_request()
|
||
|
base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
|
||
|
urllib.quote(req.environ.get('SCRIPT_NAME')))
|
||
|
data['remove_url'] = base_url + '/token/?token=%s' % token
|
||
|
data['cancel_url'] = base_url + '/token/cancel?token=%s' % token
|
||
|
data['email'] = req.user.email
|
||
|
# add the token in the LDAP
|
||
|
add_token(token, get_session().user, "remove")
|
||
|
# send mail
|
||
|
emails.custom_ezt_email('aq-remove-request', data,
|
||
|
data['email'], fire_and_forget = True)
|
||
|
|
||
|
class EzTokenDirectory(Directory):
|
||
|
_q_exports = ['', 'cancel']
|
||
|
|
||
|
def _q_index (self):
|
||
|
'''token processing'''
|
||
|
token = get_request().form.get('token')
|
||
|
if token:
|
||
|
result_data = self._get_ldap_entry_token(token)
|
||
|
if result_data:
|
||
|
return self._actions(result_data[0][1]['tokenAction'][0], result_data)
|
||
|
template.html_top(_('Error'))
|
||
|
return _('The token you submitted does not exist, has expired, or has been cancelled.')
|
||
|
|
||
|
def _get_ldap_entry_token(self, token):
|
||
|
''' return a ldap result for the given token '''
|
||
|
ldap_basedn = get_cfg('misc', {}).get('aq-ezldap-basedn')
|
||
|
ldap_conn = ezldap.get_ldap_conn()
|
||
|
ldap_result_id = ldap_conn.search(ldap_basedn, ldap.SCOPE_SUBTREE,
|
||
|
filterstr='token=%s' % token)
|
||
|
result_type, result_data = ldap_conn.result(ldap_result_id, all=0)
|
||
|
if result_type == ldap.RES_SEARCH_ENTRY:
|
||
|
return result_data
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
def _actions(self, token_action, result_data):
|
||
|
dn = result_data[0][0]
|
||
|
action = token_action.split(";")[0]
|
||
|
if action == 'create':
|
||
|
mod_list = [(ldap.MOD_REPLACE, 'actif', 'TRUE'),
|
||
|
(ldap.MOD_DELETE, 'token', None),
|
||
|
(ldap.MOD_DELETE, 'tokenAction', None),
|
||
|
(ldap.MOD_DELETE, 'tokenExpiration', None)]
|
||
|
ezldap.get_ldap_conn().modify_s(dn, mod_list)
|
||
|
email_to_admins(dn, 'new-registration-admin-notification')
|
||
|
template.html_top(_('Welcome'))
|
||
|
return TextsDirectory.get_html_text('account-created')
|
||
|
elif action == 'change_email':
|
||
|
new_email = token_action.split(";")[1]
|
||
|
mod_list = [(ldap.MOD_REPLACE, ezldap.LDAP_EMAIL, str(new_email)),
|
||
|
(ldap.MOD_REPLACE, 'actif', 'TRUE'),
|
||
|
(ldap.MOD_DELETE, 'token', None),
|
||
|
(ldap.MOD_DELETE, 'tokenAction', None),
|
||
|
(ldap.MOD_DELETE, 'tokenExpiration', None)]
|
||
|
ezldap.get_ldap_conn().modify_s(dn, mod_list)
|
||
|
template.html_top(_('Email changed'))
|
||
|
return TextsDirectory.get_html_text('aq-new-email-confirmed') % { 'new_email': new_email }
|
||
|
elif action == 'change_password':
|
||
|
email = result_data[0][1][ezldap.LDAP_EMAIL][0]
|
||
|
new_password = make_password()
|
||
|
userPassword = ['{sha}%s' % base64.encodestring(sha(new_password).digest()).strip()]
|
||
|
mod_list = [(ldap.MOD_REPLACE, 'userPassword', userPassword),
|
||
|
(ldap.MOD_REPLACE, 'actif', 'TRUE'),
|
||
|
(ldap.MOD_DELETE, 'token', None),
|
||
|
(ldap.MOD_DELETE, 'tokenAction', None),
|
||
|
(ldap.MOD_DELETE, 'tokenExpiration', None)]
|
||
|
ezldap.get_ldap_conn().modify_s(dn, mod_list)
|
||
|
data = {
|
||
|
'username': email,
|
||
|
'password': new_password,
|
||
|
'hostname': get_request().get_server(),
|
||
|
}
|
||
|
emails.custom_ezt_email('new-generated-password', data, email,
|
||
|
exclude_current_user = False)
|
||
|
template.html_top(_('New password sent by email'))
|
||
|
return TextsDirectory.get_html_text('new-password-sent-by-email')
|
||
|
elif action == 'remove':
|
||
|
template.html_top(_('Removing Account'))
|
||
|
# email (just before remove)
|
||
|
email_to_admins(dn, 'notification-of-removed-account')
|
||
|
# delete all related forms
|
||
|
formdefs = FormDef.select()
|
||
|
user_forms = []
|
||
|
for formdef in formdefs:
|
||
|
user_forms.extend(formdef.data_class().get_with_indexed_value('user_id', dn))
|
||
|
for formdata in user_forms:
|
||
|
formdata.remove_self()
|
||
|
# delete the user in ldap
|
||
|
ezldap.get_ldap_conn().delete_s(dn)
|
||
|
# delete session and redirect to login page
|
||
|
get_session_manager().expire_session()
|
||
|
root_url = get_publisher().get_root_url()
|
||
|
return redirect('%slogin' % root_url)
|
||
|
|
||
|
def cancel(self):
|
||
|
token = get_request().form.get('token')
|
||
|
if token:
|
||
|
result_data = self._get_ldap_entry_token(token)
|
||
|
if result_data:
|
||
|
dn = result_data[0][0]
|
||
|
mod_list = [(ldap.MOD_DELETE, 'token', None),
|
||
|
(ldap.MOD_DELETE, 'tokenAction', None),
|
||
|
(ldap.MOD_DELETE, 'tokenExpiration', None)]
|
||
|
ezldap.get_ldap_conn().modify_s(dn, mod_list)
|
||
|
template.html_top(_('Request cancelled'))
|
||
|
return _('Your request has been cancelled.')
|
||
|
|
||
|
template.html_top(_('Error'))
|
||
|
return _('The token you submitted does not exist or has expired')
|
||
|
|
||
|
|
||
|
class EzRegisterDirectory(Directory):
|
||
|
_q_exports = ['', 'passwordreset']
|
||
|
|
||
|
def _q_index(self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
|
||
|
template.html_top(_('Registration'))
|
||
|
head = TextsDirectory.get_html_text('new-account')
|
||
|
form.widgets.append(HtmlWidget(head))
|
||
|
|
||
|
# build password hint : "at least 4 cars, at most 12"
|
||
|
passwords_cfg = get_cfg('passwords', {})
|
||
|
min_len = passwords_cfg.get('min_length', 0)
|
||
|
max_len = passwords_cfg.get('max_length', 0)
|
||
|
if min_len and max_len:
|
||
|
password_hint = _('At least %(min)d characters, at most %(max)d') % \
|
||
|
{'min': min_len, 'max': max_len }
|
||
|
elif min_len:
|
||
|
password_hint = _('At least %d characters') % min_len
|
||
|
elif max_len:
|
||
|
password_hint = _('At most %d characters') % max_len
|
||
|
else:
|
||
|
password_hint = ''
|
||
|
|
||
|
# get required attributes from User configuration
|
||
|
for f in User.get_formdef().fields:
|
||
|
if f.required:
|
||
|
kwargs = {'required': True, 'value': form.get(f.varname)}
|
||
|
for k in f.extra_attributes:
|
||
|
if hasattr(f, k):
|
||
|
kwargs[k] = getattr(f, k)
|
||
|
f.perform_more_widget_changes(form, kwargs)
|
||
|
form.add(f.widget_class, f.varname, title=f.label, hint=f.hint, **kwargs)
|
||
|
# static required fields
|
||
|
form.add(ezldap.EzEmailWidget, ezldap.LDAP_EMAIL, title=_('Email'),
|
||
|
value=form.get(ezldap.LDAP_EMAIL), required=True, size=30,
|
||
|
hint=_('Remember: this will be your username'))
|
||
|
form.add(PasswordWidget, '__password', title=_('New Password'),
|
||
|
value=form.get('__password'), required=True, size=15,
|
||
|
hint=password_hint)
|
||
|
form.add(PasswordWidget, '__password2', title=_('New Password (confirm)'),
|
||
|
value=form.get('__password2'), required=True, size=15)
|
||
|
form.widgets.append(CaptchaWidget('__captcha'))
|
||
|
form.add_submit('submit', _('Register'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
|
||
|
if form.get_submit() == 'cancel':
|
||
|
return redirect('..')
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
check_password(form, '__password')
|
||
|
password = form.get_widget('__password').parse()
|
||
|
password2 = form.get_widget('__password2').parse()
|
||
|
if password != password2:
|
||
|
form.set_error('__password2', _('Passwords do not match'))
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
self.register_submit(form)
|
||
|
template.html_top(_('Welcome'))
|
||
|
return TextsDirectory.get_html_text('email-sent-confirm-creation')
|
||
|
|
||
|
return form.render()
|
||
|
|
||
|
def register_submit(self, form):
|
||
|
data = {}
|
||
|
exp_days = 3
|
||
|
|
||
|
for widget in form.widgets:
|
||
|
value = widget.parse()
|
||
|
if value:
|
||
|
if widget.name == '__password':
|
||
|
password = value
|
||
|
data['userPassword'] = ['{sha}%s' % \
|
||
|
base64.encodestring(sha(password).digest()).strip()]
|
||
|
elif not widget.name.startswith('__'):
|
||
|
if widget.name.startswith('date'):
|
||
|
date = datetime.datetime.strptime(value, misc.date_format())
|
||
|
data[widget.name] = date.strftime('%Y%m%d%H%M%SZ')
|
||
|
else:
|
||
|
data[widget.name] = [value]
|
||
|
|
||
|
misc_cfg = get_cfg('misc', {})
|
||
|
expdate = datetime.datetime.utcnow() + datetime.timedelta(exp_days)
|
||
|
data['actif'] = 'FALSE'
|
||
|
data['token'] = make_token()
|
||
|
data['tokenAction'] = 'create'
|
||
|
data['tokenExpiration'] = expdate.strftime('%Y%m%d%H%M%SZ')
|
||
|
req = get_request()
|
||
|
base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
|
||
|
urllib.quote(req.environ.get('SCRIPT_NAME')))
|
||
|
token_url = base_url + '/token/?token=%s' % data['token']
|
||
|
|
||
|
data['objectClass'] = 'vincennesCitoyen'
|
||
|
mod_list = ldap.modlist.addModlist(data)
|
||
|
ldap_dntemplate = misc_cfg.get('aq-ezldap-dntemplate')
|
||
|
while True:
|
||
|
dn = ldap_dntemplate % random.randint(100000, 5000000)
|
||
|
try:
|
||
|
ezldap.get_ldap_conn().add_s(dn, mod_list)
|
||
|
except ldap.ALREADY_EXISTS:
|
||
|
continue
|
||
|
break
|
||
|
|
||
|
data = {
|
||
|
'email': data[ezldap.LDAP_EMAIL][0],
|
||
|
'username': data[ezldap.LDAP_EMAIL][0],
|
||
|
'password': password,
|
||
|
'token_url': token_url,
|
||
|
'website': base_url,
|
||
|
'admin_email': '',
|
||
|
}
|
||
|
emails.custom_ezt_email('password-subscription-notification', data,
|
||
|
data.get('email'), fire_and_forget = True)
|
||
|
|
||
|
|
||
|
def passwordreset [html] (self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
form.add(EmailWidget, 'email', title=_('Email'), required=True, size=30)
|
||
|
form.add_submit('change', _('Submit Request'))
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
email = form.get_widget('email').parse()
|
||
|
dn = self.get_dn_by_email(email)
|
||
|
if dn:
|
||
|
self.passwordreset_submit(email, dn)
|
||
|
template.html_top(_('Forgotten Password'))
|
||
|
TextsDirectory.get_html_text(str('password-forgotten-token-sent'))
|
||
|
else:
|
||
|
form.set_error('email', _('There is no user with that email.'))
|
||
|
template.html_top(_('Forgotten Password'))
|
||
|
TextsDirectory.get_html_text(str('password-forgotten-enter-username'))
|
||
|
form.render()
|
||
|
else:
|
||
|
template.html_top(_('Forgotten Password'))
|
||
|
TextsDirectory.get_html_text(str('password-forgotten-enter-username'))
|
||
|
form.render()
|
||
|
|
||
|
def get_dn_by_email(self, email):
|
||
|
ldap_conn = ezldap.get_ldap_conn()
|
||
|
ldap_basedn = get_cfg('misc', {}).get('aq-ezldap-basedn')
|
||
|
ldap_result_id = ldap_conn.search(ldap_basedn, ldap.SCOPE_SUBTREE,
|
||
|
filterstr='%s=%s' % (ezldap.LDAP_EMAIL, email))
|
||
|
result_type, result_data = ldap_conn.result(ldap_result_id, all=0)
|
||
|
if result_type == ldap.RES_SEARCH_ENTRY:
|
||
|
return result_data[0][0] # dn
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
def passwordreset_submit(self, email, dn):
|
||
|
token = make_token()
|
||
|
# add token into the LDAP
|
||
|
add_token(token, dn, "change_password")
|
||
|
# send mail
|
||
|
req = get_request()
|
||
|
base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
|
||
|
urllib.quote(req.environ.get('SCRIPT_NAME')))
|
||
|
data = { 'change_url': base_url + '/token/?token=%s' % token,
|
||
|
'cancel_url': base_url + '/token/cancel?token=%s' % token,
|
||
|
# FIXME: set the expiration date here
|
||
|
'time': None }
|
||
|
emails.custom_ezt_email('change-password-request', data,
|
||
|
email, exclude_current_user = False)
|
||
|
|
||
|
|
||
|
def restore_legacy_root(root_directory):
|
||
|
root_directory.myspace = MyspaceDirectory()
|
||
|
from root import AlternateRegisterDirectory
|
||
|
root_directory.register = AlternateRegisterDirectory()
|
||
|
|
||
|
def try_auth(root_directory):
|
||
|
misc_cfg = get_cfg('misc', {})
|
||
|
ldap_url = misc_cfg.get('aq-ezldap-url')
|
||
|
|
||
|
if (ldap is None) or (not ldap_url) or (not get_publisher().has_site_option('ezldap')):
|
||
|
# no ldap: restore non monkey patched classes & dir
|
||
|
get_publisher().user_class = User
|
||
|
restore_legacy_root(root_directory)
|
||
|
return
|
||
|
|
||
|
# activate the "magic user" (legacy+ldap)
|
||
|
get_publisher().user_class = ezldap.EzMagicUser
|
||
|
|
||
|
# reverse proxy detection
|
||
|
reverse_url = get_request().get_header('X-WCS-ReverseProxy-URL')
|
||
|
if reverse_url:
|
||
|
# gorilla patching : SCRIPT_NAME/get_server/get_scheme
|
||
|
parsed = urlparse(reverse_url)
|
||
|
if parsed.path[-1:] == '/':
|
||
|
get_request().environ['SCRIPT_NAME'] = parsed.path[:-1]
|
||
|
else:
|
||
|
get_request().environ['SCRIPT_NAME'] = parsed.path
|
||
|
get_request().get_server = lambda clean=True: parsed.netloc
|
||
|
get_request().get_scheme = lambda : parsed.scheme
|
||
|
else:
|
||
|
restore_legacy_root(root_directory)
|
||
|
return
|
||
|
|
||
|
# the request must come from the reverse-proxy IP
|
||
|
ezldap_ip = misc_cfg.get('aq-ezldap-ip', None)
|
||
|
if ezldap_ip and get_request().environ.get('REMOTE_ADDR') != ezldap_ip:
|
||
|
# not a good IP: restore non monkey patched classes & dir
|
||
|
restore_legacy_root(root_directory)
|
||
|
return
|
||
|
|
||
|
# add ldap register & token (anonymous actions)
|
||
|
root_directory.register = EzRegisterDirectory()
|
||
|
root_directory.token = EzTokenDirectory()
|
||
|
|
||
|
# does the reverse-proxy provide the user DN ?
|
||
|
user_dn = get_request().get_header('X-Auquotidien-Auth-Dn')
|
||
|
session = get_session()
|
||
|
if user_dn:
|
||
|
# this is a LDAP user: set session, activate special myspace
|
||
|
session.set_user(user_dn)
|
||
|
root_directory.myspace = EzMyspaceDirectory()
|
||
|
else:
|
||
|
# disconnected
|
||
|
session.set_user(None)
|
||
|
root_directory.myspace = MyspaceDirectory()
|
||
|
|
||
|
|
||
|
EmailsDirectory.register('aq-change-email-request',
|
||
|
N_('Request for email change'),
|
||
|
N_('Available variables: new_email, current_email, change_url, cancel_url'),
|
||
|
category = N_('Identification'),
|
||
|
default_subject = N_('Change email Request'),
|
||
|
default_body = N_('''
|
||
|
You have requested to associate your account with the email address : [new_email]
|
||
|
|
||
|
Warning : this email will become your username.
|
||
|
|
||
|
To complete the change, visit the following link:
|
||
|
[change_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 cancel the contact email change request, your account will remain with
|
||
|
your current email ([current_email]).
|
||
|
|
||
|
If you do nothing, the request will lapse after 3 days.
|
||
|
'''))
|
||
|
|
||
|
TextsDirectory.register('aq-change-email',
|
||
|
N_('Text when user want to change his email'),
|
||
|
category = N_('Identification'),
|
||
|
default = N_('''
|
||
|
You can change your email address here.
|
||
|
'''))
|
||
|
|
||
|
TextsDirectory.register('aq-change-email-token-sent',
|
||
|
N_('Text after user had requested to change his email'),
|
||
|
category = N_('Identification'),
|
||
|
default = N_('''
|
||
|
A confirmation email has been sent to your new address (%(new_email)s).
|
||
|
Follow the instructions in that email to validate your request.
|
||
|
'''))
|
||
|
|
||
|
TextsDirectory.register('aq-new-email-confirmed',
|
||
|
N_('Text when new email confirmed by user'),
|
||
|
category = N_('Identification'),
|
||
|
default = N_('''
|
||
|
Your email has been changed to : %(new_email)s.
|
||
|
Remember that it's your new username !
|
||
|
'''))
|
||
|
|
||
|
TextsDirectory.register('aq-profile-presentation',
|
||
|
N_('Profile presentation'),
|
||
|
hint = N_('variables: user fields varnames'),
|
||
|
default = N_('''<p>
|
||
|
<ul>
|
||
|
<li>Email: [session_user_email]</li>
|
||
|
</ul>
|
||
|
</p>'''))
|
||
|
|
||
|
EmailsDirectory.register('aq-remove-request',
|
||
|
N_('Delete account request'),
|
||
|
N_('Available variables: email, remove_url, cancel_url'),
|
||
|
category = N_('Identification'),
|
||
|
default_subject = N_('Delete account request'),
|
||
|
default_body = N_('''
|
||
|
You have requested to *delete* your account [email]
|
||
|
|
||
|
Warning: this action is irreversible; it will destruct your personal
|
||
|
datas and destruct the access to your request history.
|
||
|
|
||
|
To complete the request, visit the following link:
|
||
|
[remove_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.
|
||
|
'''))
|
||
|
|
||
|
TextsDirectory.register('aq-remove-account',
|
||
|
N_('Text when user want to delete his account'),
|
||
|
category = N_('Identification'),
|
||
|
default = N_('Are you really sure you want to remove your account?'))
|
||
|
|
||
|
TextsDirectory.register('aq-remove-token-sent',
|
||
|
N_('Text after user had requested to delete his account'),
|
||
|
category = N_('Identification'),
|
||
|
default = N_('''
|
||
|
A confirmation email has been sent to your email address.
|
||
|
Follow the instructions in that email to validate your request.
|
||
|
'''))
|
||
|
|
||
| extra/modules/ezldap_ui.py | ||
|---|---|---|
|
'''
|
||
|
Module to provide the kind of interaction needed for a eZ publish website
|
||
|
acting as reverse proxy and passing in an HTTP header a pointer to an LDAP
|
||
|
entry.
|
||
|
'''
|
||
|
|
||
|
try:
|
||
|
import ldap
|
||
|
import ldap.modlist
|
||
|
except ImportError:
|
||
|
ldap = None
|
||
|
|
||
|
import base64
|
||
|
import datetime
|
||
|
import random
|
||
|
import string
|
||
|
import urllib
|
||
|
from urlparse import urlparse
|
||
|
try:
|
||
|
from hashlib import sha1 as sha
|
||
|
except ImportError:
|
||
|
from sha import sha
|
||
|
|
||
|
from quixote import get_publisher, get_request, redirect, get_session, get_session_manager
|
||
|
from quixote.directory import Directory
|
||
|
from quixote.html import TemplateIO, htmltext
|
||
|
|
||
|
from qommon import get_cfg, template, emails
|
||
|
from qommon import misc
|
||
|
from qommon.admin.emails import EmailsDirectory
|
||
|
from qommon.admin.texts import TextsDirectory
|
||
|
from qommon.form import *
|
||
|
from qommon.ident.password import check_password, make_password
|
||
|
from qommon import errors
|
||
|
|
||
|
from wcs.formdef import FormDef
|
||
|
from wcs.users import User
|
||
|
|
||
|
from myspace import MyspaceDirectory
|
||
|
from payments import is_payment_supported
|
||
|
|
||
|
import ezldap
|
||
|
|
||
|
def make_token():
|
||
|
'''Create a token, checking it does not already exist in the ldap base.'''
|
||
|
r = random.SystemRandom()
|
||
|
ldap_basedn = get_cfg('misc', {}).get('aq-ezldap-basedn')
|
||
|
ldap_conn = ezldap.get_ldap_conn()
|
||
|
while True:
|
||
|
token = ''.join([r.choice(string.letters + string.digits) for x in range(16)])
|
||
|
ldap_result_id = ldap_conn.search(ldap_basedn, ldap.SCOPE_SUBTREE,
|
||
|
filterstr='token=%s' % token)
|
||
|
result_type, result_data = ldap_conn.result(ldap_result_id, all=0)
|
||
|
if result_data == []:
|
||
|
break
|
||
|
return token
|
||
|
|
||
|
def add_token(token, dn, action, exp_days=3):
|
||
|
''' Add a token in the LDAP '''
|
||
|
expdate = datetime.datetime.utcnow() + datetime.timedelta(exp_days)
|
||
|
ldap_conn = ezldap.get_ldap_conn()
|
||
|
# add token into the LDAP
|
||
|
mod_list = [(ldap.MOD_REPLACE, 'token', token),
|
||
|
(ldap.MOD_REPLACE, 'tokenAction', action),
|
||
|
(ldap.MOD_REPLACE, 'tokenExpiration', expdate.strftime('%Y%m%d%H%M%SZ'))]
|
||
|
ldap_conn.modify_s(dn, mod_list)
|
||
|
return token
|
||
|
|
||
|
def email_to_admins(dn, template):
|
||
|
admins = [x for x in get_publisher().user_class.select() if x.is_admin]
|
||
|
if not admins:
|
||
|
return
|
||
|
admin_emails = [x.email for x in admins if x.email]
|
||
|
|
||
|
user = ezldap.EzLdapUser(dn)
|
||
|
data = {
|
||
|
'hostname': get_request().get_server(),
|
||
|
'username': user.email,
|
||
|
'email_as_username': 'True',
|
||
|
'name': user.display_name,
|
||
|
'email': user.email,
|
||
|
}
|
||
|
get_publisher().substitutions.feed(user)
|
||
|
|
||
|
emails.custom_ezt_email(template, data,
|
||
|
admin_emails, fire_and_forget = True)
|
||
|
|
||
|
|
||
|
class EzMyspaceDirectory(MyspaceDirectory):
|
||
|
|
||
|
def __init__(self):
|
||
|
self._q_exports.extend(['change_email'])
|
||
|
|
||
|
def _index_buttons(self, form_data):
|
||
|
r = TemplateIO(html=True)
|
||
|
r += super(EzMyspaceDirectory, self)._index_buttons(form_data)
|
||
|
r += htmltext('<p class="command"><a href="change_email">%s</a></p>') % _('Change my email')
|
||
|
r += htmltext('<br />')
|
||
|
r += htmltext('<br />')
|
||
|
r += htmltext('<p>')
|
||
|
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(' <strong><a href="remove">%s</a></strong>.') % _('Delete My Account')
|
||
|
r += htmltext('</p>')
|
||
|
return r.getvalue()
|
||
|
|
||
|
def _my_profile(self, user_formdef, user):
|
||
|
r = TemplateIO(html=True)
|
||
|
r += htmltext('<h3 id="my-profile">%s</h3>') % _('My Profile')
|
||
|
|
||
|
r += TextsDirectory.get_html_text('top-of-profile')
|
||
|
|
||
|
if user.form_data:
|
||
|
get_publisher().substitutions.feed(get_request().user)
|
||
|
data = get_publisher().substitutions.get_context_variables()
|
||
|
r += TextsDirectory.get_html_text('aq-profile-presentation', data)
|
||
|
else:
|
||
|
r += htmltext('<p>%s</p>') % _('Empty profile')
|
||
|
return r.getvalue()
|
||
|
|
||
|
def submit_password(self, new_password):
|
||
|
userPassword = ['{sha}%s' % base64.encodestring(sha(new_password).digest()).strip()]
|
||
|
mod_list = [(ldap.MOD_REPLACE, 'userPassword', userPassword)]
|
||
|
ezldap.get_ldap_conn().modify_s(get_session().user, mod_list)
|
||
|
|
||
|
def change_email(self):
|
||
|
form = Form(enctype = 'multipart/form-data')
|
||
|
form.add(ezldap.EzEmailWidget, 'new_email', title = _('New email'),
|
||
|
required=True, size=30)
|
||
|
form.add_submit('submit', _('Submit Request'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
if form.get_submit() == 'cancel':
|
||
|
return redirect('.')
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
new_email = form.get_widget('new_email').parse()
|
||
|
self.change_email_submit(new_email)
|
||
|
template.html_top(_('Change email'))
|
||
|
return TextsDirectory.get_html_text('aq-change-email-token-sent') % { 'new_email': new_email }
|
||
|
else:
|
||
|
template.html_top(_('Change email'))
|
||
|
r = TemplateIO(html=True)
|
||
|
r += TextsDirectory.get_html_text('aq-change-email')
|
||
|
r += form.render()
|
||
|
return r.getvalue()
|
||
|
|
||
|
def change_email_submit(self, new_email):
|
||
|
data = {}
|
||
|
|
||
|
token = make_token()
|
||
|
req = get_request()
|
||
|
base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
|
||
|
urllib.quote(req.environ.get('SCRIPT_NAME')))
|
||
|
data['change_url'] = base_url + '/token/?token=%s' % token
|
||
|
data['cancel_url'] = base_url + '/token/cancel?token=%s' % token
|
||
|
data['new_email'] = new_email
|
||
|
data['current_email'] = req.user.email
|
||
|
|
||
|
# add the token in the LDAP
|
||
|
add_token(token, get_session().user, "change_email;%s" % new_email)
|
||
|
# send mail
|
||
|
emails.custom_ezt_email('aq-change-email-request', data,
|
||
|
data['new_email'], fire_and_forget = True)
|
||
|
|
||
|
def remove(self):
|
||
|
user = get_request().user
|
||
|
if not user or user.anonymous:
|
||
|
raise errors.AccessUnauthorizedError()
|
||
|
form = Form(enctype = 'multipart/form-data')
|
||
|
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():
|
||
|
self.remove_email_submit()
|
||
|
template.html_top(_('Removing Account'))
|
||
|
return TextsDirectory.get_html_text('aq-remove-token-sent') % { 'email': user.email }
|
||
|
else:
|
||
|
template.html_top(_('Removing Account'))
|
||
|
r = TemplateIO(html=True)
|
||
|
r += TextsDirectory.get_html_text('aq-remove-account')
|
||
|
r += form.render()
|
||
|
return r.getvalue()
|
||
|
|
||
|
def remove_email_submit(self):
|
||
|
data = {}
|
||
|
token = make_token()
|
||
|
req = get_request()
|
||
|
base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
|
||
|
urllib.quote(req.environ.get('SCRIPT_NAME')))
|
||
|
data['remove_url'] = base_url + '/token/?token=%s' % token
|
||
|
data['cancel_url'] = base_url + '/token/cancel?token=%s' % token
|
||
|
data['email'] = req.user.email
|
||
|
# add the token in the LDAP
|
||
|
add_token(token, get_session().user, "remove")
|
||
|
# send mail
|
||
|
emails.custom_ezt_email('aq-remove-request', data,
|
||
|
data['email'], fire_and_forget = True)
|
||
|
|
||
|
class EzTokenDirectory(Directory):
|
||
|
_q_exports = ['', 'cancel']
|
||
|
|
||
|
def _q_index (self):
|
||
|
'''token processing'''
|
||
|
token = get_request().form.get('token')
|
||
|
if token:
|
||
|
result_data = self._get_ldap_entry_token(token)
|
||
|
if result_data:
|
||
|
return self._actions(result_data[0][1]['tokenAction'][0], result_data)
|
||
|
template.html_top(_('Error'))
|
||
|
return _('The token you submitted does not exist, has expired, or has been cancelled.')
|
||
|
|
||
|
def _get_ldap_entry_token(self, token):
|
||
|
''' return a ldap result for the given token '''
|
||
|
ldap_basedn = get_cfg('misc', {}).get('aq-ezldap-basedn')
|
||
|
ldap_conn = ezldap.get_ldap_conn()
|
||
|
ldap_result_id = ldap_conn.search(ldap_basedn, ldap.SCOPE_SUBTREE,
|
||
|
filterstr='token=%s' % token)
|
||
|
result_type, result_data = ldap_conn.result(ldap_result_id, all=0)
|
||
|
if result_type == ldap.RES_SEARCH_ENTRY:
|
||
|
return result_data
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
def _actions(self, token_action, result_data):
|
||
|
dn = result_data[0][0]
|
||
|
action = token_action.split(";")[0]
|
||
|
if action == 'create':
|
||
|
mod_list = [(ldap.MOD_REPLACE, 'actif', 'TRUE'),
|
||
|
(ldap.MOD_DELETE, 'token', None),
|
||
|
(ldap.MOD_DELETE, 'tokenAction', None),
|
||
|
(ldap.MOD_DELETE, 'tokenExpiration', None)]
|
||
|
ezldap.get_ldap_conn().modify_s(dn, mod_list)
|
||
|
email_to_admins(dn, 'new-registration-admin-notification')
|
||
|
template.html_top(_('Welcome'))
|
||
|
return TextsDirectory.get_html_text('account-created')
|
||
|
elif action == 'change_email':
|
||
|
new_email = token_action.split(";")[1]
|
||
|
mod_list = [(ldap.MOD_REPLACE, ezldap.LDAP_EMAIL, str(new_email)),
|
||
|
(ldap.MOD_REPLACE, 'actif', 'TRUE'),
|
||
|
(ldap.MOD_DELETE, 'token', None),
|
||
|
(ldap.MOD_DELETE, 'tokenAction', None),
|
||
|
(ldap.MOD_DELETE, 'tokenExpiration', None)]
|
||
|
ezldap.get_ldap_conn().modify_s(dn, mod_list)
|
||
|
template.html_top(_('Email changed'))
|
||
|
return TextsDirectory.get_html_text('aq-new-email-confirmed') % { 'new_email': new_email }
|
||
|
elif action == 'change_password':
|
||
|
email = result_data[0][1][ezldap.LDAP_EMAIL][0]
|
||
|
new_password = make_password()
|
||
|
userPassword = ['{sha}%s' % base64.encodestring(sha(new_password).digest()).strip()]
|
||
|
mod_list = [(ldap.MOD_REPLACE, 'userPassword', userPassword),
|
||
|
(ldap.MOD_REPLACE, 'actif', 'TRUE'),
|
||
|
(ldap.MOD_DELETE, 'token', None),
|
||
|
(ldap.MOD_DELETE, 'tokenAction', None),
|
||
|
(ldap.MOD_DELETE, 'tokenExpiration', None)]
|
||
|
ezldap.get_ldap_conn().modify_s(dn, mod_list)
|
||
|
data = {
|
||
|
'username': email,
|
||
|
'password': new_password,
|
||
|
'hostname': get_request().get_server(),
|
||
|
}
|
||
|
emails.custom_ezt_email('new-generated-password', data, email,
|
||
|
exclude_current_user = False)
|
||
|
template.html_top(_('New password sent by email'))
|
||
|
return TextsDirectory.get_html_text('new-password-sent-by-email')
|
||
|
elif action == 'remove':
|
||
|
template.html_top(_('Removing Account'))
|
||
|
# email (just before remove)
|
||
|
email_to_admins(dn, 'notification-of-removed-account')
|
||
|
# delete all related forms
|
||
|
formdefs = FormDef.select()
|
||
|
user_forms = []
|
||
|
for formdef in formdefs:
|
||
|
user_forms.extend(formdef.data_class().get_with_indexed_value('user_id', dn))
|
||
|
for formdata in user_forms:
|
||
|
formdata.remove_self()
|
||
|
# delete the user in ldap
|
||
|
ezldap.get_ldap_conn().delete_s(dn)
|
||
|
# delete session and redirect to login page
|
||
|
get_session_manager().expire_session()
|
||
|
root_url = get_publisher().get_root_url()
|
||
|
return redirect('%slogin' % root_url)
|
||
|
|
||
|
def cancel(self):
|
||
|
token = get_request().form.get('token')
|
||
|
if token:
|
||
|
result_data = self._get_ldap_entry_token(token)
|
||
|
if result_data:
|
||
|
dn = result_data[0][0]
|
||
|
mod_list = [(ldap.MOD_DELETE, 'token', None),
|
||
|
(ldap.MOD_DELETE, 'tokenAction', None),
|
||
|
(ldap.MOD_DELETE, 'tokenExpiration', None)]
|
||
|
ezldap.get_ldap_conn().modify_s(dn, mod_list)
|
||
|
template.html_top(_('Request cancelled'))
|
||
|
return _('Your request has been cancelled.')
|
||
|
|
||
|
template.html_top(_('Error'))
|
||
|
return _('The token you submitted does not exist or has expired')
|
||
|
|
||
|
|
||
|
class EzRegisterDirectory(Directory):
|
||
|
_q_exports = ['', 'passwordreset']
|
||
|
|
||
|
def _q_index(self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
|
||
|
template.html_top(_('Registration'))
|
||
|
head = TextsDirectory.get_html_text('new-account')
|
||
|
form.widgets.append(HtmlWidget(head))
|
||
|
|
||
|
# build password hint : "at least 4 cars, at most 12"
|
||
|
passwords_cfg = get_cfg('passwords', {})
|
||
|
min_len = passwords_cfg.get('min_length', 0)
|
||
|
max_len = passwords_cfg.get('max_length', 0)
|
||
|
if min_len and max_len:
|
||
|
password_hint = _('At least %(min)d characters, at most %(max)d') % \
|
||
|
{'min': min_len, 'max': max_len }
|
||
|
elif min_len:
|
||
|
password_hint = _('At least %d characters') % min_len
|
||
|
elif max_len:
|
||
|
password_hint = _('At most %d characters') % max_len
|
||
|
else:
|
||
|
password_hint = ''
|
||
|
|
||
|
# get required attributes from User configuration
|
||
|
for f in User.get_formdef().fields:
|
||
|
if f.required:
|
||
|
kwargs = {'required': True, 'value': form.get(f.varname)}
|
||
|
for k in f.extra_attributes:
|
||
|
if hasattr(f, k):
|
||
|
kwargs[k] = getattr(f, k)
|
||
|
f.perform_more_widget_changes(form, kwargs)
|
||
|
form.add(f.widget_class, f.varname, title=f.label, hint=f.hint, **kwargs)
|
||
|
# static required fields
|
||
|
form.add(ezldap.EzEmailWidget, ezldap.LDAP_EMAIL, title=_('Email'),
|
||
|
value=form.get(ezldap.LDAP_EMAIL), required=True, size=30,
|
||
|
hint=_('Remember: this will be your username'))
|
||
|
form.add(PasswordWidget, '__password', title=_('New Password'),
|
||
|
value=form.get('__password'), required=True, size=15,
|
||
|
hint=password_hint)
|
||
|
form.add(PasswordWidget, '__password2', title=_('New Password (confirm)'),
|
||
|
value=form.get('__password2'), required=True, size=15)
|
||
|
form.widgets.append(CaptchaWidget('__captcha'))
|
||
|
form.add_submit('submit', _('Register'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
|
||
|
if form.get_submit() == 'cancel':
|
||
|
return redirect('..')
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
check_password(form, '__password')
|
||
|
password = form.get_widget('__password').parse()
|
||
|
password2 = form.get_widget('__password2').parse()
|
||
|
if password != password2:
|
||
|
form.set_error('__password2', _('Passwords do not match'))
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
self.register_submit(form)
|
||
|
template.html_top(_('Welcome'))
|
||
|
return TextsDirectory.get_html_text('email-sent-confirm-creation')
|
||
|
|
||
|
return form.render()
|
||
|
|
||
|
def register_submit(self, form):
|
||
|
data = {}
|
||
|
exp_days = 3
|
||
|
|
||
|
for widget in form.widgets:
|
||
|
value = widget.parse()
|
||
|
if value:
|
||
|
if widget.name == '__password':
|
||
|
password = value
|
||
|
data['userPassword'] = ['{sha}%s' % \
|
||
|
base64.encodestring(sha(password).digest()).strip()]
|
||
|
elif not widget.name.startswith('__'):
|
||
|
if widget.name.startswith('date'):
|
||
|
date = datetime.datetime.strptime(value, misc.date_format())
|
||
|
data[widget.name] = date.strftime('%Y%m%d%H%M%SZ')
|
||
|
else:
|
||
|
data[widget.name] = [value]
|
||
|
|
||
|
misc_cfg = get_cfg('misc', {})
|
||
|
expdate = datetime.datetime.utcnow() + datetime.timedelta(exp_days)
|
||
|
data['actif'] = 'FALSE'
|
||
|
data['token'] = make_token()
|
||
|
data['tokenAction'] = 'create'
|
||
|
data['tokenExpiration'] = expdate.strftime('%Y%m%d%H%M%SZ')
|
||
|
req = get_request()
|
||
|
base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
|
||
|
urllib.quote(req.environ.get('SCRIPT_NAME')))
|
||
|
token_url = base_url + '/token/?token=%s' % data['token']
|
||
|
|
||
|
data['objectClass'] = 'vincennesCitoyen'
|
||
|
mod_list = ldap.modlist.addModlist(data)
|
||
|
ldap_dntemplate = misc_cfg.get('aq-ezldap-dntemplate')
|
||
|
while True:
|
||
|
dn = ldap_dntemplate % random.randint(100000, 5000000)
|
||
|
try:
|
||
|
ezldap.get_ldap_conn().add_s(dn, mod_list)
|
||
|
except ldap.ALREADY_EXISTS:
|
||
|
continue
|
||
|
break
|
||
|
|
||
|
data = {
|
||
|
'email': data[ezldap.LDAP_EMAIL][0],
|
||
|
'username': data[ezldap.LDAP_EMAIL][0],
|
||
|
'password': password,
|
||
|
'token_url': token_url,
|
||
|
'website': base_url,
|
||
|
'admin_email': '',
|
||
|
}
|
||
|
emails.custom_ezt_email('password-subscription-notification', data,
|
||
|
data.get('email'), fire_and_forget = True)
|
||
|
|
||
|
|
||
|
def passwordreset(self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
form.add(EmailWidget, 'email', title=_('Email'), required=True, size=30)
|
||
|
form.add_submit('change', _('Submit Request'))
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
email = form.get_widget('email').parse()
|
||
|
dn = self.get_dn_by_email(email)
|
||
|
if dn:
|
||
|
self.passwordreset_submit(email, dn)
|
||
|
template.html_top(_('Forgotten Password'))
|
||
|
return TextsDirectory.get_html_text(str('password-forgotten-token-sent'))
|
||
|
else:
|
||
|
form.set_error('email', _('There is no user with that email.'))
|
||
|
template.html_top(_('Forgotten Password'))
|
||
|
r = TemplateIO(html=True)
|
||
|
r += TextsDirectory.get_html_text(str('password-forgotten-enter-username'))
|
||
|
r += form.render()
|
||
|
return r.getvalue()
|
||
|
else:
|
||
|
template.html_top(_('Forgotten Password'))
|
||
|
r = TemplateIO(html=True)
|
||
|
r += TextsDirectory.get_html_text(str('password-forgotten-enter-username'))
|
||
|
r += form.render()
|
||
|
return r.getvalue()
|
||
|
|
||
|
def get_dn_by_email(self, email):
|
||
|
ldap_conn = ezldap.get_ldap_conn()
|
||
|
ldap_basedn = get_cfg('misc', {}).get('aq-ezldap-basedn')
|
||
|
ldap_result_id = ldap_conn.search(ldap_basedn, ldap.SCOPE_SUBTREE,
|
||
|
filterstr='%s=%s' % (ezldap.LDAP_EMAIL, email))
|
||
|
result_type, result_data = ldap_conn.result(ldap_result_id, all=0)
|
||
|
if result_type == ldap.RES_SEARCH_ENTRY:
|
||
|
return result_data[0][0] # dn
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
def passwordreset_submit(self, email, dn):
|
||
|
token = make_token()
|
||
|
# add token into the LDAP
|
||
|
add_token(token, dn, "change_password")
|
||
|
# send mail
|
||
|
req = get_request()
|
||
|
base_url = '%s://%s%s' % (req.get_scheme(), req.get_server(),
|
||
|
urllib.quote(req.environ.get('SCRIPT_NAME')))
|
||
|
data = { 'change_url': base_url + '/token/?token=%s' % token,
|
||
|
'cancel_url': base_url + '/token/cancel?token=%s' % token,
|
||
|
# FIXME: set the expiration date here
|
||
|
'time': None }
|
||
|
emails.custom_ezt_email('change-password-request', data,
|
||
|
email, exclude_current_user = False)
|
||
|
|
||
|
|
||
|
def restore_legacy_root(root_directory):
|
||
|
root_directory.myspace = MyspaceDirectory()
|
||
|
from root import AlternateRegisterDirectory
|
||
|
root_directory.register = AlternateRegisterDirectory()
|
||
|
|
||
|
def try_auth(root_directory):
|
||
|
misc_cfg = get_cfg('misc', {})
|
||
|
ldap_url = misc_cfg.get('aq-ezldap-url')
|
||
|
|
||
|
if (ldap is None) or (not ldap_url) or (not get_publisher().has_site_option('ezldap')):
|
||
|
# no ldap: restore non monkey patched classes & dir
|
||
|
get_publisher().user_class = User
|
||
|
restore_legacy_root(root_directory)
|
||
|
return
|
||
|
|
||
|
# activate the "magic user" (legacy+ldap)
|
||
|
get_publisher().user_class = ezldap.EzMagicUser
|
||
|
|
||
|
# reverse proxy detection
|
||
|
reverse_url = get_request().get_header('X-WCS-ReverseProxy-URL')
|
||
|
if reverse_url:
|
||
|
# gorilla patching : SCRIPT_NAME/get_server/get_scheme
|
||
|
parsed = urlparse(reverse_url)
|
||
|
if parsed.path[-1:] == '/':
|
||
|
get_request().environ['SCRIPT_NAME'] = parsed.path[:-1]
|
||
|
else:
|
||
|
get_request().environ['SCRIPT_NAME'] = parsed.path
|
||
|
get_request().get_server = lambda clean=True: parsed.netloc
|
||
|
get_request().get_scheme = lambda : parsed.scheme
|
||
|
else:
|
||
|
restore_legacy_root(root_directory)
|
||
|
return
|
||
|
|
||
|
# the request must come from the reverse-proxy IP
|
||
|
ezldap_ip = misc_cfg.get('aq-ezldap-ip', None)
|
||
|
if ezldap_ip and get_request().environ.get('REMOTE_ADDR') != ezldap_ip:
|
||
|
# not a good IP: restore non monkey patched classes & dir
|
||
|
restore_legacy_root(root_directory)
|
||
|
return
|
||
|
|
||
|
# add ldap register & token (anonymous actions)
|
||
|
root_directory.register = EzRegisterDirectory()
|
||
|
root_directory.token = EzTokenDirectory()
|
||
|
|
||
|
# does the reverse-proxy provide the user DN ?
|
||
|
user_dn = get_request().get_header('X-Auquotidien-Auth-Dn')
|
||
|
session = get_session()
|
||
|
if user_dn:
|
||
|
# this is a LDAP user: set session, activate special myspace
|
||
|
session.set_user(user_dn)
|
||
|
root_directory.myspace = EzMyspaceDirectory()
|
||
|
else:
|
||
|
# disconnected
|
||
|
session.set_user(None)
|
||
|
root_directory.myspace = MyspaceDirectory()
|
||
|
|
||
|
|
||
|
EmailsDirectory.register('aq-change-email-request',
|
||
|
N_('Request for email change'),
|
||
|
N_('Available variables: new_email, current_email, change_url, cancel_url'),
|
||
|
category = N_('Identification'),
|
||
|
default_subject = N_('Change email Request'),
|
||
|
default_body = N_('''
|
||
|
You have requested to associate your account with the email address : [new_email]
|
||
|
|
||
|
Warning : this email will become your username.
|
||
|
|
||
|
To complete the change, visit the following link:
|
||
|
[change_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 cancel the contact email change request, your account will remain with
|
||
|
your current email ([current_email]).
|
||
|
|
||
|
If you do nothing, the request will lapse after 3 days.
|
||
|
'''))
|
||
|
|
||
|
TextsDirectory.register('aq-change-email',
|
||
|
N_('Text when user want to change his email'),
|
||
|
category = N_('Identification'),
|
||
|
default = N_('''
|
||
|
You can change your email address here.
|
||
|
'''))
|
||
|
|
||
|
TextsDirectory.register('aq-change-email-token-sent',
|
||
|
N_('Text after user had requested to change his email'),
|
||
|
category = N_('Identification'),
|
||
|
default = N_('''
|
||
|
A confirmation email has been sent to your new address (%(new_email)s).
|
||
|
Follow the instructions in that email to validate your request.
|
||
|
'''))
|
||
|
|
||
|
TextsDirectory.register('aq-new-email-confirmed',
|
||
|
N_('Text when new email confirmed by user'),
|
||
|
category = N_('Identification'),
|
||
|
default = N_('''
|
||
|
Your email has been changed to : %(new_email)s.
|
||
|
Remember that it's your new username !
|
||
|
'''))
|
||
|
|
||
|
TextsDirectory.register('aq-profile-presentation',
|
||
|
N_('Profile presentation'),
|
||
|
hint = N_('variables: user fields varnames'),
|
||
|
default = N_('''<p>
|
||
|
<ul>
|
||
|
<li>Email: [session_user_email]</li>
|
||
|
</ul>
|
||
|
</p>'''))
|
||
|
|
||
|
EmailsDirectory.register('aq-remove-request',
|
||
|
N_('Delete account request'),
|
||
|
N_('Available variables: email, remove_url, cancel_url'),
|
||
|
category = N_('Identification'),
|
||
|
default_subject = N_('Delete account request'),
|
||
|
default_body = N_('''
|
||
|
You have requested to *delete* your account [email]
|
||
|
|
||
|
Warning: this action is irreversible; it will destruct your personal
|
||
|
datas and destruct the access to your request history.
|
||
|
|
||
|
To complete the request, visit the following link:
|
||
|
[remove_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.
|
||
|
'''))
|
||
|
|
||
|
TextsDirectory.register('aq-remove-account',
|
||
|
N_('Text when user want to delete his account'),
|
||
|
category = N_('Identification'),
|
||
|
default = N_('Are you really sure you want to remove your account?'))
|
||
|
|
||
|
TextsDirectory.register('aq-remove-token-sent',
|
||
|
N_('Text after user had requested to delete his account'),
|
||
|
category = N_('Identification'),
|
||
|
default = N_('''
|
||
|
A confirmation email has been sent to your email address.
|
||
|
Follow the instructions in that email to validate your request.
|
||
|
'''))
|
||
|
|
||
| extra/modules/forms_ui.ptl | ||
|---|---|---|
|
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
|
||
|
from qommon.form import *
|
||
|
from qommon import misc, get_logger
|
||
|
|
||
|
from wcs.formdef import FormDef
|
||
|
from wcs.categories import Category
|
||
|
|
||
|
class FormDirectory(Directory):
|
||
|
_q_exports = ['options', 'disable', 'enable']
|
||
|
|
||
|
def __init__(self, formdef):
|
||
|
self.formdef = formdef
|
||
|
|
||
|
def enable(self):
|
||
|
self.formdef.disabled = False
|
||
|
self.formdef.store()
|
||
|
return redirect('..')
|
||
|
|
||
|
def disable(self):
|
||
|
self.formdef.disabled = True
|
||
|
self.formdef.store()
|
||
|
return redirect('..')
|
||
|
|
||
|
def options [html] (self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
form.add_submit('submit', _('Submit'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
form.add_submit('disable', _('Disable Form'))
|
||
|
|
||
|
if form.get_submit() == 'disable':
|
||
|
return self.disable()
|
||
|
|
||
|
if form.get_submit() == 'cancel':
|
||
|
return redirect('..')
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
return redirect('..')
|
||
|
|
||
|
'<p>'
|
||
|
_('No available options')
|
||
|
'</p>'
|
||
|
form.render()
|
||
|
|
||
|
|
||
|
def is_forms_admin():
|
||
|
user = get_request().user
|
||
|
if not user:
|
||
|
return False
|
||
|
admin_role = get_cfg('aq-permissions', {}).get('forms', None)
|
||
|
if not (user.is_admin or admin_role in (user.roles or [])):
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
|
||
|
class FormsDirectory(AccessControlled, Directory):
|
||
|
_q_exports = ['', 'new', 'listing', 'update_order']
|
||
|
label = N_('Forms')
|
||
|
|
||
|
def _q_access(self):
|
||
|
get_response().breadcrumb.append(('forms/', _('Forms Management')))
|
||
|
|
||
|
def _q_index [html] (self):
|
||
|
html_top('forms', _('Forms Management'))
|
||
|
|
||
|
session = get_session()
|
||
|
user = get_request().user
|
||
|
|
||
|
is_admin = is_forms_admin()
|
||
|
|
||
|
l = []
|
||
|
l2 = []
|
||
|
if user:
|
||
|
for formdef in FormDef.select(order_by='name', ignore_errors=True):
|
||
|
if not formdef.disabled:
|
||
|
if is_admin or formdef.is_of_concern_for(user):
|
||
|
l.append(formdef)
|
||
|
elif is_admin:
|
||
|
l.append(formdef)
|
||
|
|
||
|
cats = Category.select(order_by = 'name')
|
||
|
lists = []
|
||
|
for c in cats:
|
||
|
t = [x for x in l if x.category_id == c.id]
|
||
|
if t:
|
||
|
lists.append((c.name, t))
|
||
|
t = [x for x in l if x.category_id is None]
|
||
|
if t:
|
||
|
lists.append((None, t))
|
||
|
|
||
|
mid_term = ((len(lists) * 2) + sum([len(x[-1]) for x in lists])) / 2
|
||
|
count = 0
|
||
|
in_second_column = False
|
||
|
'<div class="splitcontent-left">'
|
||
|
for cat_name, formdefs in lists:
|
||
|
if not in_second_column and count > mid_term:
|
||
|
'</div>'
|
||
|
'<div class="splitcontent-right">'
|
||
|
count += 2 + len(formdefs)
|
||
|
if len([x for x in formdefs if not x.disabled]) == 0:
|
||
|
# all forms are disabled, dim the block
|
||
|
'<div class="bo-block dimmed-block">'
|
||
|
else:
|
||
|
'<div class="bo-block">'
|
||
|
if cat_name:
|
||
|
'<h2>%s</h2>' % cat_name
|
||
|
elif len(lists) > 1:
|
||
|
'<h2>%s</h2>' % _('Misc')
|
||
|
'<ul>'
|
||
|
for formdef in formdefs:
|
||
|
'<li>'
|
||
|
if formdef.disabled:
|
||
|
'<span class="disabled">'
|
||
|
'<a href="../%s/">%s</a>' % (formdef.url_name, formdef.name)
|
||
|
if formdef.disabled:
|
||
|
'</span>'
|
||
|
if is_admin:
|
||
|
if formdef.disabled:
|
||
|
' (<a href="%s/enable">%s</a>)' % (formdef.id, _('enable'))
|
||
|
else:
|
||
|
' (<a href="%s/disable">%s</a>)' % (formdef.id, _('disable'))
|
||
|
'</li>'
|
||
|
'</ul>'
|
||
|
'</div>'
|
||
|
'</div>'
|
||
|
|
||
|
get_logger().info('forms backoffice - home')
|
||
|
|
||
|
def _q_lookup(self, component):
|
||
|
try:
|
||
|
formdef = FormDef.get(component)
|
||
|
except KeyError:
|
||
|
raise errors.TraversalError()
|
||
|
get_response().breadcrumb.append((str(formdef.id), formdef.name))
|
||
|
return FormDirectory(formdef)
|
||
|
|
||
|
def listing(self):
|
||
|
return redirect('.')
|
||
|
|
||
| extra/modules/forms_ui.py | ||
|---|---|---|
|
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
|
||
|
from qommon.form import *
|
||
|
from qommon import misc, get_logger
|
||
|
|
||
|
from wcs.formdef import FormDef
|
||
|
from wcs.categories import Category
|
||
|
|
||
|
class FormDirectory(Directory):
|
||
|
_q_exports = ['options', 'disable', 'enable']
|
||
|
|
||
|
def __init__(self, formdef):
|
||
|
self.formdef = formdef
|
||
|
|
||
|
def enable(self):
|
||
|
self.formdef.disabled = False
|
||
|
self.formdef.store()
|
||
|
return redirect('..')
|
||
|
|
||
|
def disable(self):
|
||
|
self.formdef.disabled = True
|
||
|
self.formdef.store()
|
||
|
return redirect('..')
|
||
|
|
||
|
def options(self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
form.add_submit('submit', _('Submit'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
form.add_submit('disable', _('Disable Form'))
|
||
|
|
||
|
if form.get_submit() == 'disable':
|
||
|
return self.disable()
|
||
|
|
||
|
if form.get_submit() == 'cancel':
|
||
|
return redirect('..')
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
return redirect('..')
|
||
|
|
||
|
r = TemplateIO(html=True)
|
||
|
r += htmltext('<p>')
|
||
|
r += _('No available options')
|
||
|
r += htmltext('</p>')
|
||
|
r += form.render()
|
||
|
return r.getvalue()
|
||
|
|
||
|
|
||
|
def is_forms_admin():
|
||
|
user = get_request().user
|
||
|
if not user:
|
||
|
return False
|
||
|
admin_role = get_cfg('aq-permissions', {}).get('forms', None)
|
||
|
if not (user.is_admin or admin_role in (user.roles or [])):
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
|
||
|
class FormsDirectory(AccessControlled, Directory):
|
||
|
_q_exports = ['', 'new', 'listing', 'update_order']
|
||
|
label = N_('Forms')
|
||
|
|
||
|
def _q_access(self):
|
||
|
get_response().breadcrumb.append(('forms/', _('Forms Management')))
|
||
|
|
||
|
def _q_index(self):
|
||
|
html_top('forms', _('Forms Management'))
|
||
|
r = TemplateIO(html=True)
|
||
|
|
||
|
session = get_session()
|
||
|
user = get_request().user
|
||
|
|
||
|
is_admin = is_forms_admin()
|
||
|
|
||
|
l = []
|
||
|
l2 = []
|
||
|
if user:
|
||
|
for formdef in FormDef.select(order_by='name', ignore_errors=True):
|
||
|
if not formdef.disabled:
|
||
|
if is_admin or formdef.is_of_concern_for(user):
|
||
|
l.append(formdef)
|
||
|
elif is_admin:
|
||
|
l.append(formdef)
|
||
|
|
||
|
cats = Category.select(order_by = 'name')
|
||
|
lists = []
|
||
|
for c in cats:
|
||
|
t = [x for x in l if x.category_id == c.id]
|
||
|
if t:
|
||
|
lists.append((c.name, t))
|
||
|
t = [x for x in l if x.category_id is None]
|
||
|
if t:
|
||
|
lists.append((None, t))
|
||
|
|
||
|
mid_term = ((len(lists) * 2) + sum([len(x[-1]) for x in lists])) / 2
|
||
|
count = 0
|
||
|
in_second_column = False
|
||
|
r += htmltext('<div class="splitcontent-left">')
|
||
|
for cat_name, formdefs in lists:
|
||
|
if not in_second_column and count > mid_term:
|
||
|
r += htmltext('</div>')
|
||
|
r += htmltext('<div class="splitcontent-right">')
|
||
|
count += 2 + len(formdefs)
|
||
|
if len([x for x in formdefs if not x.disabled]) == 0:
|
||
|
# all forms are disabled, dim the block
|
||
|
r += htmltext('<div class="bo-block dimmed-block">')
|
||
|
else:
|
||
|
r += htmltext('<div class="bo-block">')
|
||
|
if cat_name:
|
||
|
r += htmltext('<h2>%s</h2>') % cat_name
|
||
|
elif len(lists) > 1:
|
||
|
r += htmltext('<h2>%s</h2>') % _('Misc')
|
||
|
r += htmltext('<ul>')
|
||
|
for formdef in formdefs:
|
||
|
r += htmltext('<li>')
|
||
|
if formdef.disabled:
|
||
|
r += htmltext('<span class="disabled">')
|
||
|
r += htmltext('<a href="../%s/">%s</a>') % (formdef.url_name, formdef.name)
|
||
|
if formdef.disabled:
|
||
|
r += htmltext('</span>')
|
||
|
if is_admin:
|
||
|
if formdef.disabled:
|
||
|
r += htmltext(' (<a href="%s/enable">%s</a>)') % (formdef.id, _('enable'))
|
||
|
else:
|
||
|
r += htmltext(' (<a href="%s/disable">%s</a>)') % (formdef.id, _('disable'))
|
||
|
r += htmltext('</li>')
|
||
|
r += htmltext('</ul>')
|
||
|
r += htmltext('</div>')
|
||
|
r += htmltext('</div>')
|
||
|
|
||
|
get_logger().info('forms backoffice - home')
|
||
|
return r.getvalue()
|
||
|
|
||
|
def _q_lookup(self, component):
|
||
|
try:
|
||
|
formdef = FormDef.get(component)
|
||
|
except KeyError:
|
||
|
raise errors.TraversalError()
|
||
|
get_response().breadcrumb.append((str(formdef.id), formdef.name))
|
||
|
return FormDirectory(formdef)
|
||
|
|
||
|
def listing(self):
|
||
|
return redirect('.')
|
||
|
|
||
| extra/modules/links_ui.ptl | ||
|---|---|---|
|
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
|
||
|
from qommon.form import *
|
||
|
|
||
|
from links import Link
|
||
|
|
||
|
|
||
|
class LinkDirectory(Directory):
|
||
|
_q_exports = ['', 'edit', 'delete']
|
||
|
|
||
|
def __init__(self, link):
|
||
|
self.link = link
|
||
|
|
||
|
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('links', title = _('Link: %s') % self.link.title)
|
||
|
'<h2>%s</h2>' % _('Link: %s') % self.link.title
|
||
|
'<p>'
|
||
|
self.link.url
|
||
|
'</p>'
|
||
|
|
||
|
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('links', title = _('Edit Link: %s') % self.link.title)
|
||
|
'<h2>%s</h2>' % _('Edit Link: %s') % self.link.title
|
||
|
form.render()
|
||
|
|
||
|
|
||
|
def form(self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
form.add(StringWidget, 'title', title = _('Title'), required = True,
|
||
|
value = self.link.title)
|
||
|
form.add(StringWidget, 'url', title = _('URL'), required=False,
|
||
|
value = self.link.url,
|
||
|
hint=_('Leave empty to create a title'))
|
||
|
form.add_submit('submit', _('Submit'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
return form
|
||
|
|
||
|
def submit(self, form):
|
||
|
for k in ('title', 'url'):
|
||
|
widget = form.get_widget(k)
|
||
|
if widget:
|
||
|
setattr(self.link, k, widget.parse())
|
||
|
self.link.store()
|
||
|
|
||
|
def delete [html] (self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
|
||
|
'You are about to irrevocably delete this link.')))
|
||
|
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('links', title = _('Delete Link'))
|
||
|
'<h2>%s</h2>' % _('Deleting Link: %s') % self.link.title
|
||
|
form.render()
|
||
|
else:
|
||
|
self.link.remove_self()
|
||
|
return redirect('..')
|
||
|
|
||
|
|
||
|
class LinksDirectory(AccessControlled, Directory):
|
||
|
_q_exports = ['', 'new', 'listing', 'update_order']
|
||
|
label = N_('Links')
|
||
|
|
||
|
def _q_access(self):
|
||
|
user = get_request().user
|
||
|
if not user:
|
||
|
raise errors.AccessUnauthorizedError()
|
||
|
admin_role = get_cfg('aq-permissions', {}).get('links', None)
|
||
|
if not (user.is_admin or admin_role in (user.roles or [])):
|
||
|
raise errors.AccessForbiddenError(
|
||
|
public_msg = _('You are not allowed to access Links Management'),
|
||
|
location_hint = 'backoffice')
|
||
|
|
||
|
get_response().breadcrumb.append(('links/', _('Links')))
|
||
|
|
||
|
|
||
|
def _q_index [html] (self):
|
||
|
html_top('links', _('Links'))
|
||
|
get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'biglist.js'])
|
||
|
|
||
|
'<ul id="main-actions">'
|
||
|
' <li><a class="new-item" href="new">%s</a></li>' % _('New Link')
|
||
|
'</ul>'
|
||
|
|
||
|
links = Link.select()
|
||
|
Link.sort_by_position(links)
|
||
|
|
||
|
'<ul class="biglist sortable" id="links-list">'
|
||
|
for l in links:
|
||
|
link_id = l.id
|
||
|
'<li class="biglistitem" id="itemId_%s">' % link_id
|
||
|
'<strong class="label"><a href="%s/">%s</a></strong>' % (link_id, l.title)
|
||
|
'<p class="details">'
|
||
|
l.url
|
||
|
'</p>'
|
||
|
'<p class="commands">'
|
||
|
command_icon('%s/edit' % link_id, 'edit')
|
||
|
command_icon('%s/delete' % link_id, 'remove')
|
||
|
'</p></li>'
|
||
|
'</ul>'
|
||
|
|
||
|
def update_order(self):
|
||
|
request = get_request()
|
||
|
new_order = request.form['order'].strip(';').split(';')
|
||
|
links = Link.select()
|
||
|
dict = {}
|
||
|
for l in links:
|
||
|
dict[str(l.id)] = l
|
||
|
for i, o in enumerate(new_order):
|
||
|
dict[o].position = i + 1
|
||
|
dict[o].store()
|
||
|
return 'ok'
|
||
|
|
||
|
|
||
|
def new [html] (self):
|
||
|
link_ui = LinkDirectory(Link())
|
||
|
|
||
|
form = link_ui.form()
|
||
|
if form.get_submit() == 'cancel':
|
||
|
return redirect('.')
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
link_ui.submit(form)
|
||
|
return redirect('%s/' % link_ui.link.id)
|
||
|
|
||
|
get_response().breadcrumb.append(('new', _('New Link')))
|
||
|
html_top('links', title = _('New Link'))
|
||
|
'<h2>%s</h2>' % _('New Link')
|
||
|
form.render()
|
||
|
|
||
|
def _q_lookup(self, component):
|
||
|
try:
|
||
|
link = Link.get(component)
|
||
|
except KeyError:
|
||
|
raise errors.TraversalError()
|
||
|
get_response().breadcrumb.append((str(link.id), link.title))
|
||
|
return LinkDirectory(link)
|
||
|
|
||
|
def listing(self):
|
||
|
return redirect('.')
|
||
|
|
||
| extra/modules/links_ui.py | ||
|---|---|---|
|
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
|
||
|
from qommon.form import *
|
||
|
|
||
|
from links import Link
|
||
|
|
||
|
|
||
|
class LinkDirectory(Directory):
|
||
|
_q_exports = ['', 'edit', 'delete']
|
||
|
|
||
|
def __init__(self, link):
|
||
|
self.link = link
|
||
|
|
||
|
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('links', title = _('Link: %s') % self.link.title)
|
||
|
r = TemplateIO(html=True)
|
||
|
r += htmltext('<h2>%s</h2>') % _('Link: %s') % self.link.title
|
||
|
r += htmltext('<p>')
|
||
|
r += self.link.url
|
||
|
r += htmltext('</p>')
|
||
|
|
||
|
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('links', title = _('Edit Link: %s') % self.link.title)
|
||
|
r = TemplateIO(html=True)
|
||
|
r += htmltext('<h2>%s</h2>') % _('Edit Link: %s') % self.link.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.link.title)
|
||
|
form.add(StringWidget, 'url', title = _('URL'), required=False,
|
||
|
value = self.link.url,
|
||
|
hint=_('Leave empty to create a title'))
|
||
|
form.add_submit('submit', _('Submit'))
|
||
|
form.add_submit('cancel', _('Cancel'))
|
||
|
return form
|
||
|
|
||
|
def submit(self, form):
|
||
|
for k in ('title', 'url'):
|
||
|
widget = form.get_widget(k)
|
||
|
if widget:
|
||
|
setattr(self.link, k, widget.parse())
|
||
|
self.link.store()
|
||
|
|
||
|
def delete(self):
|
||
|
form = Form(enctype='multipart/form-data')
|
||
|
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
|
||
|
'You are about to irrevocably delete this link.')))
|
||
|
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('links', title = _('Delete Link'))
|
||
|
r = TemplateIO(html=True)
|
||
|
r += htmltext('<h2>%s</h2>') % _('Deleting Link: %s') % self.link.title
|
||
|
r += form.render()
|
||
|
return r.getvalue()
|
||
|
else:
|
||
|
self.link.remove_self()
|
||
|
return redirect('..')
|
||
|
|
||
|
|
||
|
class LinksDirectory(AccessControlled, Directory):
|
||
|
_q_exports = ['', 'new', 'listing', 'update_order']
|
||
|
label = N_('Links')
|
||
|
|
||
|
def _q_access(self):
|
||
|
user = get_request().user
|
||
|
if not user:
|
||
|
raise errors.AccessUnauthorizedError()
|
||
|
admin_role = get_cfg('aq-permissions', {}).get('links', None)
|
||
|
if not (user.is_admin or admin_role in (user.roles or [])):
|
||
|
raise errors.AccessForbiddenError(
|
||
|
public_msg = _('You are not allowed to access Links Management'),
|
||
|
location_hint = 'backoffice')
|
||
|
|
||
|
get_response().breadcrumb.append(('links/', _('Links')))
|
||
|
|
||
|
|
||
|
def _q_index(self):
|
||
|
html_top('links', _('Links'))
|
||
|
r = TemplateIO(html=True)
|
||
|
get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'biglist.js'])
|
||
|
|
||
|
r += htmltext('<ul id="main-actions">')
|
||
|
r += htmltext(' <li><a class="new-item" href="new">%s</a></li>') % _('New Link')
|
||
|
r += htmltext('</ul>')
|
||
|
|
||
|
links = Link.select()
|
||
|
Link.sort_by_position(links)
|
||
|
|
||
|
r += htmltext('<ul class="biglist sortable" id="links-list">')
|
||
|
for l in links:
|
||
|
link_id = l.id
|
||
|
r += htmltext('<li class="biglistitem" id="itemId_%s">') % link_id
|
||
|
r += htmltext('<strong class="label"><a href="%s/">%s</a></strong>') % (link_id, l.title)
|
||
|
r += htmltext('<p class="details">')
|
||
|
r += l.url
|
||
|
r += htmltext('</p>')
|
||
|
r += htmltext('<p class="commands">')
|
||
|
r += command_icon('%s/edit' % link_id, 'edit')
|
||
|
r += command_icon('%s/delete' % link_id, 'remove')
|
||
|
r += htmltext('</p></li>')
|
||
|
r += htmltext('</ul>')
|
||
|
return r.getvalue()
|
||
|
|
||
|
def update_order(self):
|
||
|
request = get_request()
|
||
|
new_order = request.form['order'].strip(';').split(';')
|
||
|
links = Link.select()
|
||
|
dict = {}
|
||
|
for l in links:
|
||
|
dict[str(l.id)] = l
|
||
|
for i, o in enumerate(new_order):
|
||
|
dict[o].position = i + 1
|
||
|
dict[o].store()
|
||
|
return 'ok'
|
||
|
|
||
|
|
||
|
def new(self):
|
||
|
link_ui = LinkDirectory(Link())
|
||
|
|
||
|
form = link_ui.form()
|
||
|
if form.get_submit() == 'cancel':
|
||
|
return redirect('.')
|
||
|
|
||
|
if form.is_submitted() and not form.has_errors():
|
||
|
link_ui.submit(form)
|
||
|
return redirect('%s/' % link_ui.link.id)
|
||
|
|
||
|
get_response().breadcrumb.append(('new', _('New Link')))
|
||
|
html_top('links', title = _('New Link'))
|
||
|
r = TemplateIO(html=True)
|
||
|
r += htmltext('<h2>%s</h2>') % _('New Link')
|
||
|
r += form.render()
|
||
|
return r.getvalue()
|
||
|
|
||
|
def _q_lookup(self, component):
|
||
|
try:
|
||
|
link = Link.get(component)
|
||
|
except KeyError:
|
||
|
raise errors.TraversalError()
|
||
|
get_response().breadcrumb.append((str(link.id), link.title))
|
||
|
return LinkDirectory(link)
|
||
|
|
||
|
def listing(self):
|
||
|
return redirect('.')
|
||
|
|
||
Also available in: Unified diff
convert some ptl files (#3930)