merge branch master

This commit is contained in:
Helmut Merz 2013-06-23 09:46:14 +02:00
commit f2fbcb52c3
26 changed files with 537 additions and 79 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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">&nbsp;</td></tr> <tr><td colspan="6">&nbsp;</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 />&nbsp;
</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.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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