Compare commits
73 Commits
Author | SHA1 | Date |
---|---|---|
Thomas NOËL | 1da88185d7 | |
Frédéric Péters | c5abb97846 | |
Thomas NOËL | 0ee69733cb | |
Frédéric Péters | 2d44e3e1c5 | |
Thomas NOËL | cf3ae32eae | |
Thomas NOËL | d35cd08f13 | |
Frédéric Péters | 4367708e69 | |
Thomas NOËL | b975123670 | |
Thomas NOËL | 4112517b51 | |
Thomas NOËL | 522bb1d368 | |
Thomas NOËL | 5c21b45e48 | |
Thomas NOËL | fe97a1a543 | |
Thomas NOËL | ef97287773 | |
Thomas NOËL | f1c480f744 | |
Thomas NOËL | e79b4e1f87 | |
Thomas NOËL | 42c92394d6 | |
Thomas NOËL | eb29fb0826 | |
Thomas NOËL | 55db0d34be | |
Thomas NOËL | 57801a6e7c | |
Thomas NOËL | 8f4f8a255d | |
Frédéric Péters | 975aecb98e | |
Frédéric Péters | 9277880b87 | |
Frédéric Péters | 1f136c1b13 | |
Frédéric Péters | 9a3a679048 | |
Frédéric Péters | a7b4f461b6 | |
Frédéric Péters | a935bb65a5 | |
Frédéric Péters | 56371c4ccb | |
Frédéric Péters | 56dfd14339 | |
Frédéric Péters | f0b7060d87 | |
Frédéric Péters | f66ff32899 | |
Frédéric Péters | aa1d54e622 | |
Frédéric Péters | b443e1912a | |
Frédéric Péters | 69100dd066 | |
Frédéric Péters | ebe3e94fde | |
Frédéric Péters | 0bb4f40c1c | |
Thomas NOËL | 6c071a154a | |
Frédéric Péters | dfa5675d82 | |
Frédéric Péters | 57ffd032b2 | |
Thomas NOËL | ab694ac166 | |
Frédéric Péters | 3caa9927f4 | |
Frédéric Péters | b8fca01771 | |
Benjamin Dauvergne | b3a715a3b9 | |
Frédéric Péters | 3b087fba26 | |
Thomas NOËL | 44538b52f1 | |
Thomas NOËL | 236bbd8e2b | |
Frédéric Péters | 909e6cd7ca | |
Thomas NOËL | 8230b00de6 | |
Thomas NOËL | 7c8e1ab0b8 | |
Benjamin Dauvergne | da7635f902 | |
Benjamin Dauvergne | 27a170136f | |
Frédéric Péters | 9ca1b0c05f | |
Frédéric Péters | af78aecfac | |
Frédéric Péters | ed8b18268f | |
Frédéric Péters | 93cf9ff1bc | |
Frédéric Péters | b9d4b1e59e | |
Thomas NOËL | 13351720fb | |
Thomas NOËL | 99ec4610c9 | |
Thomas NOËL | c444d92f45 | |
Frédéric Péters | d4033440a8 | |
Frédéric Péters | dc9a2de6c4 | |
Thomas NOËL | 24d9cb877c | |
Thomas NOËL | 92ae53e83d | |
Thomas NOËL | b6f344a6f7 | |
Thomas NOËL | b6b0b8e862 | |
Frédéric Péters | 9ab836e326 | |
Frédéric Péters | e90df55517 | |
Thomas NOËL | 42aca70ef2 | |
Frédéric Péters | d9ad5b0e66 | |
Frédéric Péters | 8137ebd93a | |
Frédéric Péters | 746b001ba8 | |
Thomas NOËL | 0fbde47930 | |
Thomas NOËL | f13883763f | |
Thomas NOËL | de48f2420f |
|
@ -1,3 +1,157 @@
|
|||
wcs (2012.11-17) squeeze; urgency=low
|
||||
|
||||
[ Thomas NOEL ]
|
||||
* remove wcs.pot
|
||||
|
||||
[ Frédéric Péters ]
|
||||
* forms: fix edition of current formdata (#2789)
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Tue, 23 Apr 2013 09:23:52 +0200
|
||||
|
||||
wcs (2012.11-16) squeeze; urgency=low
|
||||
|
||||
[ Frédéric Péters ]
|
||||
* workflows: fix embedding of timeout options (#2596)
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Fri, 15 Mar 2013 15:01:32 +0100
|
||||
|
||||
wcs (2012.11-15) squeeze; urgency=low
|
||||
|
||||
* repackaging (error in building translations *.mo files)
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Thu, 14 Mar 2013 10:17:31 +0100
|
||||
|
||||
wcs (2012.11-14) squeeze; urgency=low
|
||||
|
||||
* biglist can handle long ids. fix #2580
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Thu, 14 Mar 2013 09:38:00 +0100
|
||||
|
||||
wcs (2012.11-13) squeeze; urgency=low
|
||||
|
||||
* update french translations
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Mon, 11 Mar 2013 14:45:01 +0100
|
||||
|
||||
wcs (2012.11-12) squeeze; urgency=low
|
||||
|
||||
* workflow/svg: correct encoding for textwrap (fix #2544)
|
||||
* french translation updates
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Thu, 07 Mar 2013 17:39:48 +0100
|
||||
|
||||
wcs (2012.11-11) squeeze; urgency=low
|
||||
|
||||
* sms: separate setting and test pages
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Thu, 07 Mar 2013 12:09:41 +0100
|
||||
|
||||
wcs (2012.11-10) squeeze; urgency=low
|
||||
|
||||
* fix sms sending
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Tue, 05 Mar 2013 15:39:25 +0100
|
||||
|
||||
wcs (2012.11-9) squeeze; urgency=low
|
||||
|
||||
[ Frédéric Péters ]
|
||||
* cron: use prctl to set process name & title (#2524)
|
||||
* publisher: do not fail on early statsd call (#2521)
|
||||
* formdata: catch various bugs happening with draft forms
|
||||
* formdef: avoid duplicated url_name
|
||||
* formdef: do not run migration path when checking on url_name
|
||||
* formdef: fix passing of ignore_migration arg
|
||||
* admin: add links to formdefs from a workflow page (#1908)
|
||||
* admin: properly translate 'change' link (#2449)
|
||||
* ctl: add a runscript command, to run a given script within a publisher context
|
||||
* admin: add possibility for a theme to provide a supplementary css (#2429)
|
||||
* fields: automatically create paragraphs on blank lines in comment fields
|
||||
* css: add note styles to default theme (#2265)
|
||||
* form: add effective use of <label> in more places
|
||||
* fix bug in rendering file widget label (in case of htmltext)
|
||||
* display general form view on new form, not fields view
|
||||
* do not fail getting workflow subdirectories for draft formdata (#2436)
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Tue, 05 Mar 2013 14:25:29 +0100
|
||||
|
||||
wcs (2012.11-8) squeeze; urgency=low
|
||||
|
||||
[ Frédéric Péters ]
|
||||
* workflows: do not reuse action instances in default workflow
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Tue, 05 Mar 2013 13:41:57 +0100
|
||||
|
||||
wcs (2012.11-7) squeeze; urgency=low
|
||||
|
||||
[ Frédéric Péters ]
|
||||
* admin/wf: use gettext in breadcrumbs (#486)
|
||||
* qommon: added basic statsd infrastructure (#1862)
|
||||
* password: do not abort on trying to remove a user without password account
|
||||
|
||||
[ Benjamin Dauvergne ]
|
||||
* storage: fix _atomic_write to create hidden temporary files (#2010)
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Mon, 04 Mar 2013 16:38:25 +0100
|
||||
|
||||
wcs (2012.11-6) squeeze; urgency=low
|
||||
|
||||
[ Thomas NOEL ]
|
||||
* ptl: use %s instead of %d (fix #1910)
|
||||
* default workflow items are visible
|
||||
* update french translations
|
||||
|
||||
[ Frédéric Péters ]
|
||||
* do not fail on checking for submitter when user hashes get disabled
|
||||
* css: avoid a vertical scrollbar on main content of backoffice/admin
|
||||
* workflows: enhance default workflow to mail comments when in 'new' status
|
||||
* admin: reduce number of breadcrumbs in workflows admin (#486)
|
||||
* workflows: allow marking a status as terminal (#1960)
|
||||
|
||||
[ Benjamin Dauvergne ]
|
||||
* prevent error when lasso does not have the EP_HREF symbol
|
||||
* storage: remove unused import
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Fri, 23 Nov 2012 14:43:17 +0100
|
||||
|
||||
wcs (2012.11-5) squeeze; urgency=low
|
||||
|
||||
* workflows: do not stop checking for authorization after submitter check
|
||||
* fix notification emails to point to the backoffice URLs
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Mon, 12 Nov 2012 09:56:42 +0100
|
||||
|
||||
wcs (2012.11-4) squeeze; urgency=low
|
||||
|
||||
* hotfixes:
|
||||
- storage: fix getting object filename (#1901)
|
||||
- rebuild-indexes: also rebuild formdata indexes (#1903)
|
||||
- fix form_status_changed (oups...)
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Fri, 09 Nov 2012 12:31:19 +0100
|
||||
|
||||
wcs (2012.11-3) squeeze; urgency=low
|
||||
|
||||
* hotfixes:
|
||||
- fields: fix calculation of first element length in item field
|
||||
- forms: fix public listing
|
||||
- publisher: do not fail exporting substitution variables in cron jobs
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Tue, 06 Nov 2012 19:21:56 +0100
|
||||
|
||||
wcs (2012.11-2) squeeze; urgency=low
|
||||
|
||||
* formdata.py: also migrate formdata evolution to new workflows
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Mon, 05 Nov 2012 15:16:28 +0100
|
||||
|
||||
wcs (2012.11-1) squeeze; urgency=low
|
||||
|
||||
* now use a date versioning scheme: year.month
|
||||
* this w.c.s. release is linked to the "auquotidien" 2012.3 Release
|
||||
Candidate 1, see http://dev.entrouvert.org/versions/86
|
||||
|
||||
-- Thomas NOEL <tnoel@entrouvert.com> Fri, 02 Nov 2012 14:46:11 +0100
|
||||
|
||||
wcs (1.9-0.20111226.1) squeeze; urgency=low
|
||||
|
||||
* Upgrade packaging for Squeeze.
|
||||
|
|
3993
po/wcs.pot
3993
po/wcs.pot
File diff suppressed because it is too large
Load Diff
|
@ -296,7 +296,7 @@ class FormDefPage(Directory):
|
|||
'<div class="splitcontent-right">'
|
||||
'<div class="bo-block">'
|
||||
'<h3>%s' % _('Options')
|
||||
' <span class="change">(<a rel="popup" href="options">change</a>)</span></h3>'
|
||||
' <span class="change">(<a rel="popup" href="options">%s</a>)</span></h3>' % _('change')
|
||||
'<ul>'
|
||||
if self.formdef.confirmation:
|
||||
'<li>%s</li>' % _('Include confirmation page')
|
||||
|
@ -1048,7 +1048,7 @@ class FormsDirectory(Directory):
|
|||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
return redirect(str(formdef.id) + '/fields')
|
||||
return redirect(str(formdef.id) + '/')
|
||||
|
||||
html_top('forms', title = _('New Form'))
|
||||
'<h2>%s</h2>' % _('New Form')
|
||||
|
|
|
@ -126,7 +126,7 @@ class RolePage(Directory):
|
|||
'<h3>%s</h3>' % _('Forms handled by this role')
|
||||
'<ul>'
|
||||
for formdef in receiver_formdefs:
|
||||
'<li><a href="../../forms/%d">' % formdef.id
|
||||
'<li><a href="../../forms/%s">' % formdef.id
|
||||
formdef.name
|
||||
'</a></li>'
|
||||
if not receiver_formdefs:
|
||||
|
@ -140,7 +140,7 @@ class RolePage(Directory):
|
|||
'<h3>%s</h3>' % _('Forms private to this role')
|
||||
'<ul>'
|
||||
for formdef in sender_formdefs:
|
||||
'<li><a href="../../forms/%d">' % formdef.id
|
||||
'<li><a href="../../forms/%s">' % formdef.id
|
||||
formdef.name
|
||||
'</a></li>'
|
||||
if not sender_formdefs:
|
||||
|
|
|
@ -698,7 +698,7 @@ class SettingsDirectory(QommonSettingsDirectory):
|
|||
form = Form(enctype='multipart/form-data')
|
||||
form.add(SingleSelectWidget, 'mode', title = _('SMS Mode'),
|
||||
value = mode,
|
||||
options = [('none', _('No support'), 'none')]+
|
||||
options = [(str('none'), _('No support'), str('none'))]+
|
||||
[(str(k), _(SMS.providers.get(k)[0]), str(k)) for k in SMS.providers.keys()])
|
||||
|
||||
form.add_submit('submit', _('Submit'))
|
||||
|
@ -732,14 +732,7 @@ class SettingsDirectory(QommonSettingsDirectory):
|
|||
form.render()
|
||||
|
||||
if mode != 'none':
|
||||
form = Form(enctype='multipart/form-data', action='smstest')
|
||||
form.add(StringWidget, 'sender', title=_('Sender'), required=True)
|
||||
form.add(StringWidget, 'destinations', title=_('Destinations'), required=True)
|
||||
form.add(StringWidget, 'text', title=_('Text'), required=True)
|
||||
form.add_submit('submit', _('Submit'))
|
||||
|
||||
'<h3>%s</h3>' % _('SMS Test')
|
||||
form.render()
|
||||
'<p><a href="smstest">%s</a></p>' % _('SMS Test')
|
||||
|
||||
def smstest [html] (self):
|
||||
form = Form(enctype='multipart/form-data', action='smstest')
|
||||
|
@ -752,15 +745,13 @@ class SettingsDirectory(QommonSettingsDirectory):
|
|||
if form.get_widget('cancel').parse():
|
||||
return redirect('sms')
|
||||
|
||||
if not form.get_submit():
|
||||
return redirect('sms')
|
||||
|
||||
get_response().breadcrumb.append(('sms', _('SMS')))
|
||||
get_response().breadcrumb.append(('smstest', _('SMS Test')))
|
||||
html_top('settings', title = _('SMS Test'))
|
||||
'<h2>%s</h2>' % _('SMS Test')
|
||||
form.render()
|
||||
|
||||
if not form.has_errors():
|
||||
if form.get_submit() and not form.has_errors():
|
||||
sms_cfg = get_cfg('sms', {})
|
||||
mode = sms_cfg.get('mode', 'none')
|
||||
sms = SMS.get_sms_class(mode)
|
||||
|
|
|
@ -133,7 +133,10 @@ def graphviz(workflow, url_prefix='', select=None, svg=True,
|
|||
label += ' %s %s' % (_('by'), roles)
|
||||
else:
|
||||
label = item.render_as_line()
|
||||
label = textwrap.fill(label.replace('"', '\\"'), 20)
|
||||
label = label.replace('"', '\\"')
|
||||
label = label.decode('utf8')
|
||||
label = textwrap.fill(label, 20, break_long_words=False)
|
||||
label = label.encode('utf8')
|
||||
label = label.replace('\n', '\\n')
|
||||
print >>out, '[label="%s"' % label,
|
||||
if select == '%s-%s' % (i, item.id):
|
||||
|
@ -205,7 +208,7 @@ class WorkflowItemPage(Directory):
|
|||
raise errors.TraversalError()
|
||||
self.workflow = workflow
|
||||
self.status = status
|
||||
get_response().breadcrumb.append((component + '/', self.item.description))
|
||||
get_response().breadcrumb.append(('items/%s/' % component, _(self.item.description)))
|
||||
|
||||
def _q_index [html] (self):
|
||||
request = get_request()
|
||||
|
@ -237,6 +240,15 @@ class WorkflowItemPage(Directory):
|
|||
self.workflow.store()
|
||||
return redirect('..')
|
||||
|
||||
get_response().filter['sidebar'] = self.get_sidebar()
|
||||
|
||||
def get_sidebar [html] (self):
|
||||
if str(self.workflow.id).startswith(str('_')):
|
||||
'<p>'
|
||||
_('''This is the default workflow, you cannot edit it but you can
|
||||
duplicate it to base your own workflow on it.''')
|
||||
'</p>'
|
||||
|
||||
def delete [html] (self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.widgets.append(HtmlWidget('<p>%s</p>' % _('You are about to remove an item.')))
|
||||
|
@ -264,7 +276,6 @@ class WorkflowItemsDir(Directory):
|
|||
self.status = status
|
||||
|
||||
def _q_lookup(self, component):
|
||||
get_response().breadcrumb.append(('items/', _('Items')))
|
||||
return WorkflowItemPage(self.workflow, self.status, component)
|
||||
|
||||
def _q_index(self):
|
||||
|
@ -272,7 +283,8 @@ class WorkflowItemsDir(Directory):
|
|||
|
||||
class WorkflowStatusPage(Directory):
|
||||
_q_exports = ['', 'delete', 'newitem', ('items', 'items_dir'),
|
||||
'update_order', 'edit', 'reassign', 'visibility']
|
||||
'update_order', 'edit', 'reassign', 'visibility',
|
||||
'endpoint']
|
||||
|
||||
def __init__(self, workflow, status_id):
|
||||
self.workflow = workflow
|
||||
|
@ -282,7 +294,7 @@ class WorkflowStatusPage(Directory):
|
|||
raise errors.TraversalError()
|
||||
|
||||
self.items_dir = WorkflowItemsDir(workflow, self.status)
|
||||
get_response().breadcrumb.append((status_id + '/', self.status.name))
|
||||
get_response().breadcrumb.append(('status/%s/' % status_id, _('Status: %s') % self.status.name))
|
||||
|
||||
def _q_index [html] (self):
|
||||
html_top('workflows', '%s - %s' % (_('Workflow'), self.workflow.name))
|
||||
|
@ -318,13 +330,11 @@ class WorkflowStatusPage(Directory):
|
|||
'<ul id="items-list" class="biglist sortable">'
|
||||
for i, item in enumerate(self.status.items):
|
||||
'<li class="biglistitem" id="itemId_%s">' % item.id
|
||||
if str(self.workflow.id).startswith(str('_')):
|
||||
item.render_as_line()
|
||||
if hasattr(item, str('fill_admin_form')):
|
||||
'<a href="items/%s/">%s</a>' % (item.id, item.render_as_line())
|
||||
else:
|
||||
if hasattr(item, str('fill_admin_form')):
|
||||
'<a href="items/%s/">%s</a>' % (item.id, item.render_as_line())
|
||||
else:
|
||||
item.render_as_line()
|
||||
item.render_as_line()
|
||||
if not str(self.workflow.id).startswith(str('_')):
|
||||
'<p class="commands">'
|
||||
if hasattr(item, str('fill_admin_form')):
|
||||
command_icon('items/%s/' % item.id, 'edit')
|
||||
|
@ -354,6 +364,7 @@ class WorkflowStatusPage(Directory):
|
|||
'<ul>'
|
||||
'<li><a href="edit">%s</a></li>' % _('Change Status Name')
|
||||
'<li><a href="visibility" rel="popup">%s</a></li>' % _('Change Status Visibility')
|
||||
'<li><a href="endpoint" rel="popup">%s</a></li>' % _('Change Terminal Status')
|
||||
'<li><a href="delete" rel="popup">%s</a></li>' % _('Delete')
|
||||
'</ul>'
|
||||
'<div id="new-field">'
|
||||
|
@ -553,6 +564,25 @@ class WorkflowStatusPage(Directory):
|
|||
'<h2>%s</h2>' % _('Edit Workflow Status Visibility')
|
||||
form.render()
|
||||
|
||||
def endpoint [html] (self):
|
||||
form = Form(enctype = 'multipart/form-data')
|
||||
form.add(CheckboxWidget, 'force_terminal_status',
|
||||
title=_('Force Terminal Status'),
|
||||
value=(self.status.forced_endpoint == True))
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('..')
|
||||
|
||||
if form.is_submitted() and not form.has_errors():
|
||||
self.status.forced_endpoint = form.get_widget('force_terminal_status').parse()
|
||||
self.workflow.store()
|
||||
return redirect('.')
|
||||
|
||||
html_top('workflows', title = _('Edit Terminal Status'))
|
||||
get_response().breadcrumb.append( ('endpoint', _('Terminal Status')) )
|
||||
form.render()
|
||||
|
||||
|
||||
class WorkflowStatusDirectory(Directory):
|
||||
_q_exports = ['']
|
||||
|
@ -561,7 +591,6 @@ class WorkflowStatusDirectory(Directory):
|
|||
self.workflow = workflow
|
||||
|
||||
def _q_lookup(self, component):
|
||||
get_response().breadcrumb.append(('status/', _('Status')))
|
||||
return WorkflowStatusPage(self.workflow, component)
|
||||
|
||||
def _q_index(self):
|
||||
|
@ -612,6 +641,15 @@ class WorkflowPage(Directory):
|
|||
htmltext(graphviz(self.workflow, include=True))
|
||||
'</div>' # bo-block
|
||||
|
||||
formdefs = [x for x in FormDef.select() if x.workflow_id == self.workflow.id]
|
||||
if formdefs:
|
||||
'<div class="bo-block">'
|
||||
'<h3>%s</h3>' % _('Forms')
|
||||
'<p>%s ' % _('This workflow is used for the following forms:')
|
||||
', '.join(['<a href="../../forms/%s/">%s</a>' % (x.id, x.name) for x in formdefs])
|
||||
'</p>'
|
||||
'</div>'
|
||||
|
||||
get_response().filter['sidebar'] = self.get_sidebar()
|
||||
|
||||
def get_sidebar [html] (self):
|
||||
|
|
|
@ -29,6 +29,11 @@ def rebuild_vhost_indexes(destroy=False):
|
|||
if destroy:
|
||||
User.destroy_indexes()
|
||||
User.rebuild_indexes()
|
||||
for formdef in FormDef.select():
|
||||
formdata = formdef.data_class()
|
||||
if destroy:
|
||||
formdata.destroy_indexes()
|
||||
formdata.rebuild_indexes()
|
||||
|
||||
|
||||
class CmdRebuildIndexes(Command):
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
# w.c.s. - web application for online forms
|
||||
# Copyright (C) 2005-2013 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/>.
|
||||
|
||||
import os.path
|
||||
import runpy
|
||||
import sys
|
||||
|
||||
from qommon.ctl import Command, make_option
|
||||
|
||||
class CmdRunScript(Command):
|
||||
'''Run a script within a given host publisher context'''
|
||||
|
||||
name = 'runscript'
|
||||
|
||||
def __init__(self):
|
||||
Command.__init__(self, [
|
||||
make_option('--vhost', metavar='VHOST', action='store',
|
||||
dest='vhost'),
|
||||
])
|
||||
|
||||
def execute(self, base_options, sub_options, args):
|
||||
import publisher
|
||||
self.config.remove_option('main', 'error_log')
|
||||
publisher.WcsPublisher.configure(self.config, sub_options.extra)
|
||||
publisher = publisher.WcsPublisher.create_publisher()
|
||||
publisher.app_dir = os.path.join(publisher.app_dir, sub_options.vhost)
|
||||
publisher.set_config()
|
||||
fullpath = os.path.dirname(os.path.abspath(args[0]))
|
||||
sys.path.append(fullpath)
|
||||
module_name = os.path.splitext(os.path.basename(args[0]))[0]
|
||||
runpy.run_module(module_name)
|
||||
|
||||
CmdRunScript.register()
|
|
@ -15,6 +15,7 @@
|
|||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
import re
|
||||
|
||||
try:
|
||||
import elementtree.ElementTree as ET
|
||||
|
@ -359,13 +360,19 @@ class CommentField(Field):
|
|||
extra_css_class = ' class="%s"' % self.extra_css_class
|
||||
else:
|
||||
extra_css_class = ''
|
||||
if '<p' in self.label:
|
||||
|
||||
if '\n\n' in self.label:
|
||||
label = '<p>' + re.sub('\n\n+', '</p>\n<p>', self.label) + '</p>'
|
||||
else:
|
||||
label = self.label
|
||||
|
||||
if '<p' in label:
|
||||
enclosing_tag = 'div'
|
||||
else:
|
||||
enclosing_tag = 'p'
|
||||
form.widgets.append(HtmlWidget(
|
||||
htmltext('<%s%s>%s</%s>' % (enclosing_tag, extra_css_class,
|
||||
self.label, enclosing_tag))))
|
||||
label, enclosing_tag))))
|
||||
|
||||
def add_to_view_form(self, *args):
|
||||
pass
|
||||
|
@ -684,7 +691,11 @@ class ItemField(WidgetField):
|
|||
kwargs['options'] = [(None, '---')]
|
||||
if self.show_as_radio:
|
||||
self.widget_class = RadiobuttonsWidget
|
||||
if len(kwargs['options']) > 3 or len(kwargs['options'][0][1]) > 10:
|
||||
if type(kwargs['options'][0]) is str:
|
||||
first_item = kwargs['options'][0]
|
||||
else:
|
||||
first_item = kwargs['options'][0][1]
|
||||
if len(kwargs['options']) > 3 or len(first_item) > 10:
|
||||
# TODO: absence/presence of delimitor should be an option
|
||||
kwargs['delim'] = htmltext('<br />')
|
||||
|
||||
|
|
|
@ -115,6 +115,11 @@ class FormData(StorableObject):
|
|||
if self.status and not self.status.startswith('wf-'):
|
||||
self.status = 'wf-%s' % self.status
|
||||
changed = True
|
||||
if self.evolution:
|
||||
for evo in self.evolution:
|
||||
if evo.status and not evo.status.startswith('wf-'):
|
||||
evo.status = 'wf-%s' % evo.status
|
||||
changed = True
|
||||
if changed:
|
||||
self.store()
|
||||
|
||||
|
@ -163,6 +168,8 @@ class FormData(StorableObject):
|
|||
|
||||
def display_workflow_message(self):
|
||||
wf_status = self.get_workflow_status()
|
||||
if not wf_status:
|
||||
return ''
|
||||
for status in wf_status.items:
|
||||
if hasattr(status, 'get_message'):
|
||||
return status.get_message(self)
|
||||
|
@ -204,10 +211,14 @@ class FormData(StorableObject):
|
|||
|
||||
def get_workflow_form(self, user):
|
||||
wf_status = self.get_workflow_status()
|
||||
if not wf_status:
|
||||
return None
|
||||
return wf_status.get_action_form(self, user)
|
||||
|
||||
def get_workflow_subdirectories(self):
|
||||
wf_status = self.get_workflow_status()
|
||||
if not wf_status: # draft
|
||||
return []
|
||||
return wf_status.get_subdirectories(self)
|
||||
|
||||
def handle_workflow_form(self, user, form):
|
||||
|
@ -275,7 +286,7 @@ class FormData(StorableObject):
|
|||
if evolution.status and evolution.status != self.status:
|
||||
d['form_previous_status'] = self.get_status_label(evolution.status)
|
||||
break
|
||||
if d['form_status'] == d['form_previous_status']:
|
||||
if d['form_status'] != d['form_previous_status']:
|
||||
d['form_status_changed'] = True
|
||||
|
||||
d['form_evolution'] = self.formdef.get_detailed_evolution(self)
|
||||
|
@ -284,8 +295,9 @@ class FormData(StorableObject):
|
|||
|
||||
if self.formdef.workflow and self.status:
|
||||
wf_status = self.get_workflow_status()
|
||||
for item in wf_status.items:
|
||||
d.update(item.get_substitution_variables(self))
|
||||
if wf_status:
|
||||
for item in wf_status.items:
|
||||
d.update(item.get_substitution_variables(self))
|
||||
|
||||
return d
|
||||
|
||||
|
@ -309,8 +321,11 @@ class FormData(StorableObject):
|
|||
def is_submitter(self, user):
|
||||
if self.user_id and str(self.user_id) == str(user.id):
|
||||
return True
|
||||
if self.user_hash and self.user_hash == user.hash:
|
||||
return True
|
||||
try:
|
||||
if self.user_hash and self.user_hash == user.hash:
|
||||
return True
|
||||
except AttributeError:
|
||||
return False
|
||||
return False
|
||||
|
||||
# don't pickle _formdef cache
|
||||
|
|
|
@ -103,7 +103,7 @@ class FormDef(StorableObject):
|
|||
except ValueError:
|
||||
self.url_name = self.id
|
||||
else:
|
||||
self.url_name = simplify(self.name)
|
||||
self.url_name = self.get_new_url_name()
|
||||
changed = True
|
||||
|
||||
if self.fields and type(self.fields[0]) is dict:
|
||||
|
@ -153,8 +153,23 @@ class FormDef(StorableObject):
|
|||
setattr(sys.modules['wcs.formdef'], self.url_name.title(), cls)
|
||||
return cls
|
||||
|
||||
def store(self):
|
||||
def get_new_url_name(self):
|
||||
new_url_name = simplify(self.name)
|
||||
base_new_url_name = new_url_name
|
||||
suffix_no = 0
|
||||
while True:
|
||||
try:
|
||||
formdef = self.get_by_urlname(new_url_name, ignore_migration=True)
|
||||
except KeyError:
|
||||
break
|
||||
if formdef.id == self.id:
|
||||
break
|
||||
suffix_no += 1
|
||||
new_url_name = '%s-%s' % (base_new_url_name, suffix_no)
|
||||
return new_url_name
|
||||
|
||||
def store(self):
|
||||
new_url_name = self.get_new_url_name()
|
||||
if not self.url_name:
|
||||
self.url_name = new_url_name
|
||||
if get_publisher().has_site_option('postgresql') and get_cfg('postgresql', {}):
|
||||
|
@ -233,8 +248,8 @@ class FormDef(StorableObject):
|
|||
self.workflow_id = None
|
||||
workflow = property(get_workflow, set_workflow)
|
||||
|
||||
def get_by_urlname(cls, url_name):
|
||||
return cls.get_on_index(url_name, 'url_name')
|
||||
def get_by_urlname(cls, url_name, ignore_migration=False):
|
||||
return cls.get_on_index(url_name, 'url_name', ignore_migration=ignore_migration)
|
||||
get_by_urlname = classmethod(get_by_urlname)
|
||||
|
||||
def get_url(self, backoffice = False):
|
||||
|
@ -650,7 +665,7 @@ EmailsDirectory.register('new_receiver', N_('Notification of creation to receive
|
|||
Hello,
|
||||
|
||||
A new form has been submitted, you can see it with this link:
|
||||
[url]
|
||||
[form_url_backoffice]
|
||||
|
||||
[if-any details]
|
||||
For reference, here are the details:
|
||||
|
@ -670,7 +685,7 @@ Hello,
|
|||
|
||||
A form just changed, you can consult it with this link:
|
||||
|
||||
[url]
|
||||
[form_url_backoffice]
|
||||
|
||||
[if-any form_comment]New comment: [form_comment][end]
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ class FormStatusPage(Directory):
|
|||
for evo in self.filled.evolution:
|
||||
if evo.status:
|
||||
wf_status = self.filled.get_status(evo.status)
|
||||
if not wf_status.is_visible(self.filled, get_request().user):
|
||||
if wf_status and not wf_status.is_visible(self.filled, get_request().user):
|
||||
hidden = True
|
||||
else:
|
||||
hidden = False
|
||||
|
|
|
@ -250,7 +250,7 @@ class FormPage(Directory):
|
|||
def get_namespaces(self):
|
||||
namespaces = {
|
||||
'pp': getattr(lasso, 'PP11_HREF', getattr(lasso, 'PP_HREF', None)), # support old lasso
|
||||
'ep': lasso.EP_HREF,
|
||||
'ep': getattr(lasso, 'EP_HREF', ''),
|
||||
'eoa': 'urn:entrouvert:id-sis-adeline:2005-09', # eo schema for adeline
|
||||
'cafp': 'urn:entrouvert:id-sis-caf-profile:2005-09', # eo schema for caf
|
||||
}
|
||||
|
@ -393,21 +393,17 @@ class FormPage(Directory):
|
|||
form.submit_widgets.remove(form._names['prefill'])
|
||||
del form._names['prefill']
|
||||
|
||||
if not editing:
|
||||
if page_no == 0 and not get_request().form.has_key('magictoken'):
|
||||
magictoken = randbytes(8)
|
||||
else:
|
||||
magictoken = get_request().form['magictoken']
|
||||
form.add_hidden('magictoken', magictoken)
|
||||
|
||||
form.add_submit('submit', _('Next'))
|
||||
data = session.get_by_magictoken(magictoken, {})
|
||||
if page_no == 0 and not get_request().form.has_key('magictoken'):
|
||||
magictoken = randbytes(8)
|
||||
else:
|
||||
if page_no == self.page_number - 1:
|
||||
form.add_submit('submit', _('Save Changes'))
|
||||
else:
|
||||
form.add_submit('submit', _('Next'))
|
||||
data = editing.data
|
||||
magictoken = get_request().form['magictoken']
|
||||
form.add_hidden('magictoken', magictoken)
|
||||
data = session.get_by_magictoken(magictoken, {})
|
||||
|
||||
if editing and page_no == self.page_number - 1:
|
||||
form.add_submit('submit', _('Save Changes'))
|
||||
else:
|
||||
form.add_submit('submit', _('Next'))
|
||||
|
||||
if page_no > 0:
|
||||
form.add_submit('previous', _('Previous'))
|
||||
|
@ -511,6 +507,9 @@ class FormPage(Directory):
|
|||
if editing:
|
||||
existing_formdata = editing.data
|
||||
if not get_request().form:
|
||||
# on the initial visit editing the form (i.e. not after
|
||||
# clicking for previous or next page), we need to load the
|
||||
# existing data into the session
|
||||
token = randbytes(8)
|
||||
get_request().form['magictoken'] = token
|
||||
session.add_magictoken(token, editing.data)
|
||||
|
@ -974,11 +973,15 @@ class FormPage(Directory):
|
|||
raise errors.AccessForbiddenError()
|
||||
get_response().breadcrumb.append( ('listing', _('Listing')) )
|
||||
html_top('%s - %s' % (_('Listing'), self.formdef.name))
|
||||
if self.user and (self.user.is_admin or self.formdef.receiver_id in (self.user.roles or [])):
|
||||
FormDefUI(self.formdef).listing('status', include_form = True)
|
||||
else:
|
||||
FormDefUI(self.formdef).listing(include_form = True)
|
||||
'<a href="..">%s</a>' % _('Back')
|
||||
|
||||
fields = []
|
||||
for field in self.formdef.fields:
|
||||
if hasattr(field, str('get_view_value')) and field.in_listing:
|
||||
fields.append(field)
|
||||
|
||||
FormDefUI(self.formdef).listing(fields, 'all', include_form=True)
|
||||
|
||||
'<p><a href="..">%s</a></p>' % _('Back')
|
||||
|
||||
def _q_lookup(self, component):
|
||||
try:
|
||||
|
|
|
@ -18,9 +18,9 @@ import os
|
|||
import signal
|
||||
import time
|
||||
try:
|
||||
import dl
|
||||
import prctl
|
||||
except ImportError:
|
||||
dl = None
|
||||
prctl = None
|
||||
import sys
|
||||
|
||||
parent_killed = False
|
||||
|
@ -88,17 +88,10 @@ def spawn_cron(create_publisher):
|
|||
cron_pid = os.fork()
|
||||
if cron_pid == 0:
|
||||
pub = create_publisher()
|
||||
# set process name, this only works on Linux, 15 == PR_SET_NAME
|
||||
if dl:
|
||||
try:
|
||||
libc = dl.open('/lib/libc.so.6')
|
||||
except dl.error:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
libc.call('prctl', 15, os.path.basename(sys.argv[0]) + ' [cron]\0', 0, 0, 0)
|
||||
except ValueError: # missing symbol
|
||||
pass
|
||||
# set process name and title (this only works on Linux)
|
||||
if prctl:
|
||||
prctl.set_name(os.path.basename(sys.argv[0]) + ' [cron]')
|
||||
prctl.set_proctitle(os.path.basename(sys.argv[0]) + ' [cron]')
|
||||
signal.signal(signal.SIGTERM, stop_cron_process)
|
||||
cron(pub)
|
||||
sys.exit(0)
|
||||
|
|
|
@ -84,6 +84,16 @@ def is_prefilled(self):
|
|||
else:
|
||||
return False
|
||||
|
||||
def render_title(self, title):
|
||||
if title:
|
||||
if self.required:
|
||||
title += htmltext('<span class="required">*</span>')
|
||||
return htmltext('<div class="title"><label for="form_%s">%s</label></div>') % (
|
||||
self.name, title)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def render(self):
|
||||
# quixote/form/widget.py, Widget::render
|
||||
r = TemplateIO(html=True)
|
||||
|
@ -121,8 +131,65 @@ Widget.cleanup = None
|
|||
Widget.set_message = set_message
|
||||
Widget.get_message = get_message
|
||||
Widget.render_message = render_message
|
||||
Widget.render_title = render_title
|
||||
Widget.is_prefilled = is_prefilled
|
||||
|
||||
def string_render_content(self):
|
||||
attrs = {'id': 'form_' + self.name}
|
||||
if self.attrs:
|
||||
attrs.update(self.attrs)
|
||||
return htmltag("input", xml_end=True, type=self.HTML_TYPE, name=self.name,
|
||||
value=self.value, **attrs)
|
||||
StringWidget.render_content = string_render_content
|
||||
|
||||
|
||||
def file_render_content(self):
|
||||
attrs = {'id': 'form_' + str(self.name).split('$')[0]}
|
||||
if self.attrs:
|
||||
attrs.update(self.attrs)
|
||||
return htmltag("input", xml_end=True, type=self.HTML_TYPE, name=self.name,
|
||||
value=self.value, **attrs)
|
||||
FileWidget.render_content = file_render_content
|
||||
|
||||
|
||||
def text_render_content(self):
|
||||
attrs = {'id': 'form_' + self.name}
|
||||
if self.attrs:
|
||||
attrs.update(self.attrs)
|
||||
return (htmltag("textarea", name=self.name, **attrs) +
|
||||
htmlescape(self.value or "") + htmltext("</textarea>"))
|
||||
TextWidget.render_content = text_render_content
|
||||
|
||||
|
||||
def checkbox_render_content(self):
|
||||
attrs = {'id': 'form_' + self.name}
|
||||
if self.attrs:
|
||||
attrs.update(self.attrs)
|
||||
return htmltag("input", xml_end=True, type="checkbox", name=self.name,
|
||||
value="yes", checked=self.value and "checked" or None,
|
||||
**attrs)
|
||||
CheckboxWidget.render_content = checkbox_render_content
|
||||
|
||||
|
||||
def select_render_content(self):
|
||||
attrs = {'id': 'form_' + self.name}
|
||||
if self.attrs:
|
||||
attrs.update(self.attrs)
|
||||
tags = [htmltag("select", name=self.name, **attrs)]
|
||||
for object, description, key in self.options:
|
||||
if self.is_selected(object):
|
||||
selected = 'selected'
|
||||
else:
|
||||
selected = None
|
||||
if description is None:
|
||||
description = ""
|
||||
r = htmltag("option", value=key, selected=selected)
|
||||
tags.append(r + htmlescape(description) + htmltext('</option>'))
|
||||
tags.append(htmltext("</select>"))
|
||||
return htmltext("\n").join(tags)
|
||||
SelectWidget.render_content = select_render_content
|
||||
|
||||
|
||||
class Form(QuixoteForm):
|
||||
TOKEN_NOTICE = N_(
|
||||
"The form you have submitted is invalid. Most "
|
||||
|
@ -683,7 +750,7 @@ $.dpText = {
|
|||
prepare_javascript = classmethod(prepare_javascript)
|
||||
|
||||
def render_content(self):
|
||||
self.attrs['id'] = 'id_%s' % self.name
|
||||
self.attrs['id'] = 'form_%s' % self.name
|
||||
self.attrs['class'] = 'date-pick'
|
||||
|
||||
if self.attrs.get('readonly') or not self.jquery:
|
||||
|
@ -1167,9 +1234,12 @@ class SingleSelectHintWidget(SingleSelectWidget):
|
|||
return (self.hint and len(self.hint) > 80)
|
||||
|
||||
def render_content(self):
|
||||
attrs = {'id': 'form_' + self.name}
|
||||
if self.attrs:
|
||||
attrs.update(self.attrs)
|
||||
if self.separate_hint():
|
||||
return SingleSelectWidget.render_content(self)
|
||||
tags = [htmltag('select', name=self.name, **self.attrs)]
|
||||
tags = [htmltag('select', name=self.name, **attrs)]
|
||||
if self.hint:
|
||||
r = htmltag('option', value='', selected=None)
|
||||
tags.append(r + htmlescape(self.hint) + htmltext('</option>'))
|
||||
|
|
|
@ -206,14 +206,18 @@ class MethodDirectory(Directory):
|
|||
username = form.get_widget('username').parse()
|
||||
password = form.get_widget('password').parse()
|
||||
|
||||
get_publisher().statsd.increment('login-submit')
|
||||
|
||||
try:
|
||||
user = PasswordAccount.get_with_credentials(username, password)
|
||||
except:
|
||||
form.set_error('username', _('Invalid credentials'))
|
||||
get_publisher().statsd.increment('login-error')
|
||||
return
|
||||
|
||||
account = PasswordAccount.get(username)
|
||||
get_publisher().statsd.increment('login-success')
|
||||
|
||||
account = PasswordAccount.get(username)
|
||||
return self.login_submit_account_user(account, user, form)
|
||||
|
||||
def login_submit_account_user(self, account, user, form=None):
|
||||
|
@ -1570,7 +1574,10 @@ def handle_unused_accounts(publisher):
|
|||
account.store()
|
||||
|
||||
if (time.time() - user.last_seen) > 86400*remove_unused_account_delay:
|
||||
account = PasswordAccount.get_on_index(user.id, 'user_id')
|
||||
try:
|
||||
account = PasswordAccount.get_on_index(user.id, 'user_id')
|
||||
except KeyError:
|
||||
continue
|
||||
user.remove_self()
|
||||
account.remove_self()
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@ class QommonPublisher(Publisher):
|
|||
missing_appdir_redirect = None
|
||||
use_sms_feature = True
|
||||
app_translations = dict()
|
||||
statsd = None
|
||||
|
||||
def get_root_url(self):
|
||||
if self.get_request():
|
||||
|
@ -240,12 +241,19 @@ class QommonPublisher(Publisher):
|
|||
# this could happen on file descriptor exhaustion
|
||||
pass
|
||||
|
||||
def finish_successful_request(self):
|
||||
Publisher.finish_successful_request(self)
|
||||
self.statsd.increment('successful-request')
|
||||
|
||||
def finish_failed_request(self):
|
||||
# duplicate at lot from parent class, just to use our own HTTPResponse
|
||||
request = get_request()
|
||||
original_response = request.response
|
||||
request.response = HTTPResponse()
|
||||
|
||||
if self.statsd: # maybe unset if very early failure
|
||||
self.statsd.increment('failed-request')
|
||||
|
||||
(exc_type, exc_value, tb) = sys.exc_info()
|
||||
|
||||
if exc_type is NotImplementedError:
|
||||
|
@ -452,6 +460,13 @@ class QommonPublisher(Publisher):
|
|||
except ImmediateRedirectException, e:
|
||||
return redirect(e.location)
|
||||
|
||||
from vendor import pystatsd
|
||||
self.statsd = pystatsd.Client(
|
||||
host=request.get_environ('QOMMON_STATSD_HOSTNAME', 'localhost'),
|
||||
prefix='%s.%s' % (
|
||||
self.APP_NAME,
|
||||
os.path.split(self.app_dir)[-1].replace('+', '-').replace('.', '-')))
|
||||
|
||||
self.initialize_app_dir()
|
||||
|
||||
canonical_hostname = request.get_server(False).lower().split(':')[0].rstrip('.')
|
||||
|
@ -809,7 +824,7 @@ class QommonPublisher(Publisher):
|
|||
'site_theme': get_cfg('branding', {}).get('theme', self.default_theme),
|
||||
'site_url': self.get_frontoffice_url(),
|
||||
'site_url_backoffice': self.get_backoffice_url(),
|
||||
'site_lang': get_request().language or 'en',
|
||||
'site_lang': (get_request() and get_request().language) or 'en',
|
||||
'today': strftime.strftime(misc.date_format(), time.localtime()),
|
||||
'now': misc.localstrftime(time.localtime()),
|
||||
}
|
||||
|
|
|
@ -128,11 +128,15 @@ class OxydSMS:
|
|||
elif number.startswith('0'):
|
||||
# local prefix, remove 0 and add default country code
|
||||
number = self.default_country_code + number[1:]
|
||||
params = urllib.urlencode({
|
||||
'id': self.user,
|
||||
'pass': self.password,
|
||||
'num': number,
|
||||
'sms': text,
|
||||
'flash': '0'
|
||||
})
|
||||
try:
|
||||
r = urllib2.urlopen('http://sms.oxyd.fr/send.php?id=%s&pass=%s&num=%s&sms=%s&flash=0' % (
|
||||
self.user, self.password,
|
||||
number,
|
||||
text))
|
||||
r = urllib2.urlopen('http://sms.oxyd.fr/send.php', params)
|
||||
except Exception, e:
|
||||
# XXX: add proper handling of errors
|
||||
raise errors.SMSError('urlopen oxyd.fr failed : %s' % e)
|
||||
|
|
|
@ -475,6 +475,7 @@ div#main-content {
|
|||
float: left;
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
div#main-content h2 {
|
||||
|
|
|
@ -316,3 +316,16 @@ div.buttons {
|
|||
clear: both;
|
||||
}
|
||||
|
||||
div.note {
|
||||
padding: 6px;
|
||||
border: solid 1px #e5e5e3;
|
||||
background: #f3f3f0 none 5px 15px no-repeat;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 3px #888;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
div.icon-important { padding-left: 34px; background-image: url(../images/yelp-note-important.png); }
|
||||
div.icon-tip { padding-left: 34px; background-image: url(../images/yelp-note-tip.png); }
|
||||
div.icon-warning { padding-left: 34px; background-image: url(../images/yelp-note-warning.png); }
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 838 B |
|
@ -7,7 +7,6 @@ function updateStatus()
|
|||
$(this).load(QOMMON_ROOT_URL + 'afterjobs/' + $(this).attr('id'), null,
|
||||
function () {
|
||||
var current_text = $(this).text();
|
||||
console.log('current text:', current_text);
|
||||
if (current_text.indexOf('completed') == 0) {
|
||||
$(this).text(current_text.substr(10, current_text.length));
|
||||
$(this).addClass('activity-done');
|
||||
|
@ -18,7 +17,6 @@ function updateStatus()
|
|||
$(this).text(current_text.substr(7, current_text.length));
|
||||
$(this).addClass('activity-done');
|
||||
} else {
|
||||
console.log('hello');
|
||||
$(this).text(current_text.split('|')[1]);
|
||||
window.setTimeout(updateStatus, 2500);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ $(document).ready(
|
|||
for (i=0; i < items.length; i++) {
|
||||
item = items[i];
|
||||
// 6 is length of 'itemId'
|
||||
result += item.id.substr(7, 9) + ';';
|
||||
result += item.id.substr(7, 30) + ';';
|
||||
}
|
||||
$.post('update_order', {'order': result},
|
||||
function(data) { $('ul.biglist').Highlight(500, '#ff8'); });
|
||||
|
|
|
@ -18,7 +18,6 @@ import os
|
|||
import time
|
||||
import pickle
|
||||
import os.path
|
||||
import random
|
||||
import shutil
|
||||
import tempfile
|
||||
try:
|
||||
|
@ -52,7 +51,9 @@ def atomic_write(path, content, async=False):
|
|||
'''Rewrite a complete file automatically, that is write to new file with
|
||||
temporary name, fsync, then rename to final name. Use threads to remove blocking.'''
|
||||
def doit():
|
||||
fd, temp = tempfile.mkstemp(dir=os.path.dirname(path), prefix=os.path.basename(path))
|
||||
dirname = os.path.dirname(path)
|
||||
fd, temp = tempfile.mkstemp(dir=dirname,
|
||||
prefix='.tmp-'+os.path.basename(path)+'-')
|
||||
f = os.fdopen(fd, "w")
|
||||
if hasattr(content, 'read'):
|
||||
# file pointer
|
||||
|
@ -399,7 +400,7 @@ class StorableObject(object):
|
|||
if relative_object_filename:
|
||||
os.symlink(relative_object_filename, link_name)
|
||||
else:
|
||||
os.symlink(object_filename, link_name)
|
||||
os.symlink(self.get_object_filename(), link_name)
|
||||
except OSError, exc:
|
||||
if exc.errno == 2:
|
||||
os.mkdir(index_dir)
|
||||
|
@ -416,7 +417,7 @@ class StorableObject(object):
|
|||
if relative_object_filename:
|
||||
os.symlink(relative_object_filename, link_name)
|
||||
else:
|
||||
os.symlink(object_filename, link_name)
|
||||
os.symlink(self.get_object_filename(), link_name)
|
||||
|
||||
for index in self._hashed_indexes or []:
|
||||
index_dir = os.path.join(objects_dir, '.indexes', index)
|
||||
|
|
|
@ -159,6 +159,9 @@ ADMIN_TEMPLATE_EZT = """<!DOCTYPE html>
|
|||
<head>
|
||||
<title>[page_title]</title>
|
||||
<link rel="stylesheet" type="text/css" href="[css]"/>
|
||||
[if-any extra_css]
|
||||
<link rel="stylesheet" type="text/css" href="[extra_css]"/>
|
||||
[end]
|
||||
[script]
|
||||
</head>
|
||||
<body>
|
||||
|
@ -339,6 +342,15 @@ def decorate(body, response):
|
|||
css = root_url + get_publisher().qommon_static_dir + get_publisher().qommon_admin_css
|
||||
generate_breadcrumb = True
|
||||
template = admin_template
|
||||
|
||||
app_dir_filename = os.path.join(get_publisher().app_dir, 'themes',
|
||||
current_theme, 'admin.css')
|
||||
data_dir_filename = os.path.join(get_publisher().data_dir, 'themes',
|
||||
current_theme, 'admin.css')
|
||||
for filename in (app_dir_filename, data_dir_filename):
|
||||
if os.path.exists(filename):
|
||||
extra_css = root_url + 'themes/%s/admin.css' % current_theme
|
||||
break
|
||||
else:
|
||||
if current_theme == 'default':
|
||||
css = root_url + 'css/%s.css' % get_publisher().APP_NAME
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
# w.c.s. - web application for online forms
|
||||
# Copyright (C) 2005-2012 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/>.
|
||||
#
|
||||
#
|
||||
# statsd.py - Python implementation of the statsd client
|
||||
#
|
||||
# Initially released (as License :: OSI Approved :: BSD License, from setup.py)
|
||||
# by Steve Ivy <steveivy@gmail.com> - <https://github.com/sivy/py-statsd>
|
||||
|
||||
import random
|
||||
import socket
|
||||
import time
|
||||
|
||||
|
||||
# Sends statistics to the stats daemon over UDP
|
||||
class Client(object):
|
||||
|
||||
def __init__(self, host='localhost', port=8125, prefix=None):
|
||||
"""
|
||||
Create a new Statsd client.
|
||||
* host: the host where statsd is listening, defaults to localhost
|
||||
* port: the port where statsd is listening, defaults to 8125
|
||||
|
||||
>>> from pystatsd import statsd
|
||||
>>> stats_client = statsd.Statsd(host, port)
|
||||
"""
|
||||
self.addr = (socket.gethostbyname(host), port)
|
||||
self.prefix = prefix
|
||||
self.udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
||||
def timing_since(self, stat, start, sample_rate=1):
|
||||
"""
|
||||
Log timing information as the number of microseconds since the provided time float
|
||||
>>> start = time.time()
|
||||
>>> # do stuff
|
||||
>>> statsd_client.timing_since('some.time', start)
|
||||
"""
|
||||
self.timing(stat, int((time.time() - start) * 1000000), sample_rate)
|
||||
|
||||
def timing(self, stat, time, sample_rate=1):
|
||||
"""
|
||||
Log timing information for a single stat
|
||||
>>> statsd_client.timing('some.time',500)
|
||||
"""
|
||||
stats = {stat: "%f|ms" % time}
|
||||
self.send(stats, sample_rate)
|
||||
|
||||
def gauge(self, stat, value, sample_rate=1):
|
||||
"""
|
||||
Log gauge information for a single stat
|
||||
>>> statsd_client.gauge('some.gauge',42)
|
||||
"""
|
||||
stats = {stat: "%f|g" % value}
|
||||
self.send(stats, sample_rate)
|
||||
|
||||
def increment(self, stats, sample_rate=1):
|
||||
"""
|
||||
Increments one or more stats counters
|
||||
>>> statsd_client.increment('some.int')
|
||||
>>> statsd_client.increment('some.int',0.5)
|
||||
"""
|
||||
self.update_stats(stats, 1, sample_rate=sample_rate)
|
||||
|
||||
def decrement(self, stats, sample_rate=1):
|
||||
"""
|
||||
Decrements one or more stats counters
|
||||
>>> statsd_client.decrement('some.int')
|
||||
"""
|
||||
self.update_stats(stats, -1, sample_rate=sample_rate)
|
||||
|
||||
def update_stats(self, stats, delta, sample_rate=1):
|
||||
"""
|
||||
Updates one or more stats counters by arbitrary amounts
|
||||
>>> statsd_client.update_stats('some.int',10)
|
||||
"""
|
||||
if not isinstance(stats, list):
|
||||
stats = [stats]
|
||||
|
||||
data = dict((stat, "%s|c" % delta) for stat in stats)
|
||||
self.send(data, sample_rate)
|
||||
|
||||
def send(self, data, sample_rate=1):
|
||||
"""
|
||||
Squirt the metrics over UDP
|
||||
"""
|
||||
|
||||
if self.prefix:
|
||||
data = dict((".".join((self.prefix, stat)), value) for stat, value in data.iteritems())
|
||||
|
||||
if sample_rate < 1:
|
||||
if random.random() > sample_rate:
|
||||
return
|
||||
sampled_data = dict((stat, "%s|@%s" % (value, sample_rate)) for stat, value in data.iteritems())
|
||||
else:
|
||||
sampled_data = data
|
||||
|
||||
try:
|
||||
[self.udp_sock.sendto("%s:%s" % (stat, value), self.addr) for stat, value in sampled_data.iteritems()]
|
||||
except:
|
||||
pass
|
|
@ -47,7 +47,7 @@ class TimeoutWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
'<br/><span class="warning">Minimal duration is %(granularity)s</span>')) % {
|
||||
'variables': ','.join(timewords()),
|
||||
'granularity': seconds2humanduration(self._granularity)}
|
||||
form.add(StringWidget, 'timeout', title=_('Timeout'),
|
||||
form.add(StringWidget, '%stimeout' % prefix, title=_('Timeout'),
|
||||
value=seconds2humanduration(self.timeout), hint=_hint)
|
||||
|
||||
def timeout_parse(self, value):
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
from qommon import ezt
|
||||
from cStringIO import StringIO
|
||||
import copy
|
||||
|
||||
try:
|
||||
import elementtree.ElementTree as ET
|
||||
|
@ -133,9 +134,12 @@ class Workflow(StorableObject):
|
|||
for status in self.possible_status:
|
||||
waitpoint = False
|
||||
endpoint = True
|
||||
for item in status.items:
|
||||
endpoint = item.endpoint and endpoint
|
||||
waitpoint = item.waitpoint or waitpoint
|
||||
if status.forced_endpoint:
|
||||
endpoint = True
|
||||
else:
|
||||
for item in status.items:
|
||||
endpoint = item.endpoint and endpoint
|
||||
waitpoint = item.waitpoint or waitpoint
|
||||
if endpoint or waitpoint:
|
||||
waitpoint_status.append(status)
|
||||
return waitpoint_status
|
||||
|
@ -148,6 +152,8 @@ class Workflow(StorableObject):
|
|||
def get_not_endpoint_status(self):
|
||||
not_endpoint_status = []
|
||||
for status in self.possible_status:
|
||||
if status.forced_endpoint:
|
||||
continue
|
||||
endpoint = True
|
||||
for item in status.items:
|
||||
endpoint = item.endpoint and endpoint
|
||||
|
@ -217,13 +223,20 @@ class Workflow(StorableObject):
|
|||
just_submitted_status = workflow.add_status(_('Just Submitted'), 'just_submitted')
|
||||
just_submitted_status.visibility = ['_receiver']
|
||||
new_status = workflow.add_status(_('New'), 'new')
|
||||
handled_status = workflow.add_status(_('Handled'), 'handled')
|
||||
rejected_status = workflow.add_status(_('Rejected'), 'rejected')
|
||||
accepted_status = workflow.add_status(_('Accepted'), 'accepted')
|
||||
finished_status = workflow.add_status(_('Finished'), 'finished')
|
||||
|
||||
commentable = CommentableWorkflowStatusItem()
|
||||
commentable.id = '_commentable'
|
||||
commentable.by = ['_submitter', '_receiver']
|
||||
commentable.by = ['_receiver']
|
||||
commentable.button_label = _('Add Comment')
|
||||
|
||||
commentable_all = CommentableWorkflowStatusItem()
|
||||
commentable_all.id = '_commentable_all'
|
||||
commentable_all.by = ['_submitter', '_receiver']
|
||||
commentable_all.button_label = _('Add Comment')
|
||||
|
||||
import wf.jump
|
||||
jump_to_new = wf.jump.JumpWorkflowStatusItem()
|
||||
|
@ -231,6 +244,11 @@ class Workflow(StorableObject):
|
|||
jump_to_new.status = new_status.id
|
||||
jump_to_new.parent = just_submitted_status
|
||||
|
||||
jump_to_handled = JumpOnSubmitWorkflowStatusItem()
|
||||
jump_to_handled.id = '_jump_to_handled'
|
||||
jump_to_handled.status = handled_status.id
|
||||
jump_to_handled.parent = new_status
|
||||
|
||||
notify_new_receiver_email = SendmailWorkflowStatusItem()
|
||||
notify_new_receiver_email.id = '_notify_new_receiver_email'
|
||||
notify_new_receiver_email.to = ['_receiver']
|
||||
|
@ -264,23 +282,56 @@ class Workflow(StorableObject):
|
|||
notify_change_user_email = None
|
||||
|
||||
if notify_new_receiver_email:
|
||||
notify_new_receiver_email.parent = just_submitted_status
|
||||
just_submitted_status.items.append(notify_new_receiver_email)
|
||||
if notify_new_user_email:
|
||||
notify_new_user_email.parent = just_submitted_status
|
||||
just_submitted_status.items.append(notify_new_user_email)
|
||||
|
||||
just_submitted_status.items.append(jump_to_new)
|
||||
|
||||
if notify_change_receiver_email:
|
||||
accepted_status.items.append(notify_change_receiver_email)
|
||||
notify_change_receiver_email.parent = accepted_status
|
||||
notify_change_receiver_email = copy.copy(notify_change_receiver_email)
|
||||
|
||||
handled_status.items.append(notify_change_receiver_email)
|
||||
notify_change_receiver_email.parent = handled_status
|
||||
notify_change_receiver_email = copy.copy(notify_change_receiver_email)
|
||||
|
||||
rejected_status.items.append(notify_change_receiver_email)
|
||||
notify_change_receiver_email.parent = rejected_status
|
||||
notify_change_receiver_email = copy.copy(notify_change_receiver_email)
|
||||
|
||||
finished_status.items.append(notify_change_receiver_email)
|
||||
notify_change_receiver_email.parent = finished_status
|
||||
|
||||
if notify_change_user_email:
|
||||
accepted_status.items.append(notify_change_user_email)
|
||||
notify_change_user_email.parent = accepted_status
|
||||
notify_change_user_email = copy.copy(notify_change_user_email)
|
||||
|
||||
handled_status.items.append(notify_change_user_email)
|
||||
notify_change_user_email.parent = handled_status
|
||||
notify_change_user_email = copy.copy(notify_change_user_email)
|
||||
|
||||
rejected_status.items.append(notify_change_user_email)
|
||||
notify_change_user_email.parent = rejected_status
|
||||
notify_change_user_email = copy.copy(notify_change_user_email)
|
||||
|
||||
finished_status.items.append(notify_change_user_email)
|
||||
notify_change_user_email.parent = finished_status
|
||||
|
||||
new_status.items.append(commentable)
|
||||
accepted_status.items.append(commentable)
|
||||
commentable.parent = new_status
|
||||
|
||||
commentable = copy.copy(commentable)
|
||||
accepted_status.items.append(commentable_all)
|
||||
commentable.parent = accepted_status
|
||||
|
||||
commentable = copy.copy(commentable)
|
||||
handled_status.items.append(commentable_all)
|
||||
commentable.parent = handled_status
|
||||
|
||||
accept = ChoiceWorkflowStatusItem()
|
||||
accept.id = '_accept'
|
||||
|
@ -289,6 +340,7 @@ class Workflow(StorableObject):
|
|||
accept.status = accepted_status.id
|
||||
accept.parent = new_status
|
||||
new_status.items.append(accept)
|
||||
handled_status.items.append(accept)
|
||||
|
||||
reject = ChoiceWorkflowStatusItem()
|
||||
reject.id = '_reject'
|
||||
|
@ -297,6 +349,9 @@ class Workflow(StorableObject):
|
|||
reject.status = rejected_status.id
|
||||
reject.parent = new_status
|
||||
new_status.items.append(reject)
|
||||
handled_status.items.append(reject)
|
||||
|
||||
new_status.items.append(jump_to_handled)
|
||||
|
||||
finish = ChoiceWorkflowStatusItem()
|
||||
finish.id = '_finish'
|
||||
|
@ -316,6 +371,7 @@ class WorkflowStatus:
|
|||
name = None
|
||||
items = None
|
||||
visibility = None
|
||||
forced_endpoint = False
|
||||
|
||||
def __init__(self, name = None):
|
||||
self.name = name
|
||||
|
@ -522,7 +578,10 @@ class WorkflowStatusItem:
|
|||
if not user:
|
||||
continue
|
||||
if role == '_submitter':
|
||||
return formdata.is_submitter(user)
|
||||
t = formdata.is_submitter(user)
|
||||
if t is True:
|
||||
return True
|
||||
continue
|
||||
role = get_role_translation(formdata.formdef, role)
|
||||
if role in (user.roles or []):
|
||||
return True
|
||||
|
@ -1034,8 +1093,9 @@ class SendSMSWorkflowStatusItem(WorkflowStatusItem):
|
|||
|
||||
sms_cfg = get_cfg('sms', {})
|
||||
sender = sms_cfg.get('sender', 'AuQuotidien')[:11]
|
||||
mode = sms_cfg.get('mode', 'none')
|
||||
try:
|
||||
SMS.get_sms_class().send([self.compute(x) for x in self.to], sms_body[:160], sender)
|
||||
SMS.get_sms_class(mode).send(sender, [self.compute(x) for x in self.to], sms_body[:160])
|
||||
except qommon.errors.SMSError, e:
|
||||
get_logger().error(e)
|
||||
|
||||
|
|
Loading…
Reference in New Issue