Compare commits

...

73 Commits

Author SHA1 Message Date
Thomas NOËL 1da88185d7 afterjob.js: remove debugging statement (fix #3150) 2013-06-26 09:55:56 +02:00
Frédéric Péters c5abb97846 js: remove debugging statement 2013-06-26 09:55:56 +02:00
Thomas NOËL 0ee69733cb debian/changelog: release 2012.11-17 2013-04-23 12:39:47 +02:00
Frédéric Péters 2d44e3e1c5 forms: fix edition of current formdata (#2789) 2013-04-23 09:20:06 +02:00
Thomas NOËL cf3ae32eae remove wcs.pot 2013-04-11 16:20:31 +02:00
Thomas NOËL d35cd08f13 debian/changelog: release 2012.11-16 2013-03-15 15:03:50 +01:00
Frédéric Péters 4367708e69 workflows: fix embedding of timeout options (#2596) 2013-03-15 15:01:01 +01:00
Thomas NOËL b975123670 debian/changelog: release 2012.11-15
... another "brown paper bag" commit
2013-03-15 14:59:01 +01:00
Thomas NOËL 4112517b51 debian/changelog: release 2012.11-14 2013-03-14 09:40:20 +01:00
Thomas NOËL 522bb1d368 biglist can handle long ids. fix #2580 2013-03-12 15:51:05 +01:00
Thomas NOËL 5c21b45e48 update french translations 2013-03-11 14:44:28 +01:00
Thomas NOËL fe97a1a543 debian/changelog: release 2012.11-12 2013-03-07 17:41:24 +01:00
Thomas NOËL ef97287773 french translation updates 2013-03-07 17:39:12 +01:00
Thomas NOËL f1c480f744 workflow/svg: correct encoding for textwrap (fix #2544) 2013-03-07 17:24:47 +01:00
Thomas NOËL e79b4e1f87 debian/changelog fixes 2013-03-07 17:20:33 +01:00
Thomas NOËL 42c92394d6 debian/changelog: release 2012.11-11 2013-03-07 13:47:50 +01:00
Thomas NOËL eb29fb0826 sms: separate setting and test pages 2013-03-05 20:07:45 +01:00
Thomas NOËL 55db0d34be debian/changelog: release 2012.11-10 2013-03-05 18:14:45 +01:00
Thomas NOËL 57801a6e7c fix sms sending 2013-03-05 15:38:45 +01:00
Thomas NOËL 8f4f8a255d debian/changelog: release 2012.11-9 2013-03-05 14:32:34 +01:00
Frédéric Péters 975aecb98e do not fail getting workflow subdirectories for draft formdata (#2436) 2013-03-05 14:18:05 +01:00
Frédéric Péters 9277880b87 display general form view on new form, not fields view 2013-03-05 14:17:48 +01:00
Frédéric Péters 1f136c1b13 fix bug in rendering file widget label (in case of htmltext) 2013-03-05 14:15:19 +01:00
Frédéric Péters 9a3a679048 form: add effective use of <label> in more places 2013-03-05 14:15:05 +01:00
Frédéric Péters a7b4f461b6 css: add note styles to default theme (#2265) 2013-03-05 14:12:45 +01:00
Frédéric Péters a935bb65a5 fields: automatically create paragraphs on blank lines in comment fields 2013-03-05 14:12:32 +01:00
Frédéric Péters 56371c4ccb admin: add possibility for a theme to provide a supplementary css (#2429) 2013-03-05 14:04:09 +01:00
Frédéric Péters 56dfd14339 ctl: add a runscript command, to run a given script within a publisher context 2013-03-05 13:58:38 +01:00
Frédéric Péters f0b7060d87 admin: properly translate 'change' link (#2449) 2013-03-05 13:58:13 +01:00
Frédéric Péters f66ff32899 admin: add links to formdefs from a workflow page (#1908) 2013-03-05 13:57:04 +01:00
Frédéric Péters aa1d54e622 formdef: fix passing of ignore_migration arg 2013-03-05 13:56:30 +01:00
Frédéric Péters b443e1912a formdef: do not run migration path when checking on url_name 2013-03-05 13:56:18 +01:00
Frédéric Péters 69100dd066 formdef: avoid duplicated url_name 2013-03-05 13:56:01 +01:00
Frédéric Péters ebe3e94fde formdata: catch various bugs happening with draft forms 2013-03-05 13:55:45 +01:00
Frédéric Péters 0bb4f40c1c publisher: do not fail on early statsd call (#2521) 2013-03-05 13:54:51 +01:00
Thomas NOËL 6c071a154a debian/changelog: release 2012.11-8 2013-03-05 13:54:15 +01:00
Frédéric Péters dfa5675d82 cron: use prctl to set process name & title (#2524) 2013-03-05 13:53:23 +01:00
Frédéric Péters 57ffd032b2 workflows: do not reuse action instances in default workflow
(manual cherry pick of parts of 9ecdc0be)
2013-03-05 13:36:33 +01:00
Thomas NOËL ab694ac166 debian/changelog: release 2012.11-7 2013-03-04 16:42:03 +01:00
Frédéric Péters 3caa9927f4 password: do not abort on trying to remove a user without password account 2013-01-21 09:21:37 +01:00
Frédéric Péters b8fca01771 qommon: added basic statsd infrastructure (#1862) 2012-12-03 15:20:54 +01:00
Benjamin Dauvergne b3a715a3b9 storage: fix _atomic_write to create hidden temporary files (#2010) 2012-12-03 15:20:51 +01:00
Frédéric Péters 3b087fba26 admin/wf: use gettext in breadcrumbs (#486) 2012-12-03 15:20:25 +01:00
Thomas NOËL 44538b52f1 debian/changelog: 2012.11-6 2012-12-03 14:44:41 +01:00
Thomas NOËL 236bbd8e2b update french translations 2012-11-23 14:40:11 +01:00
Frédéric Péters 909e6cd7ca workflows: allow marking a status as terminal (#1960) 2012-11-23 14:00:54 +01:00
Thomas NOËL 8230b00de6 Revert "workflows: allow marking a status as terminal"
This reverts commit af78aecfac.
2012-11-23 14:00:36 +01:00
Thomas NOËL 7c8e1ab0b8 default workflow items are visible 2012-11-23 13:53:54 +01:00
Benjamin Dauvergne da7635f902 storage: remove unused import 2012-11-23 13:53:04 +01:00
Benjamin Dauvergne 27a170136f prevent error when lasso does not have the EP_HREF symbol 2012-11-23 13:51:27 +01:00
Frédéric Péters 9ca1b0c05f admin: reduce number of breadcrumbs in workflows admin (#486) 2012-11-16 21:51:02 +01:00
Frédéric Péters af78aecfac workflows: allow marking a status as terminal 2012-11-16 21:47:31 +01:00
Frédéric Péters ed8b18268f workflows: enhance default workflow to mail comments when in 'new' status 2012-11-16 21:47:26 +01:00
Frédéric Péters 93cf9ff1bc css: avoid a vertical scrollbar on main content of backoffice/admin 2012-11-16 21:01:33 +01:00
Frédéric Péters b9d4b1e59e do not fail on checking for submitter when user hashes get disabled 2012-11-16 21:01:30 +01:00
Thomas NOËL 13351720fb ptl: use %s instead of %d (fix #1910) 2012-11-12 16:50:09 +01:00
Thomas NOËL 99ec4610c9 debian/changelog: 2012.11-5 2012-11-12 16:48:33 +01:00
Thomas NOËL c444d92f45 french translations update (form_url_backoffice) 2012-11-12 10:06:56 +01:00
Frédéric Péters d4033440a8 workflows: do not stop checking for authorization after submitter check 2012-11-11 18:31:53 +01:00
Frédéric Péters dc9a2de6c4 fix notification emails to point to the backoffice URLs 2012-11-11 18:31:50 +01:00
Thomas NOËL 24d9cb877c debian/changelog 2012.11-4 (hotfix...) 2012-11-09 16:41:12 +01:00
Thomas NOËL 92ae53e83d update debian/changelog 2012-11-09 16:14:31 +01:00
Thomas NOËL b6f344a6f7 fix form_status_changed 2012-11-09 16:13:29 +01:00
Thomas NOËL b6b0b8e862 update changelot 2012-11-09 15:25:07 +01:00
Frédéric Péters 9ab836e326 storage: fix getting object filename (#1901) 2012-11-09 12:31:03 +01:00
Frédéric Péters e90df55517 rebuild-indexes: also rebuild formdata indexes (#1903) 2012-11-09 12:31:03 +01:00
Thomas NOËL 42aca70ef2 debian/changelog 2012.11-3 (hotfixes) 2012-11-09 12:28:11 +01:00
Frédéric Péters d9ad5b0e66 fields: fix calculation of first element length in item field 2012-11-06 19:20:36 +01:00
Frédéric Péters 8137ebd93a forms: fix public listing 2012-11-06 19:20:22 +01:00
Frédéric Péters 746b001ba8 publisher: do not fail exporting substitution variables in cron jobs 2012-11-06 19:19:50 +01:00
Thomas NOËL 0fbde47930 debian/changelog 2012.3-2 (hotfix...) 2012-11-05 15:58:53 +01:00
Thomas NOËL f13883763f also migrate formdata evolution to new workflows 2012-11-05 15:15:33 +01:00
Thomas NOËL de48f2420f debian changelog: 2012.11
cannot use 2012.3, because 2012.5 and 2012.9 has been used before (year.month scheme)
2012-11-02 14:57:15 +01:00
33 changed files with 1361 additions and 4728 deletions

154
debian/changelog vendored
View File

@ -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.

1329
po/fr.po

File diff suppressed because it is too large Load Diff

3993
po/wcs.pot

File diff suppressed because it is too large Load Diff

View File

@ -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')

View File

@ -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:

View File

@ -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)

View File

@ -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):

View File

@ -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):

46
wcs/ctl/runscript.py Normal file
View File

@ -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()

View File

@ -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 />')

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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>'))

View File

@ -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()

View File

@ -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()),
}

View File

@ -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)

View File

@ -475,6 +475,7 @@ div#main-content {
float: left;
position: relative;
overflow-x: auto;
overflow-y: hidden;
}
div#main-content h2 {

View File

@ -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

View File

@ -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);
}

View File

@ -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'); });

View File

@ -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)

View File

@ -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

113
wcs/qommon/vendor/pystatsd.py vendored Normal file
View File

@ -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

View File

@ -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):

View File

@ -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)