[gen] Allow to use class Group as parameter of Search.group (soon, we will be able to get groups of groups of groups... of searches, to produce a tree of searches); refactored i18n-related code.

This commit is contained in:
Gaetan Delannay 2012-11-06 11:32:39 +01:00
parent fcb1d36da0
commit 0dd870c042
6 changed files with 324 additions and 560 deletions

View file

@ -5,7 +5,6 @@ import re, time, copy, sys, types, os, os.path, mimetypes, string, StringIO, \
from appy import Object
from appy.gen.layout import Table
from appy.gen.layout import defaultFieldLayouts
from appy.gen.po import PoMessage
from appy.gen.mail import sendNotification
from appy.gen.indexer import defaultIndexes, XhtmlTextExtractor
from appy.gen.utils import GroupDescr, Keywords, getClassName, SomeObjects
@ -191,7 +190,6 @@ class Group:
# (b) groupData is of the form <groupName>_<numberOfColumns>.
groupElems = groupData.rsplit('_', 1)
if len(groupElems) == 1:
# We have case (a)
res = Group(groupElems[0])
else:
try:
@ -208,36 +206,29 @@ class Group:
if self.master: return (self.master, self.masterValue)
if self.group: return self.group.getMasterData()
def generateLabels(self, messages, classDescr, walkedGroups):
def generateLabels(self, messages, classDescr, walkedGroups,
forSearch=False):
'''This method allows to generate all the needed i18n labels related to
this group. p_messages is the list of i18n p_messages that we are
currently building; p_classDescr is the descriptor of the class where
this group is defined.'''
this group. p_messages is the list of i18n p_messages (a PoMessages
instance) that we are currently building; p_classDescr is the
descriptor of the class where this group is defined. If p_forSearch
is True, this group is used for grouping searches, and not fields.'''
# A part of the group label depends on p_forSearch.
if forSearch: gp = 'searchgroup'
else: gp = 'group'
if self.hasLabel:
msgId = '%s_group_%s' % (classDescr.name, self.name)
poMsg = PoMessage(msgId, '', self.name, niceDefault=True)
if poMsg not in messages:
messages.append(poMsg)
classDescr.labelsToPropagate.append(poMsg)
msgId = '%s_%s_%s' % (classDescr.name, gp, self.name)
messages.append(msgId, self.name)
if self.hasDescr:
msgId = '%s_group_%s_descr' % (classDescr.name, self.name)
poMsg = PoMessage(msgId, '', ' ')
if poMsg not in messages:
messages.append(poMsg)
classDescr.labelsToPropagate.append(poMsg)
msgId = '%s_%s_%s_descr' % (classDescr.name, gp, self.name)
messages.append(msgId, ' ', nice=False)
if self.hasHelp:
msgId = '%s_group_%s_help' % (classDescr.name, self.name)
poMsg = PoMessage(msgId, '', ' ')
if poMsg not in messages:
messages.append(poMsg)
classDescr.labelsToPropagate.append(poMsg)
msgId = '%s_%s_%s_help' % (classDescr.name, gp, self.name)
messages.append(msgId, ' ', nice=False)
if self.hasHeaders:
for i in range(self.nbOfHeaders):
msgId = '%s_group_%s_col%d' % (classDescr.name, self.name, i+1)
poMsg = PoMessage(msgId, '', ' ')
if poMsg not in messages:
messages.append(poMsg)
classDescr.labelsToPropagate.append(poMsg)
msgId = '%s_%s_%s_col%d' % (classDescr.name, gp, self.name, i+1)
messages.append(msgId, ' ', nice=False)
walkedGroups.add(self)
if self.group and (self.group not in walkedGroups) and \
not self.group.label:
@ -302,7 +293,8 @@ class Search:
def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None,
default=False, **fields):
self.name = name
self.group = group # Searches may be visually grouped in the portlet
# Searches may be visually grouped in the portlet.
self.group = Group.get(group)
self.sortBy = sortBy
self.sortOrder = sortOrder
self.limit = limit
@ -905,24 +897,6 @@ class Type:
p_self type definition on p_obj.'''
setattr(obj, self.name, value)
def clone(self, forTool=True):
'''Returns a clone of myself. If p_forTool is True, the clone will be
adapted to its life into the tool.'''
res = copy.copy(self)
res.group = copy.copy(self.group)
res.page = copy.copy(self.page)
if not forTool: return res
res.show = True
# Set default layouts for all Tool fields
res.layouts = res.formatLayouts(None)
res.specificReadPermission = False
res.specificWritePermission = False
res.multiplicity = (0, self.multiplicity[1])
if callable(res.validator):
# We will not be able to call this function from the tool.
res.validator = None
return res
def callMethod(self, obj, method, raiseOnError=True):
'''This method is used to call a p_method on p_obj. p_method is part of
this type definition (ie a default method, the method of a Computed
@ -2025,18 +1999,6 @@ class Ref(Type):
if objects:
self.linkObject(obj, objects)
def clone(self, forTool=True):
'''Produces a clone of myself.'''
res = Type.clone(self, forTool)
res.back = copy.copy(self.back)
if not forTool: return res
res.link = True
res.add = False
res.back.attribute += 'DefaultValue'
res.back.show = False
res.select = None # Not callable from tool.
return res
def mayAdd(self, obj):
'''May the user create a new referred object from p_obj via this Ref?'''
# We can't (yet) do that on back references.

View file

@ -5,7 +5,7 @@
# ------------------------------------------------------------------------------
import types, copy
import appy.gen as gen
from po import PoMessage
import po
from model import ModelClass, toolFieldPrefixes
from utils import produceNiceMessage, getClassName
TABS = 4 # Number of blanks in a Python indentation.
@ -29,20 +29,6 @@ class ClassDescriptor(Descriptor):
def __init__(self, klass, orderedAttributes, generator):
Descriptor.__init__(self, klass, orderedAttributes, generator)
self.methods = '' # Needed method definitions will be generated here
# We remember here encountered pages and groups defined in the Appy
# type. Indeed, after having parsed all application classes, we will
# need to generate i18n labels for every child class of the class
# that declared pages and groups.
self.labelsToPropagate = [] #~[PoMessage]~ Some labels (like page,
# group or action names) need to be propagated in children classes
# (because they contain the class name). But at this time we don't know
# yet every sub-class. So we store those labels here; the Generator
# will propagate them later.
self.toolFieldsToPropagate = [] # For this class, some fields have
# been defined on the Tool class. Those fields need to be defined
# for child classes of this class as well, but at this time we don't
# know yet every sub-class. So we store field definitions here; the
# Generator will propagate them later.
self.name = getClassName(self.klass, generator.applicationName)
self.predefined = False
self.customized = False
@ -280,6 +266,10 @@ class FieldDescriptor:
self.fieldType = None
self.widgetType = None
def i18n(self, id, default, nice=True):
'''Shorthand for adding a new message into self.generator.labels.'''
self.generator.labels.append(id, default, nice=nice)
def __repr__(self):
return '<Field %s, %s>' % (self.fieldName, self.classDescr)
@ -291,7 +281,7 @@ class FieldDescriptor:
fullPrefix = prefix + 'For'
if fieldName.startswith(fullPrefix):
messageId = 'MSG_%s' % prefix
res = getattr(PoMessage, messageId)
res = getattr(po, messageId)
if res.find('%s') != -1:
# I must complete the message with the field name.
res = res % fieldName.split('_')[-1]
@ -302,18 +292,15 @@ class FieldDescriptor:
'''Gets the default label, description or help (depending on p_msgType)
for i18n message p_msgId.'''
default = ' '
produceNice = False
niceDefault = False
if isLabel:
produceNice = True
niceDefault = True
default = self.fieldName
# Some attributes need a specific predefined message
if isinstance(self.classDescr, ToolClassDescriptor):
default = self.getToolFieldMessage(self.fieldName)
if default != self.fieldName: produceNice = False
msg = PoMessage(msgId, '', default)
if produceNice:
msg.produceNiceDefault()
return msg
if default != self.fieldName: niceDefault = False
return msgId, default, niceDefault
def walkString(self):
'''How to generate an Appy String?'''
@ -322,18 +309,15 @@ class FieldDescriptor:
# Generate i18n messages for every possible value if the list
# of values is fixed.
for value in self.appyType.validator:
msgLabel = '%s_%s_list_%s' % (self.classDescr.name,
self.fieldName, value)
poMsg = PoMessage(msgLabel, '', value)
poMsg.produceNiceDefault()
self.generator.labels.append(poMsg)
label = '%s_%s_list_%s' % (self.classDescr.name,
self.fieldName, value)
self.i18n(label, value)
def walkAction(self):
'''Generates the i18n-related label.'''
if self.appyType.confirm:
label = '%s_%s_confirm' % (self.classDescr.name, self.fieldName)
msg = PoMessage(label, '', PoMessage.CONFIRM)
self.generator.labels.append(msg)
self.i18n(label, po.CONFIRM, nice=False)
def walkRef(self):
'''How to generate a Ref?'''
@ -343,23 +327,18 @@ class FieldDescriptor:
back = self.appyType.back
refClassName = getClassName(self.appyType.klass, self.applicationName)
if back.hasLabel:
backLabel = "%s_%s" % (refClassName, self.appyType.back.attribute)
poMsg = PoMessage(backLabel, '', self.appyType.back.attribute)
poMsg.produceNiceDefault()
self.generator.labels.append(poMsg)
backName = self.appyType.back.attribute
self.i18n('%s_%s' % (refClassName, backName), backName)
# Add the label for the confirm message if relevant
if self.appyType.addConfirm:
label = '%s_%s_addConfirm' % (self.classDescr.name, self.fieldName)
msg = PoMessage(label, '', PoMessage.CONFIRM)
self.generator.labels.append(msg)
self.i18n(label, po.CONFIRM, nice=False)
def walkPod(self):
# Add i18n-specific messages
if self.appyType.askAction:
label = '%s_%s_askaction' % (self.classDescr.name, self.fieldName)
msg = PoMessage(label, '', PoMessage.POD_ASKACTION)
self.generator.labels.append(msg)
self.classDescr.labelsToPropagate.append(msg)
self.i18n(label, po.POD_ASKACTION, nice=False)
# Add the POD-related fields on the Tool
self.generator.tool.addPodRelatedFields(self)
@ -367,9 +346,7 @@ class FieldDescriptor:
# Add i18n-specific messages
for name, field in self.appyType.fields:
label = '%s_%s_%s' % (self.classDescr.name, self.fieldName, name)
msg = PoMessage(label, '', name)
msg.produceNiceDefault()
self.generator.labels.append(msg)
self.i18n(label, name)
def walkCalendar(self):
# Add i18n-specific messages
@ -377,9 +354,7 @@ class FieldDescriptor:
if not isinstance(eTypes, list) and not isinstance(eTypes, tuple):return
for et in self.appyType.eventTypes:
label = '%s_%s_event_%s' % (self.classDescr.name,self.fieldName,et)
msg = PoMessage(label, '', et)
msg.produceNiceDefault()
self.generator.labels.append(msg)
self.i18n(label, et)
def walkAppyType(self):
'''Walks into the Appy type definition and gathers data about the
@ -389,39 +364,33 @@ class FieldDescriptor:
if self.appyType.indexed and (self.fieldName != 'title'):
self.classDescr.addIndexMethod(self)
# i18n labels
messages = self.generator.labels
if not self.appyType.label:
# Create labels for generating them in i18n files, only if required.
i18nPrefix = "%s_%s" % (self.classDescr.name, self.fieldName)
i18nPrefix = '%s_%s' % (self.classDescr.name, self.fieldName)
if self.appyType.hasLabel:
messages.append(self.produceMessage(i18nPrefix))
self.i18n(*self.produceMessage(i18nPrefix))
if self.appyType.hasDescr:
descrId = i18nPrefix + '_descr'
messages.append(self.produceMessage(descrId,isLabel=False))
self.i18n(*self.produceMessage(descrId,isLabel=False))
if self.appyType.hasHelp:
helpId = i18nPrefix + '_help'
messages.append(self.produceMessage(helpId, isLabel=False))
self.i18n(*self.produceMessage(helpId, isLabel=False))
# Create i18n messages linked to pages and phases, only if there is more
# than one page/phase for the class.
ppMsgs = []
if len(self.classDescr.getPhases()) > 1:
# Create the message for the name of the phase
phaseName = self.appyType.page.phase
msgId = '%s_phase_%s' % (self.classDescr.name, phaseName)
ppMsgs.append(PoMessage(msgId, '', produceNiceMessage(phaseName)))
self.i18n(msgId, phaseName)
if len(self.classDescr.getPages()) > 1:
# Create the message for the name of the page
pageName = self.appyType.page.name
msgId = '%s_page_%s' % (self.classDescr.name, pageName)
ppMsgs.append(PoMessage(msgId, '', produceNiceMessage(pageName)))
for poMsg in ppMsgs:
if poMsg not in messages:
messages.append(poMsg)
self.classDescr.labelsToPropagate.append(poMsg)
self.i18n(msgId, pageName)
# Create i18n messages linked to groups
group = self.appyType.group
if group and not group.label:
group.generateLabels(messages, self.classDescr, set())
group.generateLabels(self.generator.labels, self.classDescr, set())
# Manage things which are specific to String types
if self.appyType.type == 'String': self.walkString()
# Manage things which are specific to Actions

