merge branch master
This commit is contained in:
commit
f2fbcb52c3
26 changed files with 537 additions and 79 deletions
|
@ -489,7 +489,7 @@ class BaseView(GenericView, I18NView):
|
||||||
return absoluteURL(provider, self.request)
|
return absoluteURL(provider, self.request)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def renderText(self, text, contentType):
|
def renderText(self, text, contentType='text/restructured'):
|
||||||
text = util.toUnicode(text)
|
text = util.toUnicode(text)
|
||||||
typeKey = util.renderingFactories.get(contentType, None)
|
typeKey = util.renderingFactories.get(contentType, None)
|
||||||
if typeKey is None:
|
if typeKey is None:
|
||||||
|
|
|
@ -51,6 +51,8 @@
|
||||||
</th>
|
</th>
|
||||||
</tr></tbody>
|
</tr></tbody>
|
||||||
|
|
||||||
|
<tbody metal:define-slot="custom_header" />
|
||||||
|
|
||||||
<tbody><tr><td colspan="5" style="padding-right: 15px">
|
<tbody><tr><td colspan="5" style="padding-right: 15px">
|
||||||
<div id="form.fields">
|
<div id="form.fields">
|
||||||
<metal:fields use-macro="view/fieldRenderers/fields" />
|
<metal:fields use-macro="view/fieldRenderers/fields" />
|
||||||
|
@ -59,7 +61,7 @@
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr metal:use-macro="view/template/macros/assignments" />
|
<tr metal:use-macro="view/template/macros/assignments" />
|
||||||
<tal:custom define="customMacro view/customMacro"
|
<tal:custom define="customMacro view/customMacro|nothing"
|
||||||
condition="customMacro">
|
condition="customMacro">
|
||||||
<tr metal:use-macro="customMacro" />
|
<tr metal:use-macro="customMacro" />
|
||||||
</tal:custom>
|
</tal:custom>
|
||||||
|
@ -119,6 +121,8 @@
|
||||||
tal:attributes="value typeToken" />
|
tal:attributes="value typeToken" />
|
||||||
</th></tr></tbody>
|
</th></tr></tbody>
|
||||||
|
|
||||||
|
<tbody metal:define-slot="custom_header" />
|
||||||
|
|
||||||
<tbody><tr><td colspan="5">
|
<tbody><tr><td colspan="5">
|
||||||
<div id="form.fields">
|
<div id="form.fields">
|
||||||
<metal:fields use-macro="view/fieldRenderers/fields" />
|
<metal:fields use-macro="view/fieldRenderers/fields" />
|
||||||
|
@ -127,7 +131,7 @@
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr metal:use-macro="view/template/macros/assignments" />
|
<tr metal:use-macro="view/template/macros/assignments" />
|
||||||
<tal:custom define="customMacro view/customMacro"
|
<tal:custom define="customMacro view/customMacro|nothing"
|
||||||
condition="customMacro">
|
condition="customMacro">
|
||||||
<tr metal:use-macro="customMacro" />
|
<tr metal:use-macro="customMacro" />
|
||||||
</tal:custom>
|
</tal:custom>
|
||||||
|
|
|
@ -386,6 +386,8 @@ class NodeView(BaseView):
|
||||||
ht = super(NodeView, self).headTitle
|
ht = super(NodeView, self).headTitle
|
||||||
if ht not in parts:
|
if ht not in parts:
|
||||||
parts.append(ht)
|
parts.append(ht)
|
||||||
|
if self.globalOptions('reverseHeadTitle'):
|
||||||
|
parts.reverse()
|
||||||
return ' - ' .join(parts)
|
return ' - ' .join(parts)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
|
|
@ -314,8 +314,13 @@
|
||||||
|
|
||||||
|
|
||||||
<metal:login define-macro="login">
|
<metal:login define-macro="login">
|
||||||
<div><a href="login.html"
|
<div>
|
||||||
|
<a href="login.html"
|
||||||
i18n:translate="">Log in</a></div>
|
i18n:translate="">Log in</a></div>
|
||||||
|
<div tal:define="register python:view.globalOptions('provideLogin')"
|
||||||
|
tal:condition="register">
|
||||||
|
<a tal:attributes="href python:register[0]"
|
||||||
|
i18n:translate="">Register new member</a></div>
|
||||||
</metal:login>
|
</metal:login>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,13 @@
|
||||||
<metal:tabs use-macro="views/node_macros/breadcrumbs" />
|
<metal:tabs use-macro="views/node_macros/breadcrumbs" />
|
||||||
</metal:breadcrumbs>
|
</metal:breadcrumbs>
|
||||||
<div metal:define-slot="actions"></div>
|
<div metal:define-slot="actions"></div>
|
||||||
<div metal:define-slot="message"></div>
|
<metal:message define-slot="message">
|
||||||
|
<div class="message"
|
||||||
|
i18n:translate=""
|
||||||
|
tal:define="msg request/loops.message|nothing"
|
||||||
|
tal:condition="msg"
|
||||||
|
tal:content="msg" />
|
||||||
|
</metal:message>
|
||||||
<metal:tabs use-macro="views/node_macros/view_modes" />
|
<metal:tabs use-macro="views/node_macros/view_modes" />
|
||||||
<metal:content define-slot="content">
|
<metal:content define-slot="content">
|
||||||
<tal:content define="item nocall:view/item;
|
<tal:content define="item nocall:view/item;
|
||||||
|
|
|
@ -62,6 +62,10 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
margin-bottom: 0.4em;
|
margin-bottom: 0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
a[href]:hover {
|
a[href]:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #6060c0;
|
color: #6060c0;
|
||||||
|
@ -120,6 +124,10 @@ thead th {
|
||||||
margin-bottom: 0.3em;
|
margin-bottom: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.infotext {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
.fields td {
|
.fields td {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
@ -163,6 +171,10 @@ table.listing td {
|
||||||
border-bottom: 1px dotted #dddddd;
|
border-bottom: 1px dotted #dddddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.listing tr.vpad td {
|
||||||
|
padding: 7px 2px 7px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
fieldset.box table.listing td {
|
fieldset.box table.listing td {
|
||||||
padding: 0 1px 0 1px;
|
padding: 0 1px 0 1px;
|
||||||
}
|
}
|
||||||
|
@ -267,7 +279,8 @@ fieldset.box td {
|
||||||
|
|
||||||
.top-actions {
|
.top-actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 30px;
|
top: 40px;
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quicksearch input {
|
.quicksearch input {
|
||||||
|
@ -281,7 +294,7 @@ fieldset.box td {
|
||||||
|
|
||||||
.page-actions {
|
.page-actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 55px;
|
top: 75px;
|
||||||
margin-left: 210px;
|
margin-left: 210px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.traversing.api import getName
|
from zope.traversing.api import getName
|
||||||
|
|
||||||
|
from cybertools.meta.interfaces import IOptions
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
from loops.browser.lobo import standard
|
from loops.browser.lobo import standard
|
||||||
from loops.browser.concept import ConceptView
|
from loops.browser.concept import ConceptView
|
||||||
|
@ -118,11 +119,36 @@ class Base(object):
|
||||||
self.images[idx].append(img)
|
self.images[idx].append(img)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def getCssClassForResource(self, r):
|
def getDocumentTypeForResource(self, r):
|
||||||
for c in r.context.getConcepts([self.defaultPredicate]):
|
for c in r.context.getConcepts([self.defaultPredicate]):
|
||||||
if c.conceptType == self.documentTypeType:
|
if c.conceptType == self.documentTypeType:
|
||||||
return getName(c)
|
return c
|
||||||
|
|
||||||
|
def getOptionsForResource(self, r, name):
|
||||||
|
dt = self.getDocumentTypeForResource(r)
|
||||||
|
if dt is not None:
|
||||||
|
return IOptions(adapted(dt))(name)
|
||||||
|
|
||||||
|
def getTitleForResource(self, r):
|
||||||
|
if self.getOptionsForResource(r, 'showtitle'):
|
||||||
|
return r.title
|
||||||
|
|
||||||
|
def getIconForResource(self, r):
|
||||||
|
icon = self.getOptionsForResource(r, 'icon')
|
||||||
|
if icon:
|
||||||
|
return '/'.join((self.controller.resourceBase, icon[0]))
|
||||||
|
|
||||||
|
def getCssClassForResource(self, r):
|
||||||
|
dt = self.getDocumentTypeForResource(r)
|
||||||
|
if dt is None:
|
||||||
return 'textelement'
|
return 'textelement'
|
||||||
|
css = IOptions(adapted(dt))('cssclass')
|
||||||
|
if css:
|
||||||
|
return css
|
||||||
|
return getName(dt)
|
||||||
|
|
||||||
|
def getMacroForResource(self, r):
|
||||||
|
return self.book_macros['default_text']
|
||||||
|
|
||||||
def getParentsForResource(self, r):
|
def getParentsForResource(self, r):
|
||||||
for c in r.context.getConcepts([self.defaultPredicate]):
|
for c in r.context.getConcepts([self.defaultPredicate]):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
type(u'documenttype', u'Dokumentenart', options=u'qualifier:assign',
|
type(u'documenttype', u'Dokumentenart', options=u'qualifier:assign',
|
||||||
|
typeInterface=u'loops.interfaces.IOptions',
|
||||||
viewName=u'')
|
viewName=u'')
|
||||||
|
|
||||||
# book types
|
# book types
|
||||||
|
|
|
@ -42,9 +42,18 @@
|
||||||
<metal:text define-macro="textresources">
|
<metal:text define-macro="textresources">
|
||||||
<div tal:repeat="related item/textResources">
|
<div tal:repeat="related item/textResources">
|
||||||
<div class="span-4">
|
<div class="span-4">
|
||||||
<div tal:attributes="class python:
|
<div metal:define-macro="default_text"
|
||||||
item.getCssClassForResource(related)"
|
tal:attributes="class python:
|
||||||
tal:content="structure related/render" />
|
item.getCssClassForResource(related)">
|
||||||
|
<h3 tal:define="ttitle python:item.getTitleForResource(related)"
|
||||||
|
tal:condition="ttitle"
|
||||||
|
tal:content="ttitle" />
|
||||||
|
<img class="flow-left" style="padding-top: 5px"
|
||||||
|
tal:define="icon python:item.getIconForResource(related)"
|
||||||
|
tal:condition="icon"
|
||||||
|
tal:attributes="src icon" />
|
||||||
|
<span tal:content="structure related/render" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="span-2 last" style="padding-top: 0.4em">
|
<div class="span-2 last" style="padding-top: 0.4em">
|
||||||
<div class="object-actions" style="padding-top: 0"
|
<div class="object-actions" style="padding-top: 0"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -78,28 +78,28 @@ class OfficeFile(ExternalFileAdapter):
|
||||||
@Lazy
|
@Lazy
|
||||||
def docPropertyDom(self):
|
def docPropertyDom(self):
|
||||||
fn = self.docFilename
|
fn = self.docFilename
|
||||||
dummy = dict(core=[], custom=[])
|
result = dict(core=[], custom=[])
|
||||||
root, ext = os.path.splitext(fn)
|
root, ext = os.path.splitext(fn)
|
||||||
if not ext.lower() in self.fileExtensions:
|
if not ext.lower() in self.fileExtensions:
|
||||||
return dummy
|
return result
|
||||||
try:
|
try:
|
||||||
zf = ZipFile(fn, 'r')
|
zf = ZipFile(fn, 'r')
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
self.logger.warn(e)
|
self.logger.warn(e)
|
||||||
return dummy
|
return result
|
||||||
if self.corePropFileName not in zf.namelist():
|
if self.corePropFileName not in zf.namelist():
|
||||||
self.logger.warn('Core properties not found in file %s.' %
|
self.logger.warn('Core properties not found in file %s.' %
|
||||||
self.externalAddress)
|
self.externalAddress)
|
||||||
|
else:
|
||||||
|
result['core'] = etree.fromstring(zf.read(self.corePropFileName))
|
||||||
if self.propFileName not in zf.namelist():
|
if self.propFileName not in zf.namelist():
|
||||||
self.logger.warn('Custom properties not found in file %s.' %
|
self.logger.warn('Custom properties not found in file %s.' %
|
||||||
self.externalAddress)
|
self.externalAddress)
|
||||||
propsXml = zf.read(self.propFileName)
|
else:
|
||||||
corePropsXml = zf.read(self.corePropFileName)
|
result['custom'] = etree.fromstring(zf.read(self.propFileName))
|
||||||
# TODO: read core.xml, return both trees in dictionary
|
|
||||||
zf.close()
|
zf.close()
|
||||||
return {'custom': etree.fromstring(propsXml),
|
return result
|
||||||
'core': etree.fromstring(corePropsXml)}
|
|
||||||
|
|
||||||
def getDocProperty(self, pname):
|
def getDocProperty(self, pname):
|
||||||
for p in self.docPropertyDom['custom']:
|
for p in self.docPropertyDom['custom']:
|
||||||
|
|
|
@ -690,9 +690,21 @@ class IIndexAttributes(Interface):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# reusable interface elements
|
||||||
|
|
||||||
|
class IOptions(Interface):
|
||||||
|
|
||||||
|
options = schema.List(
|
||||||
|
title=_(u'Options'),
|
||||||
|
description=_(u'Additional settings.'),
|
||||||
|
value_type=schema.TextLine(),
|
||||||
|
default=[],
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
|
||||||
# types stuff
|
# types stuff
|
||||||
|
|
||||||
class ITypeConcept(IConceptSchema, ILoopsAdapter):
|
class ITypeConcept(IConceptSchema, ILoopsAdapter, IOptions):
|
||||||
""" Concepts of type 'type' should be adaptable to this interface.
|
""" Concepts of type 'type' should be adaptable to this interface.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -725,13 +737,6 @@ class ITypeConcept(IConceptSchema, ILoopsAdapter):
|
||||||
default=u'',
|
default=u'',
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
options = schema.List(
|
|
||||||
title=_(u'Options'),
|
|
||||||
description=_(u'Additional settings.'),
|
|
||||||
value_type=schema.TextLine(),
|
|
||||||
default=[],
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
# storage = schema.Choice()
|
# storage = schema.Choice()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ class SurveyView(ConceptView):
|
||||||
mapping=dict(minAnswers=qugroup.minAnswers)),
|
mapping=dict(minAnswers=qugroup.minAnswers)),
|
||||||
target_language=lang)
|
target_language=lang)
|
||||||
if info:
|
if info:
|
||||||
text = u'%s<br />(%s)' % (text, info)
|
text = u'<i>%s</i><br />(%s)' % (text, info)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def getValues(self, question):
|
def getValues(self, question):
|
||||||
|
|
|
@ -38,6 +38,13 @@ class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire):
|
||||||
default=4,
|
default=4,
|
||||||
required=True)
|
required=True)
|
||||||
|
|
||||||
|
feedbackHeader = schema.Text(
|
||||||
|
title=_(u'Feedback Header'),
|
||||||
|
description=_(u'Text that will appear at the top of the feedback page.'),
|
||||||
|
default=u'',
|
||||||
|
missing_value=u'',
|
||||||
|
required=False)
|
||||||
|
|
||||||
feedbackFooter = schema.Text(
|
feedbackFooter = schema.Text(
|
||||||
title=_(u'Feedback Footer'),
|
title=_(u'Feedback Footer'),
|
||||||
description=_(u'Text that will appear at the end of the feedback page.'),
|
description=_(u'Text that will appear at the end of the feedback page.'),
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
</tal:description>
|
</tal:description>
|
||||||
<div tal:condition="feedback">
|
<div tal:condition="feedback">
|
||||||
<h3 i18n:translate="">Feedback</h3>
|
<h3 i18n:translate="">Feedback</h3>
|
||||||
|
<div tal:define="header item/adapted/feedbackHeader"
|
||||||
|
tal:condition="header"
|
||||||
|
tal:content="structure python:item.renderText(header, 'text/restructured')" />
|
||||||
<table class="listing">
|
<table class="listing">
|
||||||
<tr>
|
<tr>
|
||||||
<th i18n:translate="">Category</th>
|
<th i18n:translate="">Category</th>
|
||||||
|
@ -47,12 +50,12 @@
|
||||||
<table class="listing">
|
<table class="listing">
|
||||||
<tal:qugroup repeat="qugroup item/adapted/questionGroups">
|
<tal:qugroup repeat="qugroup item/adapted/questionGroups">
|
||||||
<tr><td colspan="6"> </td></tr>
|
<tr><td colspan="6"> </td></tr>
|
||||||
<tr>
|
<tr class="vpad">
|
||||||
<td tal:define="infoText python:item.getInfoText(qugroup)">
|
<td tal:define="infoText python:item.getInfoText(qugroup)">
|
||||||
<b tal:content="qugroup/title" />
|
<b tal:content="qugroup/title" />
|
||||||
<div tal:condition="infoText">
|
<div class="infotext"
|
||||||
|
tal:condition="infoText">
|
||||||
<span tal:content="structure infoText" />
|
<span tal:content="structure infoText" />
|
||||||
<br />
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: center"
|
<td style="text-align: center"
|
||||||
|
@ -63,7 +66,8 @@
|
||||||
style="text-align: right"
|
style="text-align: right"
|
||||||
i18n:translate="">Does not apply</td>
|
i18n:translate="">Does not apply</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr tal:repeat="question qugroup/questions">
|
<tr class="vpad"
|
||||||
|
tal:repeat="question qugroup/questions">
|
||||||
<td tal:content="question/text" />
|
<td tal:content="question/text" />
|
||||||
<td style="white-space: nowrap; text-align: center"
|
<td style="white-space: nowrap; text-align: center"
|
||||||
tal:repeat="value python:item.getValues(question)">
|
tal:repeat="value python:item.getValues(question)">
|
||||||
|
|
Binary file not shown.
|
@ -3,7 +3,7 @@ msgstr ""
|
||||||
|
|
||||||
"Project-Id-Version: 0.13.0\n"
|
"Project-Id-Version: 0.13.0\n"
|
||||||
"POT-Creation-Date: 2007-05-22 12:00 CET\n"
|
"POT-Creation-Date: 2007-05-22 12:00 CET\n"
|
||||||
"PO-Revision-Date: 2013-04-06 12:00 CET\n"
|
"PO-Revision-Date: 2013-06-20 12:00 CET\n"
|
||||||
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
||||||
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
@ -86,6 +86,9 @@ msgstr "Thema bearbeiten..."
|
||||||
msgid "Modify topic."
|
msgid "Modify topic."
|
||||||
msgstr "Thema ändern"
|
msgstr "Thema ändern"
|
||||||
|
|
||||||
|
msgid "Please correct the indicated errors."
|
||||||
|
msgstr "Bitte berichtigen Sie die angezeigten Fehler."
|
||||||
|
|
||||||
# blog
|
# blog
|
||||||
|
|
||||||
msgid "Edit Blog Post..."
|
msgid "Edit Blog Post..."
|
||||||
|
@ -762,12 +765,27 @@ msgstr "Teilnehmerregistrierung"
|
||||||
msgid "Register"
|
msgid "Register"
|
||||||
msgstr "Benutzer registrieren"
|
msgstr "Benutzer registrieren"
|
||||||
|
|
||||||
|
msgid "Register new member"
|
||||||
|
msgstr "Neu registrieren"
|
||||||
|
|
||||||
|
msgid "Login name already taken."
|
||||||
|
msgstr "Die von Ihnen eingegebene Benutzerkennung ist schon vergeben."
|
||||||
|
|
||||||
msgid "Your old password was not entered correctly."
|
msgid "Your old password was not entered correctly."
|
||||||
msgstr "Sie haben Ihr altes Passwort nicht korrekt eingegeben."
|
msgstr "Sie haben Ihr altes Passwort nicht korrekt eingegeben."
|
||||||
|
|
||||||
msgid "Password and password confirmation do not match."
|
msgid "Password and password confirmation do not match."
|
||||||
msgstr "Die Passwort-Wiederholung stimmt nicht mit dem eingegebenen Passwort überein."
|
msgstr "Die Passwort-Wiederholung stimmt nicht mit dem eingegebenen Passwort überein."
|
||||||
|
|
||||||
|
msgid "confirmation_mail_subject"
|
||||||
|
msgstr "Benutzer-Registrierung"
|
||||||
|
|
||||||
|
msgid "confirmation_mail_text"
|
||||||
|
msgstr "Bitte clicken Sie auf den folgenden Link, um die Anmeldung abzuschließen."
|
||||||
|
|
||||||
|
msgid "The user account has been created."
|
||||||
|
msgstr "Ihr Benutzerkonto wurde eingerichtet."
|
||||||
|
|
||||||
msgid "Your password has been changed."
|
msgid "Your password has been changed."
|
||||||
msgstr "Ihr Passwort wurde geändert."
|
msgstr "Ihr Passwort wurde geändert."
|
||||||
|
|
||||||
|
@ -1010,14 +1028,26 @@ msgid "Restrict to objects with certain states"
|
||||||
msgstr "Auf Objekte mit bestimmtem Status beschränken"
|
msgstr "Auf Objekte mit bestimmtem Status beschränken"
|
||||||
|
|
||||||
msgid "Workflow"
|
msgid "Workflow"
|
||||||
msgstr "Statusdefinition/Workflow"
|
msgstr "Workflow"
|
||||||
|
|
||||||
msgid "States"
|
msgid "States"
|
||||||
msgstr "Statuswerte"
|
msgstr "Statuswerte"
|
||||||
|
|
||||||
|
msgid "States Definition"
|
||||||
|
msgstr "Workflowdefinition"
|
||||||
|
|
||||||
|
msgid "State Transition"
|
||||||
|
msgstr "Workflow-Statusänderung"
|
||||||
|
|
||||||
|
msgid "Transition"
|
||||||
|
msgstr "Aktion"
|
||||||
|
|
||||||
msgid "State information for $definition: $title"
|
msgid "State information for $definition: $title"
|
||||||
msgstr "Status ($definition): $title"
|
msgstr "Status ($definition): $title"
|
||||||
|
|
||||||
|
msgid "Available Transitions"
|
||||||
|
msgstr "Übergänge"
|
||||||
|
|
||||||
msgid "classification_quality"
|
msgid "classification_quality"
|
||||||
msgstr "Klassifizierung"
|
msgstr "Klassifizierung"
|
||||||
|
|
||||||
|
@ -1030,6 +1060,12 @@ msgstr "Aufgabe"
|
||||||
msgid "publishable_task"
|
msgid "publishable_task"
|
||||||
msgstr "Aufgabe/Zugriff"
|
msgstr "Aufgabe/Zugriff"
|
||||||
|
|
||||||
|
msgid "label_transition_comments"
|
||||||
|
msgstr "Bemerkung"
|
||||||
|
|
||||||
|
msgid "desc_transition_comments"
|
||||||
|
msgstr "Notizen zum Statusübergang."
|
||||||
|
|
||||||
# state names
|
# state names
|
||||||
|
|
||||||
msgid "accepted"
|
msgid "accepted"
|
||||||
|
|
|
@ -185,7 +185,7 @@ sure that a principal object can be served by a corresponding factory):
|
||||||
... 'lastName': u'Sawyer',
|
... 'lastName': u'Sawyer',
|
||||||
... 'firstName': u'Tom',
|
... 'firstName': u'Tom',
|
||||||
... 'email': u'tommy@sawyer.com',
|
... 'email': u'tommy@sawyer.com',
|
||||||
... 'action': 'update',}
|
... 'form.action': 'update',}
|
||||||
|
|
||||||
and register it.
|
and register it.
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ Definition of view classes and other browser related stuff for
|
||||||
members (persons).
|
members (persons).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from email.MIMEText import MIMEText
|
||||||
from zope import interface, component
|
from zope import interface, component
|
||||||
from zope.app.authentication.principalfolder import InternalPrincipal
|
from zope.app.authentication.principalfolder import InternalPrincipal
|
||||||
from zope.app.form.browser.textwidgets import PasswordWidget as BasePasswordWidget
|
from zope.app.form.browser.textwidgets import PasswordWidget as BasePasswordWidget
|
||||||
|
@ -29,6 +31,8 @@ from zope.app.principalannotation import annotations
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.i18nmessageid import MessageFactory
|
from zope.i18nmessageid import MessageFactory
|
||||||
from zope.security import checkPermission
|
from zope.security import checkPermission
|
||||||
|
from zope.sendmail.interfaces import IMailDelivery
|
||||||
|
from zope.traversing.browser import absoluteURL
|
||||||
|
|
||||||
from cybertools.composer.interfaces import IInstance
|
from cybertools.composer.interfaces import IInstance
|
||||||
from cybertools.composer.schema.browser.common import schema_macros
|
from cybertools.composer.schema.browser.common import schema_macros
|
||||||
|
@ -36,7 +40,8 @@ from cybertools.composer.schema.browser.form import Form, CreateForm
|
||||||
from cybertools.composer.schema.schema import FormState, FormError
|
from cybertools.composer.schema.schema import FormState, FormError
|
||||||
from cybertools.meta.interfaces import IOptions
|
from cybertools.meta.interfaces import IOptions
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
from loops.browser.common import concept_macros
|
from cybertools.util.randomname import generateName
|
||||||
|
from loops.browser.common import concept_macros, form_macros
|
||||||
from loops.browser.concept import ConceptView, ConceptRelationView
|
from loops.browser.concept import ConceptView, ConceptRelationView
|
||||||
from loops.browser.node import NodeView
|
from loops.browser.node import NodeView
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
|
@ -44,7 +49,7 @@ from loops.concept import Concept
|
||||||
from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager
|
from loops.organize.interfaces import ANNOTATION_KEY, IMemberRegistrationManager
|
||||||
from loops.organize.interfaces import IMemberRegistration, IPasswordChange
|
from loops.organize.interfaces import IMemberRegistration, IPasswordChange
|
||||||
from loops.organize.party import getPersonForUser, Person
|
from loops.organize.party import getPersonForUser, Person
|
||||||
from loops.organize.util import getInternalPrincipal
|
from loops.organize.util import getInternalPrincipal, getPrincipalForUserId
|
||||||
import loops.browser.util
|
import loops.browser.util
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
@ -74,10 +79,11 @@ class PersonalInfo(ConceptView):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class MemberRegistration(NodeView, CreateForm):
|
class BaseMemberRegistration(NodeView):
|
||||||
|
|
||||||
interface = IMemberRegistration # TODO: add company, create institution
|
interface = IMemberRegistration # TODO: add company, create institution
|
||||||
message = _(u'The user account has been created.')
|
message = _(u'The user account has been created.')
|
||||||
|
template = form_macros
|
||||||
|
|
||||||
formErrors = dict(
|
formErrors = dict(
|
||||||
confirm_nomatch=FormError(_(u'Password and password confirmation do not match.')),
|
confirm_nomatch=FormError(_(u'Password and password confirmation do not match.')),
|
||||||
|
@ -86,10 +92,23 @@ class MemberRegistration(NodeView, CreateForm):
|
||||||
|
|
||||||
label = _(u'Member Registration')
|
label = _(u'Member Registration')
|
||||||
label_submit = _(u'Register')
|
label_submit = _(u'Register')
|
||||||
|
title = _('Member Registration')
|
||||||
|
|
||||||
permissions_key = u'registration.permissions'
|
permissions_key = u'registration.permissions'
|
||||||
roles_key = u'registration.roles'
|
roles_key = u'registration.roles'
|
||||||
registration_adapter_key = u'registration.adapter'
|
registration_adapter_key = u'registration.adapter'
|
||||||
|
text_names_prefix = 'organize.member.registration'
|
||||||
|
# texts: reg_info, reg_feedback, conf_mail, conf_info, conf_feedback
|
||||||
|
info_key = 'reg_info'
|
||||||
|
feedback_key = 'reg_feedback'
|
||||||
|
|
||||||
|
isInnerHtml = False
|
||||||
|
showAssignments = False
|
||||||
|
form_action = 'register'
|
||||||
|
versionInfo = None
|
||||||
|
|
||||||
|
def closeAction(self, submit=True):
|
||||||
|
return u''
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def macro(self):
|
def macro(self):
|
||||||
|
@ -106,6 +125,31 @@ class MemberRegistration(NodeView, CreateForm):
|
||||||
def item(self):
|
def item(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def data(self):
|
||||||
|
return self.request.form
|
||||||
|
|
||||||
|
def getPrincipalAnnotation(self, principal):
|
||||||
|
return annotations(principal).get(ANNOTATION_KEY, None)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def infoText(self):
|
||||||
|
name = '.'.join((self.text_names_prefix, self.info_key))
|
||||||
|
text = self.resourceManager.get(name)
|
||||||
|
if text:
|
||||||
|
return self.renderText(text.data)
|
||||||
|
return u''
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def feedbackUrl(self):
|
||||||
|
name = '.'.join((self.text_names_prefix, self.feedback_key))
|
||||||
|
text = self.resourceManager.get(name)
|
||||||
|
if text:
|
||||||
|
return self.getUrlForTarget(text)
|
||||||
|
|
||||||
|
|
||||||
|
class MemberRegistration(BaseMemberRegistration, CreateForm):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def schema(self):
|
def schema(self):
|
||||||
schema = super(MemberRegistration, self).schema
|
schema = super(MemberRegistration, self).schema
|
||||||
|
@ -113,13 +157,14 @@ class MemberRegistration(NodeView, CreateForm):
|
||||||
schema.fields.reorder(-2, 'loginName')
|
schema.fields.reorder(-2, 'loginName')
|
||||||
return schema
|
return schema
|
||||||
# TODO: add company, create institution
|
# TODO: add company, create institution
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def object(self):
|
def object(self):
|
||||||
return Person(Concept())
|
return Person(Concept())
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
form = self.request.form
|
form = self.request.form
|
||||||
if not form.get('action'):
|
if not form.get('form.action'):
|
||||||
return True
|
return True
|
||||||
instance = component.getAdapter(self.object, IInstance, name='editor')
|
instance = component.getAdapter(self.object, IInstance, name='editor')
|
||||||
instance.template = self.schema
|
instance.template = self.schema
|
||||||
|
@ -155,30 +200,164 @@ class MemberRegistration(NodeView, CreateForm):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class SecureMemberRegistration(MemberRegistration):
|
class SecureMemberRegistration(BaseMemberRegistration, CreateForm):
|
||||||
|
|
||||||
permissions_key = u'secure_registration.permissions'
|
permissions_key = u'secure_registration.permissions'
|
||||||
roles_key = u'secure_registration.roles'
|
roles_key = u'secure_registration.roles'
|
||||||
|
email_key = 'reg_email'
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def schema(self):
|
def schema(self):
|
||||||
schema = super(MemberRegistration, self).schema
|
schema = super(SecureMemberRegistration, self).schema
|
||||||
schema.fields.remove('birthDate')
|
schema.fields.remove('birthDate')
|
||||||
schema.fields.remove('password')
|
schema.fields.remove('password')
|
||||||
schema.fields.remove('passwordConfirm')
|
schema.fields.remove('passwordConfirm')
|
||||||
schema.fields.remove('phoneNumbers')
|
schema.fields.remove('phoneNumbers')
|
||||||
schema.fields.reorder(-2, 'loginName')
|
#schema.fields.reorder(-2, 'loginName')
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return organize_macros.macros['register']
|
||||||
|
|
||||||
class ConfirmMemberRegistration(NodeView):
|
@Lazy
|
||||||
|
def object(self):
|
||||||
|
return Person(Concept())
|
||||||
|
|
||||||
# TODO: control form via interface?
|
def update(self):
|
||||||
|
form = self.request.form
|
||||||
|
if not form.get('form.action'):
|
||||||
|
return True
|
||||||
|
instance = component.getAdapter(self.object, IInstance, name='editor')
|
||||||
|
instance.template = self.schema
|
||||||
|
self.formState = formState = instance.applyTemplate(data=form,
|
||||||
|
fieldHandlers=self.fieldHandlers)
|
||||||
|
if formState.severity > 0:
|
||||||
|
# show form again
|
||||||
|
return True
|
||||||
|
login = form.get('loginName')
|
||||||
|
regMan = IMemberRegistrationManager(self.context.getLoopsRoot())
|
||||||
|
pw = generateName()
|
||||||
|
email = form.get('email')
|
||||||
|
try:
|
||||||
|
result = regMan.register(login, pw,
|
||||||
|
form.get('lastName'), form.get('firstName'),
|
||||||
|
email=email,)
|
||||||
|
except ValueError, e:
|
||||||
|
fi = formState.fieldInstances['loginName']
|
||||||
|
fi.setError('duplicate_loginname', self.formErrors)
|
||||||
|
formState.severity = max(formState.severity, fi.severity)
|
||||||
|
return True
|
||||||
|
self.object = result
|
||||||
|
person = result.context
|
||||||
|
pa = self.getPrincipalAnnotation(
|
||||||
|
getPrincipalForUserId(adapted(person).getUserId()))
|
||||||
|
pa['id'] = generateName()
|
||||||
|
pa['timestamp'] = datetime.utcnow()
|
||||||
|
self.notifyEmail(login, email, pa['id'])
|
||||||
|
if self.feedbackUrl:
|
||||||
|
self.request.response.redirect(self.feedbackUrl)
|
||||||
|
else:
|
||||||
|
msg = self.message
|
||||||
|
self.request.response.redirect('%s?loops.message=%s' % (self.url, msg))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def notifyEmail(self, userid, recipient, id):
|
||||||
|
baseUrl = absoluteURL(self.context.getMenu(), self.request)
|
||||||
|
url = u'%s/selfservice_confirmation.html?login=%s&id=%s' % (
|
||||||
|
baseUrl, userid, id,)
|
||||||
|
recipients = [recipient]
|
||||||
|
subject = _(u'confirmation_mail_subject')
|
||||||
|
name = '.'.join((self.text_names_prefix, self.email_key))
|
||||||
|
text = self.resourceManager.get(name)
|
||||||
|
if text:
|
||||||
|
message = (text.data % url).encode('UTF-8')
|
||||||
|
subject = text.description or subject
|
||||||
|
else:
|
||||||
|
message = _(u'confirmation_mail_text') + u':\n\n'
|
||||||
|
message = (message + url).encode('UTF-8')
|
||||||
|
senderInfo = self.globalOptions('email.sender')
|
||||||
|
sender = senderInfo and senderInfo[0] or 'info@loops.cy55.de'
|
||||||
|
sender = sender.encode('UTF-8')
|
||||||
|
msg = MIMEText(message, 'plain', 'utf-8')
|
||||||
|
msg['Subject'] = subject.encode('UTF-8')
|
||||||
|
msg['From'] = sender
|
||||||
|
msg['To'] = ', '.join(recipients)
|
||||||
|
mailhost = component.getUtility(IMailDelivery, 'Mail')
|
||||||
|
mailhost.send(sender, recipients, msg.as_string())
|
||||||
|
|
||||||
|
|
||||||
|
class ConfirmMemberRegistration(BaseMemberRegistration, Form):
|
||||||
|
|
||||||
|
permissions_key = u'secure_registration.permissions'
|
||||||
|
roles_key = u'secure_registration.roles'
|
||||||
|
info_key = 'confirm_info'
|
||||||
|
feedback_key = 'confirm_feedback'
|
||||||
|
email_key = 'confirm_email'
|
||||||
|
|
||||||
|
form_action = 'confirm_registration'
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def macro(self):
|
def macro(self):
|
||||||
return organize_macros.macros['confirm']
|
return organize_macros.macros['confirm']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def data(self):
|
||||||
|
form = self.request.form
|
||||||
|
return dict(loginName=form.get('login'), id=form.get('id'))
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def schema(self):
|
||||||
|
schema = super(ConfirmMemberRegistration, self).schema
|
||||||
|
schema.fields.remove('salutation')
|
||||||
|
schema.fields.remove('academicTitle')
|
||||||
|
schema.fields.remove('birthDate')
|
||||||
|
schema.fields.remove('phoneNumbers')
|
||||||
|
schema.fields.remove('loginName')
|
||||||
|
schema.fields.remove('firstName')
|
||||||
|
schema.fields.remove('lastName')
|
||||||
|
schema.fields.remove('email')
|
||||||
|
return schema
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
form = self.request.form
|
||||||
|
if form.get('form.action') != 'confirm_registration':
|
||||||
|
return True
|
||||||
|
if not form.get('login'):
|
||||||
|
return True
|
||||||
|
regMan = IMemberRegistrationManager(self.context.getLoopsRoot())
|
||||||
|
prefix = regMan.getPrincipalFolderFromOption().prefix
|
||||||
|
userId = prefix + form['login']
|
||||||
|
principal = getPrincipalForUserId(userId)
|
||||||
|
pa = self.getPrincipalAnnotation(principal)
|
||||||
|
id = form.get('id')
|
||||||
|
if not id or id != pa.get('id'):
|
||||||
|
return True
|
||||||
|
instance = component.getAdapter(self.object, IInstance, name='editor')
|
||||||
|
instance.template = self.schema
|
||||||
|
self.formState = formState = instance.applyTemplate(data=form,
|
||||||
|
fieldHandlers=self.fieldHandlers)
|
||||||
|
#formState = self.formState = self.validate(form)
|
||||||
|
if formState.severity > 0:
|
||||||
|
return True
|
||||||
|
pw = form.get('password')
|
||||||
|
pwConfirm = form.get('passwordConfirm')
|
||||||
|
if pw != pwConfirm:
|
||||||
|
fi = formState.fieldInstances['password']
|
||||||
|
fi.setError('confirm_nomatch', self.formErrors)
|
||||||
|
formState.severity = max(formState.severity, fi.severity)
|
||||||
|
return True
|
||||||
|
del pa['id']
|
||||||
|
del pa['timestamp']
|
||||||
|
ip = getInternalPrincipal(userId)
|
||||||
|
ip.setPassword(pw)
|
||||||
|
if self.feedbackUrl:
|
||||||
|
self.request.response.redirect(self.feedbackUrl)
|
||||||
|
else:
|
||||||
|
url = '%s?loops.message=%s' % (self.url, self.message)
|
||||||
|
self.request.response.redirect(url)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class PasswordChange(NodeView, Form):
|
class PasswordChange(NodeView, Form):
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,40 @@
|
||||||
<html i18n:domain="loops">
|
<html i18n:domain="loops">
|
||||||
|
|
||||||
<metal:registration define-macro="confirm">
|
|
||||||
</metal:registration>
|
<metal:block define-macro="register">
|
||||||
|
<metal:data use-macro="view/form_macros/edit">
|
||||||
|
<metal:custom fill-slot="custom_header">
|
||||||
|
<tbody>
|
||||||
|
<tr><td colspan="5">
|
||||||
|
<tal:info content="structure item/infoText" />
|
||||||
|
</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</metal:custom>
|
||||||
|
</metal:data>
|
||||||
|
</metal:block>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:block define-macro="confirm">
|
||||||
|
<metal:data use-macro="view/form_macros/edit">
|
||||||
|
<metal:custom fill-slot="custom_header">
|
||||||
|
<tbody>
|
||||||
|
<tr><td colspan="5">
|
||||||
|
<tal:info content="structure item/infoText" />
|
||||||
|
</td></tr>
|
||||||
|
<tr><td colspan="5">
|
||||||
|
<input type="hidden" name="login"
|
||||||
|
tal:attributes="value item/data/loginName" />
|
||||||
|
<input type="hidden" name="id"
|
||||||
|
tal:attributes="value item/data/id" />
|
||||||
|
<table><tr>
|
||||||
|
<td i18n:translate="">Login Name</td>
|
||||||
|
<td tal:content="item/data/loginName" />
|
||||||
|
</tr></table>
|
||||||
|
</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</metal:custom>
|
||||||
|
</metal:data>
|
||||||
|
</metal:block>
|
||||||
|
|
||||||
|
|
||||||
<metal:task define-macro="task">
|
<metal:task define-macro="task">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -62,19 +62,29 @@ class MemberRegistrationManager(object):
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def personType(self):
|
||||||
|
concepts = self.context.getConceptManager()
|
||||||
|
return adapted(concepts[self.person_typeName])
|
||||||
|
|
||||||
|
def getPrincipalFolderFromOption(self):
|
||||||
|
options = IOptions(self.personType)
|
||||||
|
pfName = options(self.principalfolder_key,
|
||||||
|
(self.default_principalfolder,))[0]
|
||||||
|
return getPrincipalFolder(self.context, pfName)
|
||||||
|
|
||||||
def register(self, userId, password, lastName, firstName=u'',
|
def register(self, userId, password, lastName, firstName=u'',
|
||||||
groups=[], useExisting=False, pfName=None, **kw):
|
groups=[], useExisting=False, pfName=None, **kw):
|
||||||
concepts = self.context.getConceptManager()
|
options = IOptions(self.personType)
|
||||||
personType = adapted(concepts[self.person_typeName])
|
|
||||||
options = IOptions(personType)
|
|
||||||
if pfName is None:
|
if pfName is None:
|
||||||
pfName = options(self.principalfolder_key,
|
pfName = options(self.principalfolder_key,
|
||||||
(self.default_principalfolder,))[0]
|
(self.default_principalfolder,))[0]
|
||||||
self.createPrincipal(pfName, userId, password, lastName, firstName, useExisting=useExisting)
|
self.createPrincipal(pfName, userId, password, lastName, firstName,
|
||||||
|
useExisting=useExisting)
|
||||||
if not groups:
|
if not groups:
|
||||||
groups = options(self.groups_key, ())
|
groups = options(self.groups_key, ())
|
||||||
self.setGroupsForPrincipal(pfName, userId, groups=groups)
|
self.setGroupsForPrincipal(pfName, userId, groups=groups)
|
||||||
self.createPersonForPrincipal(pfName, userId, lastName, firstName,
|
return self.createPersonForPrincipal(pfName, userId, lastName, firstName,
|
||||||
useExisting, **kw)
|
useExisting, **kw)
|
||||||
|
|
||||||
def createPrincipal(self, pfName, userId, password, lastName,
|
def createPrincipal(self, pfName, userId, password, lastName,
|
||||||
|
|
|
@ -26,9 +26,13 @@ from zope.cachedescriptors.property import Lazy
|
||||||
from zope.i18n import translate
|
from zope.i18n import translate
|
||||||
|
|
||||||
from cybertools.browser.action import Action, actions
|
from cybertools.browser.action import Action, actions
|
||||||
|
from cybertools.composer.schema.field import Field
|
||||||
|
from cybertools.composer.schema.interfaces import ISchemaFactory
|
||||||
|
from cybertools.composer.schema.schema import Schema
|
||||||
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
|
from cybertools.stateful.interfaces import IStateful, IStatesDefinition
|
||||||
from loops.browser.common import BaseView
|
from loops.browser.common import BaseView
|
||||||
from loops.browser.concept import ConceptView
|
from loops.browser.concept import ConceptView
|
||||||
|
from loops.browser.form import ObjectForm, EditObject
|
||||||
from loops.expert.query import And, Or, State, Type, getObjects
|
from loops.expert.query import And, Or, State, Type, getObjects
|
||||||
from loops.expert.browser.search import search_template
|
from loops.expert.browser.search import search_template
|
||||||
from loops.security.common import checkPermission
|
from loops.security.common import checkPermission
|
||||||
|
@ -48,7 +52,7 @@ def registerStatesPortlet(controller, view, statesDefs,
|
||||||
cm = controller.macros
|
cm = controller.macros
|
||||||
stfs = [component.getAdapter(view.context, IStateful, name=std)
|
stfs = [component.getAdapter(view.context, IStateful, name=std)
|
||||||
for std in statesDefs]
|
for std in statesDefs]
|
||||||
cm.register(region, 'states', title=_(u'States'),
|
cm.register(region, 'states', title=_(u'Workflow'),
|
||||||
subMacro=template.macros['portlet_states'],
|
subMacro=template.macros['portlet_states'],
|
||||||
priority=priority, info=view, stfs=stfs)
|
priority=priority, info=view, stfs=stfs)
|
||||||
|
|
||||||
|
@ -79,13 +83,14 @@ class StateAction(Action):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def icon(self):
|
def icon(self):
|
||||||
icon = self.stateObject.icon or 'led%s.png' % self.stateObject.color
|
return self.stateObject.stateIcon
|
||||||
return 'cybertools.icons/' + icon
|
#icon = self.stateObject.icon or 'led%s.png' % self.stateObject.color
|
||||||
|
#return 'cybertools.icons/' + icon
|
||||||
|
|
||||||
|
|
||||||
def registerStatefulAction(std, msgFactory=_):
|
def registerStatefulAction(std, msgFactory=_):
|
||||||
actions.register('state.' + std, 'object', StateAction,
|
actions.register('state.' + std, 'object', StateAction,
|
||||||
definition = std,
|
definition=std,
|
||||||
cssClass='icon-action',
|
cssClass='icon-action',
|
||||||
msgFactory=msgFactory,
|
msgFactory=msgFactory,
|
||||||
)
|
)
|
||||||
|
@ -94,11 +99,67 @@ for std in statefulActions:
|
||||||
registerStatefulAction(std)
|
registerStatefulAction(std)
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeStateBase(object):
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def stateful(self):
|
||||||
|
return component.getAdapter(self.view.virtualTargetObject, IStateful,
|
||||||
|
name=self.definition)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def definition(self):
|
||||||
|
return self.request.form.get('stdef') or u''
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def action(self):
|
||||||
|
return self.request.form.get('action') or u''
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def transition(self):
|
||||||
|
return self.stateful.getStatesDefinition().transitions[self.action]
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def stateObject(self):
|
||||||
|
return self.stateful.getStateObject()
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeStateForm(ObjectForm, ChangeStateBase):
|
||||||
|
|
||||||
|
form_action = 'change_state_action'
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return template.macros['change_state']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def title(self):
|
||||||
|
return self.virtualTargetObject.title
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def schema(self):
|
||||||
|
# TODO: create schema directly, use field information specified
|
||||||
|
# in transition
|
||||||
|
commentsField = Field('comments', _(u'label_transition_comments'),
|
||||||
|
'textarea',
|
||||||
|
description=_(u'desc_transition_comments'))
|
||||||
|
fields = [commentsField]
|
||||||
|
return Schema(name='change_state', request=self.request,
|
||||||
|
manager=self, *fields)
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeState(EditObject, ChangeStateBase):
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
print '***', self.request.form
|
||||||
|
self.stateful.doTransition(self.action)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
#class StateQuery(ConceptView):
|
#class StateQuery(ConceptView):
|
||||||
class StateQuery(BaseView):
|
class StateQuery(BaseView):
|
||||||
|
|
||||||
template = template
|
template = template
|
||||||
|
|
||||||
form_action = 'execute_search_action'
|
form_action = 'execute_search_action'
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
set_schema="cybertools.stateful.interfaces.IStateful" />
|
set_schema="cybertools.stateful.interfaces.IStateful" />
|
||||||
</zope:class>
|
</zope:class>
|
||||||
|
|
||||||
<!-- views -->
|
<!-- views and form controllers -->
|
||||||
|
|
||||||
<browser:page
|
<browser:page
|
||||||
for="loops.interfaces.IConcept"
|
for="loops.interfaces.IConcept"
|
||||||
|
@ -91,6 +91,19 @@
|
||||||
class="loops.organize.stateful.browser.FilterAllStates"
|
class="loops.organize.stateful.browser.FilterAllStates"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<browser:page
|
||||||
|
name="change_state.html"
|
||||||
|
for="loops.interfaces.INode"
|
||||||
|
class="loops.organize.stateful.browser.ChangeStateForm"
|
||||||
|
permission="zope.ManageContent" />
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="change_state"
|
||||||
|
for="loops.browser.node.NodeView
|
||||||
|
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||||
|
factory="loops.organize.stateful.browser.ChangeState"
|
||||||
|
permission="zope.ManageContent" />
|
||||||
|
|
||||||
<!-- event handlers -->
|
<!-- event handlers -->
|
||||||
|
|
||||||
<zope:subscriber handler="loops.organize.stateful.base.handleTransition" />
|
<zope:subscriber handler="loops.organize.stateful.base.handleTransition" />
|
||||||
|
|
|
@ -59,6 +59,7 @@ def taskStates():
|
||||||
Transition('finish', 'finish', 'finished'),
|
Transition('finish', 'finish', 'finished'),
|
||||||
Transition('cancel', 'cancel', 'cancelled'),
|
Transition('cancel', 'cancel', 'cancelled'),
|
||||||
Transition('reopen', 're-open', 'draft'),
|
Transition('reopen', 're-open', 'draft'),
|
||||||
|
Transition('archive', 'archive', 'archived'),
|
||||||
initialState='draft')
|
initialState='draft')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -68,12 +68,10 @@
|
||||||
</metal:query>
|
</metal:query>
|
||||||
|
|
||||||
|
|
||||||
<!-- portlets -->
|
|
||||||
|
|
||||||
<metal:actions define-macro="portlet_states">
|
<metal:actions define-macro="portlet_states">
|
||||||
<div tal:repeat="stf macro/stfs">
|
<div tal:repeat="stf macro/stfs">
|
||||||
<div tal:condition="python:len(macro.stfs) > 1">
|
<div tal:condition="python:len(macro.stfs) > 1">
|
||||||
<span i18n:translate="">Workflow</span>
|
<span i18n:translate="">States Definition</span>
|
||||||
<span i18n:translate=""
|
<span i18n:translate=""
|
||||||
tal:content="stf/statesDefinition" />
|
tal:content="stf/statesDefinition" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,13 +79,19 @@
|
||||||
<b i18n:translate="">State</b>:
|
<b i18n:translate="">State</b>:
|
||||||
<span i18n:translate=""
|
<span i18n:translate=""
|
||||||
tal:content="stf/state" />
|
tal:content="stf/state" />
|
||||||
|
<img style="margin-bottom: -3px"
|
||||||
|
tal:define="stateObject stf/getStateObject"
|
||||||
|
tal:attributes="src string:$resourceBase/${stateObject/stateIcon}" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div><b i18n:translate="">Available Transitions</b>:
|
||||||
<div><b i18n:translate="">Available Transitions</b>:</div>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li tal:repeat="action stf/getAvailableTransitionsForUser">
|
<li tal:repeat="action stf/getAvailableTransitionsForUser">
|
||||||
<a i18n:translate=""
|
<a i18n:translate=""
|
||||||
tal:attributes="href string:change_state.html"
|
tal:define="baseUrl view/virtualTargetUrl;
|
||||||
|
url string:$baseUrl/change_state.html?action=${action/name}&stdef=${stf/statesDefinition}"
|
||||||
|
tal:attributes="href url;
|
||||||
|
onClick string:objectDialog('change_state', '$url');;
|
||||||
|
return false;"
|
||||||
tal:content="action/title" />
|
tal:content="action/title" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -96,4 +100,49 @@
|
||||||
</metal:actions>
|
</metal:actions>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:dialog define-macro="change_state">
|
||||||
|
<form name="stateful_changeState" method="post">
|
||||||
|
<div dojoType="dijit.layout.BorderContainer"
|
||||||
|
style="width: 70em; height: 600px">
|
||||||
|
<div dojoType="dijit.layout.ContentPane" region="top">
|
||||||
|
<h1><span i18n:translate="">State Transition</span> -
|
||||||
|
<span tal:content="view/title" />
|
||||||
|
</h1>
|
||||||
|
<div>
|
||||||
|
<span i18n:translate="">State</span>:
|
||||||
|
<span i18n:translate=""
|
||||||
|
tal:content="view/stateful/state" /> -
|
||||||
|
<span i18n:translate="">Transition</span>:
|
||||||
|
<span i18n:translate=""
|
||||||
|
tal:content="view/transition/title" />
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="form.action" value="change_state">
|
||||||
|
<input type="hidden" name="stdef"
|
||||||
|
tal:attributes="value request/form/stdef|nothing">
|
||||||
|
<input type="hidden" name="action"
|
||||||
|
tal:attributes="value request/form/action|nothing">
|
||||||
|
</div>
|
||||||
|
<div dojoType="dijit.layout.ContentPane" region="center">
|
||||||
|
<table cellpadding="3" class="form">
|
||||||
|
<tbody><tr><td colspan="5" style="padding-right: 15px">
|
||||||
|
<div id="form.fields">
|
||||||
|
<metal:fields use-macro="view/fieldRenderers/fields" />
|
||||||
|
</div>
|
||||||
|
</td></tr></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div dojoType="dijit.layout.ContentPane" region="bottom">
|
||||||
|
<metal:buttons define-slot="buttons">
|
||||||
|
<input value="Save" type="submit"
|
||||||
|
onClick="submit();; return false"
|
||||||
|
i18n:attributes="value">
|
||||||
|
<input type="button" value="Cancel" onClick="dialog.hide();"
|
||||||
|
i18n:attributes="value">
|
||||||
|
</metal:buttons>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</metal:dialog>
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
14
query.py
14
query.py
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Query management stuff.
|
Query management stuff.
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from BTrees.IOBTree import IOBTree
|
from BTrees.IOBTree import IOBTree
|
||||||
|
@ -33,6 +31,7 @@ from zope.intid.interfaces import IIntIds
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
from loops.common import AdapterBase
|
from loops.common import AdapterBase
|
||||||
from loops.interfaces import IConcept, IConceptSchema, ILoopsAdapter
|
from loops.interfaces import IConcept, IConceptSchema, ILoopsAdapter
|
||||||
|
from loops.interfaces import IOptions
|
||||||
from loops.security.common import canListObject
|
from loops.security.common import canListObject
|
||||||
from loops.type import TypeInterfaceSourceList
|
from loops.type import TypeInterfaceSourceList
|
||||||
from loops.versioning.util import getVersion
|
from loops.versioning.util import getVersion
|
||||||
|
@ -182,7 +181,7 @@ class ConceptQuery(BaseQuery):
|
||||||
|
|
||||||
# QueryConcept: concept objects that allow querying the database.
|
# QueryConcept: concept objects that allow querying the database.
|
||||||
|
|
||||||
class IQueryConcept(IConceptSchema, ILoopsAdapter):
|
class IQueryConcept(IConceptSchema, ILoopsAdapter, IOptions):
|
||||||
""" The schema for the query type.
|
""" The schema for the query type.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -194,13 +193,6 @@ class IQueryConcept(IConceptSchema, ILoopsAdapter):
|
||||||
default=u'',
|
default=u'',
|
||||||
required=False)
|
required=False)
|
||||||
|
|
||||||
options = schema.List(
|
|
||||||
title=_(u'Options'),
|
|
||||||
description=_(u'Additional settings.'),
|
|
||||||
value_type=schema.TextLine(),
|
|
||||||
default=[],
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class QueryConcept(AdapterBase):
|
class QueryConcept(AdapterBase):
|
||||||
|
|
||||||
|
|
4
type.py
4
type.py
|
@ -32,6 +32,7 @@ from cybertools.typology.type import BaseType, TypeManager
|
||||||
from cybertools.typology.interfaces import ITypeManager
|
from cybertools.typology.interfaces import ITypeManager
|
||||||
from loops.interfaces import ILoopsObject, IConcept, IResource
|
from loops.interfaces import ILoopsObject, IConcept, IResource
|
||||||
from loops.interfaces import ITypeConcept
|
from loops.interfaces import ITypeConcept
|
||||||
|
from loops.interfaces import IOptions
|
||||||
from loops.interfaces import IResourceAdapter, IFile, IExternalFile, IImage
|
from loops.interfaces import IResourceAdapter, IFile, IExternalFile, IImage
|
||||||
from loops.interfaces import ITextDocument, INote
|
from loops.interfaces import ITextDocument, INote
|
||||||
from loops.common import adapted
|
from loops.common import adapted
|
||||||
|
@ -276,7 +277,8 @@ class TypeInterfaceSourceList(object):
|
||||||
|
|
||||||
implements(schema.interfaces.IIterableSource)
|
implements(schema.interfaces.IIterableSource)
|
||||||
|
|
||||||
typeInterfaces = (ITypeConcept, IFile, IExternalFile, ITextDocument, INote)
|
typeInterfaces = (ITypeConcept, IFile, IExternalFile, ITextDocument, INote,
|
||||||
|
IOptions)
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
Loading…
Add table
Reference in a new issue