diff -r 4c3ce3e03e19 -r 7fb76e862ad1 roundup/cgi/form_parser.py --- a/roundup/cgi/form_parser.py Thu Jun 26 19:04:04 2014 +0400 +++ b/roundup/cgi/form_parser.py Thu Jun 26 19:22:33 2014 +0400 @@ -34,6 +34,7 @@ self.classname = client.classname self.nodeid = client.nodeid try: + self.translator=client.translator self._ = self.gettext = client.gettext self.ngettext = client.ngettext except AttributeError: @@ -41,6 +42,12 @@ self._ = self.gettext = _translator.gettext self.ngettext = _translator.ngettext + def rawToHyperdb(self, klass, itemid, propname, value, **kw): + """ Wrapper for the same function with some filled values + """ + return hyperdb.rawToHyperdb(self.db, klass, itemid, propname, value, + translator=self.translator, **kw) + def parse(self, create=0, num_re=re.compile('^\d+$')): """ Item properties and their values are edited with html FORM variables and their values. You can: @@ -393,7 +400,7 @@ elif isinstance(proptype, hyperdb.Multilink): # convert input to list of ids try: - l = hyperdb.rawToHyperdb(self.db, cl, nodeid, + l = self.rawToHyperdb(cl, nodeid, propname, value) except hyperdb.HyperdbValueError, msg: raise FormError, msg @@ -460,11 +467,11 @@ # finally, read the content RAW value = value.value else: - value = hyperdb.rawToHyperdb(self.db, cl, - nodeid, propname, value) + value = self.rawToHyperdb(cl, nodeid, + propname, value) else: - value = hyperdb.rawToHyperdb(self.db, cl, nodeid, + value = self.rawToHyperdb(cl, nodeid, propname, value) except hyperdb.HyperdbValueError, msg: raise FormError, msg diff -r 4c3ce3e03e19 -r 7fb76e862ad1 roundup/cgi/templating.py --- a/roundup/cgi/templating.py Thu Jun 26 19:04:04 2014 +0400 +++ b/roundup/cgi/templating.py Thu Jun 26 19:22:33 2014 +0400 @@ -1719,7 +1719,7 @@ if not self.is_view_ok(): return self._('[hidden]') - ret = date.Date('.', translator=self._client) + ret = date.Date(translator=self._client.translator) if isinstance(str_interval, basestring): sign = 1 @@ -1810,7 +1810,7 @@ return '' # figure the interval - interval = self._value - date.Date('.', translator=self._client) + interval = self._value - date.Date(translator=self._client.translator) if pretty: return interval.pretty() return str(interval) @@ -1833,10 +1833,19 @@ if not self._value: return '' - elif format is not self._marker: + try: + if format is not self._marker: return self._value.local(offset).pretty(format) else: return self._value.local(offset).pretty() + except AttributeError: + # not a date value, e.g. from unsaved form data + return str(self._value) + + def format(self, strftime_format): + '''Print date exactly as supplied strftime_format. + ''' + return self._value.format(strftime_format) def local(self, offset): """ Return the date/time as a local (timezone offset) date/time. diff -r 4c3ce3e03e19 -r 7fb76e862ad1 roundup/date.py --- a/roundup/date.py Thu Jun 26 19:04:04 2014 +0400 +++ b/roundup/date.py Thu Jun 26 19:22:33 2014 +0400 @@ -31,15 +31,6 @@ from roundup import i18n -# no, I don't know why we must anchor the date RE when we only ever use it -# in a match() -date_re = re.compile(r'''^ - ((?P\d\d\d\d)([/-](?P\d\d?)([/-](?P\d\d?))?)? # yyyy[-mm[-dd]] - |(?P\d\d?)[/-](?P\d\d?))? # or mm-dd - (?P\.)? # . - (((?P\d?\d):(?P\d\d))?(:(?P\d\d?(\.\d+)?))?)? # hh:mm:ss - (?P[\d\smywd\-+]+)? # offset -$''', re.VERBOSE) serialised_date_re = re.compile(r''' (\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d?(\.\d+)?) ''', re.VERBOSE) @@ -233,8 +224,8 @@ ''' - def __init__(self, spec='.', offset=0, add_granularity=False, - translator=i18n): + def __init__(self, spec=datetime.datetime.utcnow(), + offset=0, add_granularity=False, translator=i18n.translation): """Construct a date given a specification and a time zone offset. 'spec' @@ -281,7 +272,7 @@ except: raise ValueError, 'Unknown spec %r' % (spec,) - def set(self, spec, offset=0, date_re=date_re, + def set(self, spec, offset=0, serialised_re=serialised_date_re, add_granularity=False): ''' set the date to the value in spec ''' @@ -296,11 +287,11 @@ return # not serialised data, try usual format - m = date_re.match(spec) + m = self.translator.date_input_re.match(spec) if m is None: - raise ValueError, self._('Not a date spec: ' + raise ValueError, self._('%r not a date / time spec ' '"yyyy-mm-dd", "mm-dd", "HH:MM", "HH:MM:SS" or ' - '"yyyy-mm-dd.HH:MM:SS.SSS"') + '"yyyy-mm-dd.HH:MM:SS.SSS"')%(spec,) info = m.groupdict() @@ -510,6 +501,14 @@ return f%(self.year, self.month, self.day, self.hour, self.minute, self.second) + def format(self, format='%d %m %Y'): + '''print day using strftime supplied format. + ''' + dt = datetime.datetime(self.year, self.month, self.day, self.hour, + self.minute, int(self.second), + int ((self.second - int (self.second)) * 1000000.)) + return dt.strftime(format) + def pretty(self, format='%d %B %Y'): ''' print up the date date using a pretty format... @@ -640,7 +639,7 @@ TODO: more examples, showing the order of addition operation ''' def __init__(self, spec, sign=1, allowdate=1, add_granularity=False, - translator=i18n + translator=i18n.translation ): """Construct an interval given a specification.""" self.setTranslator(translator) @@ -724,7 +723,7 @@ # use a date spec if one is given if allowdate and info['D'] is not None: - now = Date('.') + now = Date() date = Date(info['D']) # if no time part was specified, nuke it in the "now" date if not date.hour or date.minute or date.second: diff -r 4c3ce3e03e19 -r 7fb76e862ad1 roundup/hyperdb.py --- a/roundup/hyperdb.py Thu Jun 26 19:04:04 2014 +0400 +++ b/roundup/hyperdb.py Thu Jun 26 19:22:33 2014 +0400 @@ -27,6 +27,7 @@ import date, password from support import ensureParentsExist, PrioList, sorted, reversed from roundup.i18n import _ +from roundup.i18n import translation # # Types @@ -71,14 +72,14 @@ class Password(_Type): """An object designating a Password property.""" - def from_raw(self, value, **kw): + def from_raw(self, value, translator=translation, **kw): if not value: return None try: return password.Password(encrypted=value, strict=True) except password.PasswordValueError, message: raise HyperdbValueError, \ - _('property %s: %s')%(kw['propname'], message) + translator.gettext('property %s: %s')%(kw['propname'], message) def sort_repr (self, cls, val, name): if not val: @@ -95,11 +96,11 @@ if self._offset is not None: return self._offset return db.getUserTimezone() - def from_raw(self, value, db, **kw): + def from_raw(self, value, db, translator=translation, **kw): try: - value = date.Date(value, self.offset(db)) + value = date.Date(value, self.offset(db), translator=translator) except ValueError, message: - raise HyperdbValueError, _('property %s: %r is an invalid '\ + raise HyperdbValueError, translator.gettext('property %s: %r is an invalid '\ 'date (%s)')%(kw['propname'], value, message) return value def range_from_raw(self, value, db): @@ -112,11 +113,11 @@ class Interval(_Type): """An object designating an Interval property.""" - def from_raw(self, value, **kw): + def from_raw(self, value, translator=translation, **kw): try: - value = date.Interval(value) + value = date.Interval(value, translator=translator) except ValueError, message: - raise HyperdbValueError, _('property %s: %r is an invalid '\ + raise HyperdbValueError, translator.gettext('property %s: %r is an invalid '\ 'date interval (%s)')%(kw['propname'], value, message) return value def sort_repr (self, cls, val, name): @@ -184,7 +185,7 @@ required = required, default_value = []) - def from_raw(self, value, db, klass, propname, itemid, **kw): + def from_raw(self, value, db, klass, propname, itemid, translator=translation, **kw): if not value: return [] @@ -232,7 +233,7 @@ try: curvalue.remove(itemid) except ValueError: - raise HyperdbValueError, _('property %s: %r is not ' \ + raise HyperdbValueError, translator.gettext('property %s: %r is not ' \ 'currently an element')%(propname, item) else: newvalue.append(itemid) @@ -271,12 +272,12 @@ class Number(_Type): """An object designating a numeric property""" - def from_raw(self, value, **kw): + def from_raw(self, value, translator=translation, **kw): value = value.strip() try: value = float(value) except ValueError: - raise HyperdbValueError, _('property %s: %r is not a number')%( + raise HyperdbValueError, translator.gettext('property %s: %r is not a number')%( kw['propname'], value) return value # @@ -1390,7 +1391,7 @@ text = text.replace('\r\n', '\n') return text.replace('\r', '\n') -def rawToHyperdb(db, klass, itemid, propname, value, **kw): +def rawToHyperdb(db, klass, itemid, propname, value, translator=translation, **kw): """ Convert the raw (user-input) value to a hyperdb-storable value. The value is for the "propname" property on itemid (may be None for a new item) of "klass" in "db". @@ -1406,7 +1407,7 @@ try: proptype = properties[propname] except KeyError: - raise HyperdbValueError, _('%r is not a property of %s')%(propname, + raise HyperdbValueError, translator.gettext('%r is not a property of %s')%(propname, klass.classname) # if we got a string, strip it now @@ -1415,7 +1416,7 @@ # convert the input value to a real property value value = proptype.from_raw(value, db=db, klass=klass, - propname=propname, itemid=itemid, **kw) + propname=propname, itemid=itemid, translator=translator, **kw) return value diff -r 4c3ce3e03e19 -r 7fb76e862ad1 roundup/i18n.py --- a/roundup/i18n.py Thu Jun 26 19:04:04 2014 +0400 +++ b/roundup/i18n.py Thu Jun 26 19:22:33 2014 +0400 @@ -38,6 +38,7 @@ import errno import gettext as gettext_module import os +import re from roundup import msgfmt @@ -61,8 +62,52 @@ # Roundup text domain DOMAIN = "roundup" -RoundupNullTranslations = gettext_module.NullTranslations -RoundupTranslations = gettext_module.GNUTranslations +#--additional class to care about locale such as date format +#--Not for direct use. Use Roundup*Translations instead. +#--This object is to use without modification with +#--gettext NullTranslations or GNUTranslations. Also, inside this class +#--we use gettext and other functions of gettext module *Translations classes +#--methods, but does not include it in base class. +#--All this become correct when instantiated as Roundup*Translaions. +class I18NStaff(object): + def __init__(self, fp = None, language = 'en'): + #ugly code. + #We must call __init__ of + #NullTranslations or GNUtranslations (gettext classes). + #The main problem is that gettext classes in Python 2.X is "old-style" + #classes. + #For python 3+ this must be changed for ?super().__init__? in case + #of unchanged Roundup*Translations base classes order. + if issubclass(self.__class__, RoundupNullTranslations): + gettext_module.NullTranslations.__init__(self, fp) + elif issubclass(self.__class__, RoundupTranslations): + gettext_module.GNUTranslations.__init__(self, fp) + + #pattern for parsing date input. + #It may be modified via language translation files. If modified translate + #message "not a date / time spec..." in date.py + #accordingly. + date_re = self.gettext(r'''^ + ((?P\d\d\d\d)([/-](?P\d\d?)([/-](?P\d\d?))?)? # yyyy[-mm[-dd]] + |(?P\d\d?)[/-](?P\d\d?))? # or mm-dd + (?P\.)? # . + (((?P\d?\d):(?P\d\d))?(:(?P\d\d?(\.\d+)?))?)? # hh:mm:ss + (?P[\d\smywd\-+]+)? # offset + $''') + self.date_input_re = re.compile(date_re, re.VERBOSE) + + +class RoundupNullTranslations(I18NStaff, gettext_module.NullTranslations): + """Do not change order of base classes. """ + pass + +class RoundupTranslations(I18NStaff, gettext_module.GNUTranslations): + """Do not change order of base classes. """ + def __init__(self, fp, language='en'): + #Require fp (translation file) parameter. Without this gettext function + #will fail with exception. If no translation file, use RoundupNullTranslations. + I18NStaff.__init__(self, fp, language) + def find_locales(language=None): """Return normalized list of locale names to try for given language