View file

@ -3,9 +3,9 @@ import os, os.path, re, sys, parser, symbol, token, types
import appy.pod, appy.pod.renderer
from appy.shared.utils import FolderDeleter
import appy.gen as gen
from po import PoMessage, PoFile, PoParser
import po
from descriptors import *
from utils import produceNiceMessage, getClassName
from utils import getClassName
from model import ModelClass, User, Group, Tool, Translation, Page
# ------------------------------------------------------------------------------
@ -365,9 +365,13 @@ class ZopeGenerator(Generator):
self.translation = TranslationClassDescriptor(Translation, self)
self.page = PageClassDescriptor(Page, self)
# i18n labels to generate
self.labels = [] # i18n labels
self.labels = po.PoMessages()
self.referers = {}
def i18n(self, id, default, nice=True):
'''Shorthand for adding a new message into self.labels.'''
self.labels.append(id, default, nice=nice)
versionRex = re.compile('(.*?\s+build)\s+(\d+)')
def initialize(self):
# Determine version number
@ -387,175 +391,20 @@ class ZopeGenerator(Generator):
for fileName in os.listdir(i18nFolder):
name, ext = os.path.splitext(fileName)
if ext in self.poExtensions:
poParser = PoParser(os.path.join(i18nFolder, fileName))
poParser = po.PoParser(os.path.join(i18nFolder, fileName))
self.i18nFiles[fileName] = poParser.parse()
def finalize(self):
# Some useful aliases
msg = PoMessage
app = self.applicationName
# Some global i18n messages
poMsg = msg(app, '', app); poMsg.produceNiceDefault()
self.labels += [poMsg,
msg('app_name', '', msg.APP_NAME),
msg('workflow_state', '', msg.WORKFLOW_STATE),
msg('appy_title', '', msg.APPY_TITLE),
msg('data_change', '', msg.DATA_CHANGE),
msg('modified_field', '', msg.MODIFIED_FIELD),
msg('previous_value', '', msg.PREVIOUS_VALUE),
msg('phase', '', msg.PHASE),
msg('workflow_comment', '', msg.WORKFLOW_COMMENT),
msg('choose_a_value', '', msg.CHOOSE_A_VALUE),
msg('choose_a_doc', '', msg.CHOOSE_A_DOC),
msg('min_ref_violated', '', msg.MIN_REF_VIOLATED),
msg('max_ref_violated', '', msg.MAX_REF_VIOLATED),
msg('no_ref', '', msg.REF_NO),
msg('add_ref', '', msg.REF_ADD),
msg('action_ok', '', msg.ACTION_OK),
msg('action_ko', '', msg.ACTION_KO),
msg('move_up', '', msg.REF_MOVE_UP),
msg('move_down', '', msg.REF_MOVE_DOWN),
msg('query_create', '', msg.QUERY_CREATE),
msg('query_import', '', msg.QUERY_IMPORT),
msg('query_no_result', '', msg.QUERY_NO_RESULT),
msg('query_consult_all', '', msg.QUERY_CONSULT_ALL),
msg('import_title', '', msg.IMPORT_TITLE),
msg('import_show_hide', '', msg.IMPORT_SHOW_HIDE),
msg('import_already', '', msg.IMPORT_ALREADY),
msg('import_many', '', msg.IMPORT_MANY),
msg('import_done', '', msg.IMPORT_DONE),
msg('search_title', '', msg.SEARCH_TITLE),
msg('search_button', '', msg.SEARCH_BUTTON),
msg('search_objects', '', msg.SEARCH_OBJECTS),
msg('search_results', '', msg.SEARCH_RESULTS),
msg('search_results_descr', '', ' '),
msg('search_new', '', msg.SEARCH_NEW),
msg('search_from', '', msg.SEARCH_FROM),
msg('search_to', '', msg.SEARCH_TO),
msg('search_or', '', msg.SEARCH_OR),
msg('search_and', '', msg.SEARCH_AND),
msg('ref_invalid_index', '', msg.REF_INVALID_INDEX),
msg('bad_long', '', msg.BAD_LONG),
msg('bad_float', '', msg.BAD_FLOAT),
msg('bad_date', '', msg.BAD_DATE),
msg('bad_email', '', msg.BAD_EMAIL),
msg('bad_url', '', msg.BAD_URL),
msg('bad_alphanumeric', '', msg.BAD_ALPHANUMERIC),
msg('bad_select_value', '', msg.BAD_SELECT_VALUE),
msg('select_delesect', '', msg.SELECT_DESELECT),
msg('no_elem_selected', '', msg.NO_SELECTION),
msg('object_edit', '', msg.EDIT),
msg('object_delete', '', msg.DELETE),
msg('object_unlink', '', msg.UNLINK),
msg('delete_confirm', '', msg.DELETE_CONFIRM),
msg('unlink_confirm', '', msg.UNLINK_CONFIRM),
msg('delete_done', '', msg.DELETE_DONE),
msg('unlink_done', '', msg.UNLINK_DONE),
msg('goto_first', '', msg.GOTO_FIRST),
msg('goto_previous', '', msg.GOTO_PREVIOUS),
msg('goto_next', '', msg.GOTO_NEXT),
msg('goto_last', '', msg.GOTO_LAST),
msg('goto_source', '', msg.GOTO_SOURCE),
msg('whatever', '', msg.WHATEVER),
msg('yes', '', msg.YES),
msg('no', '', msg.NO),
msg('field_required', '', msg.FIELD_REQUIRED),
msg('field_invalid', '', msg.FIELD_INVALID),
msg('file_required', '', msg.FILE_REQUIRED),
msg('image_required', '', msg.IMAGE_REQUIRED),
msg('odt', '', msg.FORMAT_ODT),
msg('pdf', '', msg.FORMAT_PDF),
msg('doc', '', msg.FORMAT_DOC),
msg('rtf', '', msg.FORMAT_RTF),
msg('front_page_text', '', msg.FRONT_PAGE_TEXT),
msg('captcha_text', '', msg.CAPTCHA_TEXT),
msg('bad_captcha', '', msg.BAD_CAPTCHA),
msg('app_login', '', msg.LOGIN),
msg('app_connect', '', msg.CONNECT),
msg('app_logout', '', msg.LOGOUT),
msg('app_password', '', msg.PASSWORD),
msg('app_home', '', msg.HOME),
msg('login_reserved', '', msg.LOGIN_RESERVED),
msg('login_in_use', '', msg.LOGIN_IN_USE),
msg('login_ko', '', msg.LOGIN_KO),
msg('login_ok', '', msg.LOGIN_OK),
msg('password_too_short', '', msg.PASSWORD_TOO_SHORT),
msg('passwords_mismatch', '', msg.PASSWORDS_MISMATCH),
msg('object_save', '', msg.SAVE),
msg('object_saved', '', msg.SAVED),
msg('validation_error', '', msg.ERROR),
msg('object_cancel', '', msg.CANCEL),
msg('object_canceled', '', msg.CANCELED),
msg('enable_cookies', '', msg.ENABLE_COOKIES),
msg('page_previous', '', msg.PAGE_PREVIOUS),
msg('page_next', '', msg.PAGE_NEXT),
msg('forgot_password', '', msg.FORGOT_PASSWORD),
msg('ask_password_reinit', '', msg.ASK_PASSWORD_REINIT),
msg('wrong_password_reinit','', msg.WRONG_PASSWORD_REINIT),
msg('reinit_mail_sent', '', msg.REINIT_MAIL_SENT),
msg('reinit_password', '', msg.REINIT_PASSWORD),
msg('reinit_password_body', '', msg.REINIT_PASSWORD_BODY),
msg('new_password', '', msg.NEW_PASSWORD),
msg('new_password_body', '', msg.NEW_PASSWORD_BODY),
msg('new_password_sent', '', msg.NEW_PASSWORD_SENT),
msg('last_user_access', '', msg.LAST_USER_ACCESS),
msg('object_history', '', msg.OBJECT_HISTORY),
msg('object_created_by', '', msg.OBJECT_CREATED_BY),
msg('object_created_on', '', msg.OBJECT_CREATED_ON),
msg('object_modified_on', '', msg.OBJECT_MODIFIED_ON),
msg('object_action', '', msg.OBJECT_ACTION),
msg('object_author', '', msg.OBJECT_AUTHOR),
msg('action_date', '', msg.ACTION_DATE),
msg('action_comment', '', msg.ACTION_COMMENT),
msg('day_Mon_short', '', msg.DAY_MON_SHORT),
msg('day_Tue_short', '', msg.DAY_TUE_SHORT),
msg('day_Wed_short', '', msg.DAY_WED_SHORT),
msg('day_Thu_short', '', msg.DAY_THU_SHORT),
msg('day_Fri_short', '', msg.DAY_FRI_SHORT),
msg('day_Sat_short', '', msg.DAY_SAT_SHORT),
msg('day_Sun_short', '', msg.DAY_SUN_SHORT),
msg('day_Mon', '', msg.DAY_MON),
msg('day_Tue', '', msg.DAY_TUE),
msg('day_Wed', '', msg.DAY_WED),
msg('day_Thu', '', msg.DAY_THU),
msg('day_Fri', '', msg.DAY_FRI),
msg('day_Sat', '', msg.DAY_SAT),
msg('day_Sun', '', msg.DAY_SUN),
msg('ampm_am', '', msg.AMPM_AM),
msg('ampm_pm', '', msg.AMPM_PM),
msg('month_Jan_short', '', msg.MONTH_JAN_SHORT),
msg('month_Feb_short', '', msg.MONTH_FEB_SHORT),
msg('month_Mar_short', '', msg.MONTH_MAR_SHORT),
msg('month_Apr_short', '', msg.MONTH_APR_SHORT),
msg('month_May_short', '', msg.MONTH_MAY_SHORT),
msg('month_Jun_short', '', msg.MONTH_JUN_SHORT),
msg('month_Jul_short', '', msg.MONTH_JUL_SHORT),
msg('month_Aug_short', '', msg.MONTH_AUG_SHORT),
msg('month_Sep_short', '', msg.MONTH_SEP_SHORT),
msg('month_Oct_short', '', msg.MONTH_OCT_SHORT),
msg('month_Nov_short', '', msg.MONTH_NOV_SHORT),
msg('month_Dec_short', '', msg.MONTH_DEC_SHORT),
msg('month_Jan', '', msg.MONTH_JAN),
msg('month_Feb', '', msg.MONTH_FEB),
msg('month_Mar', '', msg.MONTH_MAR),
msg('month_Apr', '', msg.MONTH_APR),
msg('month_May', '', msg.MONTH_MAY),
msg('month_Jun', '', msg.MONTH_JUN),
msg('month_Jul', '', msg.MONTH_JUL),
msg('month_Aug', '', msg.MONTH_AUG),
msg('month_Sep', '', msg.MONTH_SEP),
msg('month_Oct', '', msg.MONTH_OCT),
msg('month_Nov', '', msg.MONTH_NOV),
msg('month_Dec', '', msg.MONTH_DEC),
msg('today', '', msg.TODAY),
msg('which_event', '', msg.WHICH_EVENT),
msg('event_span', '', msg.EVENT_SPAN),
msg('del_next_events', '', msg.DEL_NEXT_EVENTS),
]
# Create a label for every role added by this application
# Add a label for the application name
self.i18n(self.applicationName, self.applicationName)
# Add default Appy i18n messages
for id, default in po.appyLabels:
self.i18n(id, default, nice=False)
# Add a label for every role added by this application (we ensure role
# 'Manager' was added even if not mentioned anywhere).
self.i18n('role_Manager', 'Manager')
for role in self.getAllUsedRoles():
self.labels.append(msg('role_%s' % role.name,'', role.name,
niceDefault=True))
self.i18n('role_%s' % role.name, role.name)
# Create basic files (config.py, etc)
self.generateTool()
self.generateInit()
@ -570,23 +419,16 @@ class ZopeGenerator(Generator):
f = open(initFile, 'w')
f.write('')
f.close()
# Decline i18n labels into versions for child classes
for classDescr in self.classes:
for poMsg in classDescr.labelsToPropagate:
for childDescr in classDescr.getChildren():
childMsg = poMsg.clone(classDescr.name, childDescr.name)
if childMsg not in self.labels:
self.labels.append(childMsg)
# Generate i18n pot file
potFileName = '%s.pot' % self.applicationName
if self.i18nFiles.has_key(potFileName):
potFile = self.i18nFiles[potFileName]
else:
fullName = os.path.join(self.application, 'tr', potFileName)
potFile = PoFile(fullName)
potFile = po.PoFile(fullName)
self.i18nFiles[potFileName] = potFile
# We update the POT file with our list of automatically managed labels.
removedLabels = potFile.update(self.labels, self.options.i18nClean,
removedLabels = potFile.update(self.labels.get(),self.options.i18nClean,
not self.options.i18nSort)
if removedLabels:
print 'Warning: %d messages were removed from translation ' \
@ -599,7 +441,7 @@ class ZopeGenerator(Generator):
nbOfPages = int(len(potFile.messages)/self.config.translationsPerPage)+1
for i in range(nbOfPages):
msgId = '%s_page_%d' % (self.translation.name, i+2)
pageLabels.append(msg(msgId, '', 'Page %d' % (i+2)))
pageLabels.append(po.PoMessage(msgId, '', 'Page %d' % (i+2)))
potFile.update(pageLabels, keepExistingOrder=False)
potFile.generate()
# Generate i18n po files
@ -611,7 +453,7 @@ class ZopeGenerator(Generator):
poFile = self.i18nFiles[poFileName]
else:
fullName = os.path.join(self.application, 'tr', poFileName)
poFile = PoFile(fullName)
poFile = po.PoFile(fullName)
self.i18nFiles[poFileName] = poFile
poFile.update(potFile.messages, self.options.i18nClean,
not self.options.i18nSort)
@ -874,11 +716,8 @@ class ZopeGenerator(Generator):
def generateTool(self):
'''Generates the tool that corresponds to this application.'''
Msg = PoMessage
# Create Tool-related i18n-related messages
msg = Msg(self.tool.name, '', Msg.CONFIG % self.applicationName)
self.labels.append(msg)
self.i18n(self.tool.name, po.CONFIG % self.applicationName, nice=False)
# Tune the Ref field between Tool->User and Group->User
Tool.users.klass = User
if self.user.customized:
@ -889,8 +728,8 @@ class ZopeGenerator(Generator):
for klass in (self.user, self.group, self.translation, self.page):
klassType = klass.name[len(self.applicationName):]
klass.generateSchema()
self.labels += [ Msg(klass.name, '', klassType),
Msg('%s_plural' % klass.name,'', klass.name+'s')]
self.i18n(klass.name, klassType, nice=False)
self.i18n('%s_plural' % klass.name, klass.name+'s', nice=False)
repls = self.repls.copy()
if klass.isFolder():
parents = 'BaseMixin, Folder'
@ -904,21 +743,16 @@ class ZopeGenerator(Generator):
self.copyFile('Class.pyt', repls, destName='%s.py' % klass.name)
# Before generating the Tool class, finalize it with query result
# columns, with fields to propagate, workflow-related fields.
# columns, search-related and import-related fields.
for classDescr in self.getClasses(include='allButTool'):
for fieldName, fieldType in classDescr.toolFieldsToPropagate:
for childDescr in classDescr.getChildren():
childFieldName = fieldName % childDescr.name
fieldType.group = childDescr.klass.__name__
self.tool.addField(childFieldName, fieldType)
if classDescr.isRoot():
# We must be able to configure query results from the tool.
self.tool.addQueryResultColumns(classDescr)
# Add the search-related fields.
self.tool.addSearchRelatedFields(classDescr)
importMean = classDescr.getCreateMean('Import')
if importMean:
self.tool.addImportRelatedFields(classDescr)
if not classDescr.isRoot(): continue
# We must be able to configure query results from the tool.
self.tool.addQueryResultColumns(classDescr)
# Add the search-related fields.
self.tool.addSearchRelatedFields(classDescr)
importMean = classDescr.getCreateMean('Import')
if importMean:
self.tool.addImportRelatedFields(classDescr)
self.tool.generateSchema()
# Generate the Tool class
@ -939,37 +773,27 @@ class ZopeGenerator(Generator):
baseClass = isFolder and 'Folder' or 'SimpleItem'
icon = isFolder and 'folder.gif' or 'object.gif'
parents = 'BaseMixin, %s' % baseClass
classDoc = classDescr.klass.__doc__ or 'Appy class.'
classDoc = k.__doc__ or 'Appy class.'
repls = self.repls.copy()
classDescr.generateSchema()
repls.update({
'parents': parents, 'className': classDescr.klass.__name__,
'parents': parents, 'className': k.__name__,
'genClassName': classDescr.name, 'baseMixin':'BaseMixin',
'classDoc': classDoc, 'applicationName': self.applicationName,
'methods': classDescr.methods, 'icon':icon})
fileName = '%s.py' % classDescr.name
# Create i18n labels (class name and plural form)
poMsg = PoMessage(classDescr.name, '', classDescr.klass.__name__)
poMsg.produceNiceDefault()
self.labels.append(poMsg)
poMsgPl = PoMessage('%s_plural' % classDescr.name, '',
classDescr.klass.__name__+'s')
poMsgPl.produceNiceDefault()
self.labels.append(poMsgPl)
self.i18n(classDescr.name, k.__name__)
self.i18n('%s_plural' % classDescr.name, k.__name__+'s')
# Create i18n labels for searches
for search in classDescr.getSearches(classDescr.klass):
searchLabel = '%s_search_%s' % (classDescr.name, search.name)
labels = [searchLabel, '%s_descr' % searchLabel]
if search.group:
grpLabel = '%s_searchgroup_%s' % (classDescr.name, search.group)
labels += [grpLabel, '%s_descr' % grpLabel]
for label in labels:
default = ' '
if label == searchLabel: default = search.name
poMsg = PoMessage(label, '', default)
poMsg.produceNiceDefault()
if poMsg not in self.labels:
self.labels.append(poMsg)
for search in classDescr.getSearches(k):
label = '%s_search_%s' % (classDescr.name, search.name)
self.i18n(label, search.name)
self.i18n('%s_descr' % label, ' ', nice=False)
# Generate labels for groups of searches
if search.group and not search.group.label:
search.group.generateLabels(self.labels, classDescr, set(),
forSearch=True)
# Generate the resulting Zope class.
self.copyFile('Class.pyt', repls, destName=fileName)
@ -983,31 +807,23 @@ class ZopeGenerator(Generator):
# Add i18n messages for states
for name in dir(wfDescr.klass):
if not isinstance(getattr(wfDescr.klass, name), gen.State): continue
poMsg = PoMessage('%s_%s' % (wfName, name), '', name)
poMsg.produceNiceDefault()
self.labels.append(poMsg)
self.i18n('%s_%s' % (wfName, name), name)
# Add i18n messages for transitions
for name in dir(wfDescr.klass):
transition = getattr(wfDescr.klass, name)
if not isinstance(transition, gen.Transition): continue
if transition.show:
poMsg = PoMessage('%s_%s' % (wfName, name), '', name)
poMsg.produceNiceDefault()
self.labels.append(poMsg)
self.i18n('%s_%s' % (wfName, name), name)
if transition.show and transition.confirm:
# We need to generate a label for the message that will be shown
# in the confirm popup.
label = '%s_%s_confirm' % (wfName, name)
poMsg = PoMessage(label, '', PoMessage.CONFIRM)
self.labels.append(poMsg)
self.i18n('%s_%s_confirm'%(wfName, name),po.CONFIRM, nice=False)
if transition.notify:
# Appy will send a mail when this transition is triggered.
# So we need 2 i18n labels: one for the mail subject and one for
# the mail body.
subjectLabel = '%s_%s_mail_subject' % (wfName, name)
poMsg = PoMessage(subjectLabel, '', PoMessage.EMAIL_SUBJECT)
self.labels.append(poMsg)
bodyLabel = '%s_%s_mail_body' % (wfName, name)
poMsg = PoMessage(bodyLabel, '', PoMessage.EMAIL_BODY)
self.labels.append(poMsg)
self.i18n('%s_%s_mail_subject' % (wfName, name),
po.EMAIL_SUBJECT, nice=False)
self.i18n('%s_%s_mail_body' % (wfName, name),
po.EMAIL_BODY, nice=False)
# ------------------------------------------------------------------------------

View file

@ -127,7 +127,7 @@ class Table(LayoutElement):
# We need to create a Table instance from another Table instance,
# given in p_other. In this case, we ignore previous params.
if derivedType != None:
# We will not simply clone p_other. If p_derivedType is:
# We will not simply mimic p_other. If p_derivedType is:
# - "view", p_derivedFrom is an "edit" layout, and we must
# create the corresponding "view" layout;
# - "cell", p_derivedFrom is a "view" layout, and we must

View file

@ -675,7 +675,7 @@ class ToolMixin(BaseMixin):
def getSearches(self, contentType):
'''Returns an object with 2 attributes:
* "searches" stores the searches that are defined for p_contentType;
* "default" stores the search defined as default one.
* "default" stores the search defined as the default one.
Every item representing a search is a dict containing info about a
search or about a group of searches.
'''
@ -685,25 +685,27 @@ class ToolMixin(BaseMixin):
visitedGroups = {} # Names of already visited search groups
for search in ClassDescriptor.getSearches(appyClass):
# Determine first group label, we will need it.
groupLabel = ''
groupLabel = None
groupName = None
if search.group:
groupLabel = '%s_searchgroup_%s' % (contentType, search.group)
groupName = search.group.name
groupLabel = '%s_searchgroup_%s' % (contentType, groupName)
# Add an item representing the search group if relevant
if search.group and (search.group not in visitedGroups):
group = {'name': search.group, 'isGroup': True,
if groupName and (groupName not in visitedGroups):
group = {'name': groupName, 'isGroup': True,
'labelId': groupLabel, 'searches': [],
'label': self.translate(groupLabel),
'descr': self.translate('%s_descr' % groupLabel),
}
searches.append(group)
visitedGroups[search.group] = group
visitedGroups[groupName] = group
# Add the search itself
searchLabel = '%s_search_%s' % (contentType, search.name)
dSearch = {'name': search.name, 'isGroup': False,
'label': self.translate(searchLabel),
'descr': self.translate('%s_descr' % searchLabel)}
if search.group:
visitedGroups[search.group]['searches'].append(dSearch)
if groupName:
visitedGroups[groupName]['searches'].append(dSearch)
else:
searches.append(dSearch)
if search.default:

415
gen/po.py
View file

@ -21,197 +21,204 @@ msgstr ""
fallbacks = {'en': 'en-us en-ca',
'fr': 'fr-be fr-ca fr-lu fr-mc fr-ch fr-fr'}
# Standard Appy labels with their default value in english ---------------------
appyLabels = [
('app_name', 'Appy'),
('workflow_state', 'state'),
('workflow_comment', 'Optional comment'),
('appy_title', 'Title'),
('data_change', 'Data change'),
('modified_field', 'Modified field'),
('previous_value', 'Previous value'),
('phase', 'phase'),
('choose_a_value', ' - '),
('choose_a_doc', '[ Documents ]'),
('min_ref_violated', 'You must choose more elements here.'),
('max_ref_violated', 'Too much elements are selected here.'),
('no_ref', 'No object.'),
('add_ref', 'Add a new one'),
('action_ok', 'The action has been successfully executed.'),
('action_ko', 'A problem occurred while executing the action.'),
('move_up', 'Move up'),
('move_down', 'Move down'),
('query_create', 'create'),
('query_import', 'import'),
('query_no_result', 'Nothing to see for the moment.'),
('query_consult_all', 'consult all'),
('import_title', 'Importing data into your application'),
('import_show_hide', 'Show / hide alreadly imported elements.'),
('import_already', 'Already imported.'),
('import_many', 'Import selected elements'),
('import_done', 'Import terminated successfully.'),
('search_title', 'Advanced search'),
('search_button', 'Search'),
('search_objects', 'Search objects of this type.'),
('search_results', 'Search results'),
('search_results_descr', ' '),
('search_new', 'New search'),
('search_from', 'From'),
('search_to', 'to'),
('search_or', 'or'),
('search_and', 'and'),
('ref_invalid_index', 'No move occurred: please specify a valid number.'),
('bad_long', 'An integer value is expected; do not enter any space.'),
('bad_float', 'A floating-point number is expected; use the dot as decimal ' \
'separator, not a comma; do not enter any space.'),
('bad_date', 'Please specify a valid date.'),
('bad_email', 'Please enter a valid email.'),
('bad_url', 'Please enter a valid URL.'),
('bad_alphanumeric', 'Please enter a valid alphanumeric value.'),
('bad_select_value', 'The value is not among possible values for this field.'),
('select_delesect', '(Un)select all'),
('no_elem_selected', 'You must select at least one element.'),
('object_edit', 'Edit'),
('object_delete', 'Delete'),
('object_unlink', 'Unlink'),
('delete_confirm', 'Are you sure you want to delete this element?'),
('unlink_confirm', 'Are you sure you want to unlink this element?'),
('delete_done', 'The element has been deleted.'),
('unlink_done', 'The element has been unlinked.'),
('goto_first', 'Go to top'),
('goto_previous', 'Go to previous'),
('goto_next', 'Go to next'),
('goto_last', 'Go to end'),
('goto_source', 'Go back'),
('whatever', 'Whatever'),
('yes', 'Yes'),
('no', 'No'),
('field_required', 'Please fill this field.'),
('field_invalid', 'Please fill or correct this.'),
('file_required', 'Please select a file.'),
('image_required', 'The uploaded file must be an image.'),
('odt', 'ODT'),
('pdf', 'PDF'),
('doc', 'DOC'),
('rtf', 'RTF'),
('front_page_text', 'Welcome to this Appy-powered site.'),
('captcha_text', 'Please type "${text}" (without the double quotes) in the ' \
'field besides, but without the character at position ' \
'${number}.'),
('bad_captcha', 'The code was not correct. Please try again.'),
('app_login', 'Login'),
('app_connect', 'Log in'),
('app_logout', 'Logout'),
('app_password', 'Password'),
('app_home', 'Home'),
('login_reserved', 'This login is reserved.'),
('login_in_use', 'This login is already in use.'),
('login_ko', 'Welcome! You are now logged in.'),
('login_ok', 'Login failed.'),
('password_too_short', 'Passwords must contain at least ${nb} characters.'),
('passwords_mismatch', 'Passwords do not match.'),
('object_save', 'Save'),
('object_saved', 'Changes saved.'),
('validation_error', 'Please correct the indicated errors.'),
('object_cancel', 'Cancel'),
('object_canceled', 'Changes canceled.'),
('enable_cookies', 'You must enable cookies before you can log in.'),
('page_previous', 'Previous page'),
('page_next', 'Next page'),
('forgot_password', 'Forgot password?'),
('ask_password_reinit', 'Ask new password'),
('wrong_password_reinit', 'Something went wrong. First possibility: you ' \
'have already clicked on the link (maybe have you double-clicked?) ' \
'and your password has already been re-initialized. Please check ' \
'that you haven\'t received your new password in another email. ' \
'Second possibility: the link that you received in your mailer was ' \
'splitted on several lines. In this case, please re-type the link in ' \
'one single line and retry. Third possibility: you have waited too ' \
'long and your request has expired, or a technical error occurred. ' \
'In this case, please try again to ask a new password from the start.'),
('reinit_mail_sent', 'A mail has been sent to you. Please follow the ' \
'instructions from this email.'),
('reinit_password', 'Password re-initialisation'),
('reinit_password_body', 'Hello,<br/><br/>A password re-initialisation ' \
'has been requested, tied to this email address, for the website ' \
'${siteUrl}. If you are not at the origin of this request, please ' \
'ignore this email. Else, click on the link below to receive a new ' \
'password.<br/><br/>${url}'),
('new_password', 'Your new password'),
('new_password_body', 'Hello,<br/><br/>The new password you have ' \
'requested for website ${siteUrl} is ${password}<br/>' \
'<br/>Best regards.'),
('new_password_sent', 'Your new password has been sent to you by email.'),
('last_user_access', 'Last access'),
('object_history', 'History'),
('object_created_by', 'By'),
('object_created_on', 'On'),
('object_modified_on', 'Last updated on'),
('object_action', 'Action'),
('object_author', 'Author'),
('action_date', 'Date'),
('action_comment', 'Comment'),
('day_Mon_short', 'Mon'),
('day_Tue_short', 'Tue'),
('day_Wed_short', 'Wed'),
('day_Thu_short', 'Thu'),
('day_Fri_short', 'Fri'),
('day_Sat_short', 'Sat'),
('day_Sun_short', 'Sun'),
('day_Mon', 'Monday'),
('day_Tue', 'Tuesday'),
('day_Wed', 'Wednesday'),
('day_Thu', 'Thursday'),
('day_Fri', 'Friday'),
('day_Sat', 'Saturday'),
('day_Sun', 'Sunday'),
('ampm_am', 'AM'),
('ampm_pm', 'PM'),
('month_Jan_short', 'Jan'),
('month_Feb_short', 'Feb'),
('month_Mar_short', 'Mar'),
('month_Apr_short', 'Apr'),
('month_May_short', 'May'),
('month_Jun_short', 'Jun'),
('month_Jul_short', 'Jul'),
('month_Aug_short', 'Aug'),
('month_Sep_short', 'Sep'),
('month_Oct_short', 'Oct'),
('month_Nov_short', 'Nov'),
('month_Dec_short', 'Dec'),
('month_Jan', 'January'),
('month_Feb', 'February'),
('month_Mar', 'March'),
('month_Apr', 'April'),
('month_May', 'May'),
('month_Jun', 'June'),
('month_Jul', 'July'),
('month_Aug', 'Augustus'),
('month_Sep', 'September'),
('month_Oct', 'October'),
('month_Nov', 'November'),
('month_Dec', 'December'),
('today', 'Today'),
('which_event', 'Which event type would you like to create?'),
('event_span', 'Extend the event on the following number of days (leave ' \
'blank to create an event on the current day only):'),
('del_next_events', 'Also delete successive events of the same type.'),
]
# Some default values for labels whose ids are not fixed (so they can't be
# included in the previous variable).
CONFIG = "Configuration panel for product '%s'"
# The following messages (starting with MSG_) correspond to tool
# attributes added for every gen-class (warning: the message IDs correspond
# to MSG_<attributePrefix>).
MSG_podTemplate = "POD template for field '%s'"
MSG_formats = "Output format(s) for field '%s'"
MSG_resultColumns = "Columns to display while showing query results"
MSG_enableAdvancedSearch = "Enable advanced search"
MSG_numberOfSearchColumns = "Number of search columns"
MSG_searchFields = "Search fields"
POD_ASKACTION = 'Trigger related action'
EMAIL_SUBJECT = '${siteTitle} - Action \\"${transitionName}\\" has been ' \
'performed on element entitled \\"${objectTitle}\\".'
EMAIL_BODY = 'You can consult this element at ${objectUrl}.'
CONFIRM = 'Are you sure ?'
# ------------------------------------------------------------------------------
class PoMessage:
'''Represents a i18n message (po format).'''
CONFIG = "Configuration panel for product '%s'"
APP_NAME = "Appy"
# The following messages (starting with MSG_) correspond to tool
# attributes added for every gen-class (warning: the message IDs correspond
# to MSG_<attributePrefix>).
MSG_podTemplate = "POD template for field '%s'"
MSG_formats = "Output format(s) for field '%s'"
MSG_resultColumns = "Columns to display while showing query results"
MSG_enableAdvancedSearch = "Enable advanced search"
MSG_numberOfSearchColumns = "Number of search columns"
MSG_searchFields = "Search fields"
POD_ASKACTION = 'Trigger related action'
REF_NO = 'No object.'
REF_ADD = 'Add a new one'
REF_MOVE_UP = 'Move up'
REF_MOVE_DOWN = 'Move down'
REF_INVALID_INDEX = 'No move occurred: please specify a valid number.'
QUERY_CREATE = 'create'
QUERY_IMPORT = 'import'
QUERY_CONSULT_ALL = 'consult all'
QUERY_NO_RESULT = 'Nothing to see for the moment.'
IMPORT_TITLE = 'Importing data into your application'
IMPORT_SHOW_HIDE = 'Show / hide alreadly imported elements.'
IMPORT_ALREADY = 'Already imported.'
IMPORT_MANY = 'Import selected elements'
IMPORT_DONE = 'Import terminated successfully.'
SEARCH_TITLE = 'Advanced search'
SEARCH_BUTTON = 'Search'
SEARCH_OBJECTS = 'Search objects of this type.'
SEARCH_RESULTS = 'Search results'
SEARCH_NEW = 'New search'
SEARCH_FROM = 'From'
SEARCH_TO = 'to'
SEARCH_OR = 'or'
SEARCH_AND = 'and'
WORKFLOW_COMMENT = 'Optional comment'
WORKFLOW_STATE = 'state'
APPY_TITLE = 'Title'
DATA_CHANGE = 'Data change'
MODIFIED_FIELD = 'Modified field'
PREVIOUS_VALUE = 'Previous value'
PHASE = 'phase'
CHOOSE_A_VALUE = ' - '
CHOOSE_A_DOC = '[ Documents ]'
MIN_REF_VIOLATED = 'You must choose more elements here.'
MAX_REF_VIOLATED = 'Too much elements are selected here.'
BAD_LONG = 'An integer value is expected; do not enter any space.'
BAD_FLOAT = 'A floating-point number is expected; use the dot as decimal ' \
'separator, not a comma; do not enter any space.'
BAD_DATE = 'Please specify a valid date.'
BAD_EMAIL = 'Please enter a valid email.'
BAD_URL = 'Please enter a valid URL.'
BAD_ALPHANUMERIC = 'Please enter a valid alphanumeric value.'
BAD_SELECT_VALUE = 'The value is not among possible values for this field.'
ACTION_OK = 'The action has been successfully executed.'
ACTION_KO = 'A problem occurred while executing the action.'
FRONT_PAGE_TEXT = 'Welcome to this Appy-powered site.'
EMAIL_SUBJECT = '${siteTitle} - Action \\"${transitionName}\\" has been ' \
'performed on element entitled \\"${objectTitle}\\".'
EMAIL_BODY = 'You can consult this element at ${objectUrl}.'
SELECT_DESELECT = '(Un)select all'
NO_SELECTION = 'You must select at least one element.'
EDIT = 'Edit'
DELETE = 'Delete'
UNLINK = 'Unlink'
DELETE_CONFIRM = 'Are you sure you want to delete this element?'
UNLINK_CONFIRM = 'Are you sure you want to unlink this element?'
DELETE_DONE = 'The element has been deleted.'
UNLINK_DONE = 'The element has been unlinked.'
GOTO_FIRST = 'Go to top'
GOTO_PREVIOUS = 'Go to previous'
GOTO_NEXT = 'Go to next'
GOTO_LAST = 'Go to end'
GOTO_SOURCE = 'Go back'
WHATEVER = 'Whatever'
CONFIRM = 'Are you sure ?'
YES = 'Yes'
NO = 'No'
FIELD_REQUIRED = 'Please fill this field.'
FILE_REQUIRED = 'Please select a file.'
FIELD_INVALID = 'Please fill or correct this.'
IMAGE_REQUIRED = 'The uploaded file must be an image.'
FORMAT_ODT = 'ODT'
FORMAT_PDF = 'PDF'
FORMAT_DOC = 'DOC'
FORMAT_RTF = 'RTF'
CAPTCHA_TEXT = 'Please type "${text}" (without the double quotes) in the ' \
'field besides, but without the character at position ' \
'${number}.'
BAD_CAPTCHA = 'The code was not correct. Please try again.'
LOGIN = 'Login'
CONNECT = 'Log in'
PASSWORD = 'Password'
LOGOUT = 'Logout'
HOME = 'Home'
LOGIN_RESERVED = 'This login is reserved.'
LOGIN_IN_USE = 'This login is already in use.'
LOGIN_OK = 'Welcome! You are now logged in.'
LOGIN_KO = 'Login failed.'
PASSWORD_TOO_SHORT = 'Passwords must contain at least ${nb} characters.'
PASSWORDS_MISMATCH = 'Passwords do not match.'
SAVE = 'Save'
SAVED = 'Changes saved.'
ERROR = 'Please correct the indicated errors.'
CANCEL = 'Cancel'
CANCELED = 'Changes canceled.'
ENABLE_COOKIES = 'You must enable cookies before you can log in.'
PAGE_PREVIOUS = 'Previous page'
PAGE_NEXT = 'Next page'
FORGOT_PASSWORD = 'Forgot password?'
ASK_PASSWORD_REINIT = 'Ask new password'
WRONG_PASSWORD_REINIT = 'Something went wrong. First possibility: you ' \
'have already clicked on the link (maybe have you double-clicked?) ' \
'and your password has already been re-initialized. Please check ' \
'that you haven\'t received your new password in another email. ' \
'Second possibility: the link that you received in your mailer was ' \
'splitted on several lines. In this case, please re-type the link in ' \
'one single line and retry. Third possibility: you have waited too ' \
'long and your request has expired, or a technical error occurred. ' \
'In this case, please try again to ask a new password from the start.'
REINIT_MAIL_SENT = 'A mail has been sent to you. Please follow the ' \
'instructions from this email.'
REINIT_PASSWORD = 'Password re-initialisation'
REINIT_PASSWORD_BODY = 'Hello,<br/><br/>A password re-initialisation ' \
'has been requested, tied to this email address, for the website ' \
'${siteUrl}. If you are not at the origin of this request, please ' \
'ignore this email. Else, click on the link below to receive a new ' \
'password.<br/><br/>${url}'
NEW_PASSWORD = 'Your new password'
NEW_PASSWORD_BODY = 'Hello,<br/><br/>The new password you have ' \
'requested for website ${siteUrl} is ${password}<br/>' \
'<br/>Best regards.'
NEW_PASSWORD_SENT = 'Your new password has been sent to you by email.'
LAST_USER_ACCESS = 'Last access'
OBJECT_HISTORY = 'History'
OBJECT_CREATED_BY = 'By'
OBJECT_CREATED_ON = 'On'
OBJECT_MODIFIED_ON = 'Last updated on'
OBJECT_ACTION = 'Action'
OBJECT_AUTHOR = 'Author'
ACTION_DATE = 'Date'
ACTION_COMMENT = 'Comment'
DAY_MON_SHORT = 'Mon'
DAY_TUE_SHORT = 'Tue'
DAY_WED_SHORT = 'Wed'
DAY_THU_SHORT = 'Thu'
DAY_FRI_SHORT = 'Fri'
DAY_SAT_SHORT = 'Sat'
DAY_SUN_SHORT = 'Sun'
DAY_MON = 'Monday'
DAY_TUE = 'Tuesday'
DAY_WED = 'Wednesday'
DAY_THU = 'Thursday'
DAY_FRI = 'Friday'
DAY_SAT = 'Saturday'
DAY_SUN = 'Sunday'
AMPM_AM = 'AM'
AMPM_PM = 'PM'
MONTH_JAN_SHORT = 'Jan'
MONTH_FEB_SHORT = 'Feb'
MONTH_MAR_SHORT = 'Mar'
MONTH_APR_SHORT = 'Apr'
MONTH_MAY_SHORT = 'May'
MONTH_JUN_SHORT = 'Jun'
MONTH_JUL_SHORT = 'Jul'
MONTH_AUG_SHORT = 'Aug'
MONTH_SEP_SHORT = 'Sep'
MONTH_OCT_SHORT = 'Oct'
MONTH_NOV_SHORT = 'Nov'
MONTH_DEC_SHORT = 'Dec'
MONTH_JAN = 'January'
MONTH_FEB = 'February'
MONTH_MAR = 'March'
MONTH_APR = 'April'
MONTH_MAY = 'May'
MONTH_JUN = 'June'
MONTH_JUL = 'July'
MONTH_AUG = 'Augustus'
MONTH_SEP = 'September'
MONTH_OCT = 'October'
MONTH_NOV = 'November'
MONTH_DEC = 'December'
TODAY = 'Today'
WHICH_EVENT = 'Which event type would you like to create?'
EVENT_SPAN = 'Extend the event on the following number of days (leave ' \
'blank to create an event on the current day only):'
DEL_NEXT_EVENTS = 'Also delete successive events of the same type.'
def __init__(self, id, msg, default, fuzzy=False, comments=[],
niceDefault=False):
self.id = id
@ -290,22 +297,30 @@ class PoMessage:
return '<i18n msg id="%s", msg="%s", default="%s">' % \
(self.id, self.msg, self.default)
def __cmp__(self, other):
return cmp(self.id, other.id)
def clone(self, oldPrefix, newPrefix):
'''This returns a cloned version of myself. The clone has another id
that includes p_newPrefix.'''
if self.id.startswith(oldPrefix):
newId = newPrefix + self.id[len(oldPrefix):]
else:
newId = '%s_%s' % (newPrefix, self.id.split('_', 1)[1])
return PoMessage(newId, self.msg, self.default, comments=self.comments)
def getMessage(self):
'''Returns self.msg, but with some replacements.'''
return self.msg.replace('<br/>', '\n').replace('\\"', '"')
class PoMessages:
'''A list of po messages under construction.'''
def __init__(self):
# The list of messages
self.messages = []
# A dict of message ids, useful for efficiently checking if an id is
# already in the list or not.
self.ids = {}
def append(self, id, default, nice=True):
'''Creates a new PoMessage and adds it to self.messages. If p_nice is
True, it produces a nice default value for the message.'''
# Avoir creating duplicate ids
if id in self.ids: return
message = PoMessage(id, '', default, niceDefault=nice)
self.messages.append(message)
self.ids[id] = True
def get(self): return self.messages
class PoHeader:
def __init__(self, name, value):
self.name = name