Browse Source

Add module datetime_formatter.

This module allows the developer to format any date/time combination in
the most comfortable way for the user, just with a quick call to
pull/1180/merge
Jairo Llopis 9 years ago
committed by Jairo Llopis
parent
commit
d7185292e4
  1. 76
      datetime_formatter/README.rst
  2. 4
      datetime_formatter/__init__.py
  3. 18
      datetime_formatter/__openerp__.py
  4. 11
      datetime_formatter/exceptions.py
  5. 120
      datetime_formatter/models.py
  6. BIN
      datetime_formatter/static/description/icon.png
  7. 4
      datetime_formatter/tests/__init__.py
  8. 75
      datetime_formatter/tests/test_best_matcher.py
  9. 92
      datetime_formatter/tests/test_formatter.py

76
datetime_formatter/README.rst

@ -0,0 +1,76 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
=====================
Date & Time Formatter
=====================
This module was written to extend the functionality of Odoo language engine to
support formatting `Date`, `Time` and `Datetime` fields easily and allow you to
print them in the best format for the user.
Usage
=====
This module adds a technical programming feature, and it should be used by
addon developers, not by end users. This means that you must not expect to see
any changes if you are a user and install this, but if you find you have it
already installed, it's probably because you have another modules that depend
on this one.
If you are a developer, to use this module, you need to:
* Call anywhere in your code::
formatted_string = self.env["res.lang"].datetime_formatter(datetime_value)
* If you use Qweb::
<t t-esc="env['res.lang'].datetime_formatter(datetime_value)"/>
* If you call it from a record that has a `lang` field::
formatted_string = record.lang.datetime_formatter(record.datetime_field)
* ``models.ResLang.datetime_formatter`` docstring explains its usage.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/149/8.0
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed `feedback
<https://github.com/OCA/
server-tools/issues/new?body=module:%20
datetime_formatter%0Aversion:%20
8.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits
=======
Contributors
------------
* Jairo Llopis <j.llopis@grupoesoc.es>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

4
datetime_formatter/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models

18
datetime_formatter/__openerp__.py

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Date & Time Formatter",
"summary": "Helper functions to give correct format to date[time] fields",
"version": "8.0.1.0.0",
"category": "Tools",
"website": "https://grupoesoc.es",
"author": "Grupo ESOC Ingeniería de Servicios, "
"Odoo Community Association (OCA)",
"license": "AGPL-3",
"installable": True,
"depends": [
"base",
],
}

11
datetime_formatter/exceptions.py

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import _, exceptions
class BestMatchedLanguageNotFoundError(exceptions.MissingError):
def __init__(self, lang):
msg = (_("Best matched language (%s) not found.") % lang)
super(BestMatchedLanguageNotFoundError, self).__init__(msg)
self.lang = lang

120
datetime_formatter/models.py

@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import datetime, timedelta
from openerp import api, exceptions, fields, models
from openerp.tools import (DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_TIME_FORMAT)
from . import exceptions as ex
# Available modes for :param:`.ResLang.datetime_formatter.template`
MODE_DATETIME = "MODE_DATETIME"
MODE_DATE = "MODE_DATE"
MODE_TIME = "MODE_TIME"
class ResLang(models.Model):
_inherit = "res.lang"
@api.model
@api.returns('self')
def best_match(self, lang=None, failure_safe=True):
"""Get best match of current default lang.
:param str lang:
If a language in the form of "en_US" is supplied, it will have the
highest priority.
:param bool failure_safe:
If ``False`` and the best matched language is not found installed,
an exception will be raised. Otherwise, the first installed
language found in the DB will be returned.
"""
# Find some installed language, as fallback
first_installed = self.search([("active", "=", True)], limit=1)
if not lang:
lang = (
# Object's language, if called like
# ``record.lang.datetime_formatter(datetime_obj)``
(self.ids and self[0].code) or
# Context language
self.env.context.get("lang") or
# User's language
self.env.user.lang or
# First installed language found
first_installed.code)
# Get DB lang record
record = self.search([("code", "=", lang)])
try:
record.ensure_one()
except exceptions.except_orm:
if not failure_safe:
raise ex.BestMatchedLanguageNotFoundError(lang)
else:
record = first_installed
return record
@api.model
def datetime_formatter(self, value, lang=None, template=MODE_DATETIME,
separator=" ", failure_safe=True):
"""Convert a datetime field to lang's default format.
:type value: `str`, `float` or `datetime.datetime`
:param value:
Datetime that will be formatted to the user's preferred format.
:param str lang:
See :param:`lang` from :meth:`~.best_match`.
:param bool failure_safe:
See :param:`failure_safe` from :meth:`~.best_match`.
:param str template:
Will be used to format :param:`value`. If it is one of the special
constants :const:`MODE_DATETIME`, :const:`MODE_DATE` or
:const:`MODE_TIME`, it will use the :param:`lang`'s default
template for that mode.
:param str separator:
Only used when :param:`template` is :const:`MODE_DATETIME`, as the
separator between the date and time parts.
"""
# Get the correct lang
lang = self.best_match(lang)
# Get the template
if template in {MODE_DATETIME, MODE_DATE, MODE_TIME}:
defaults = []
if "DATE" in template:
defaults.append(lang.date_format or
DEFAULT_SERVER_DATE_FORMAT)
if "TIME" in template:
defaults.append(lang.time_format or
DEFAULT_SERVER_TIME_FORMAT)
template = separator.join(defaults)
# Convert str to datetime objects
if isinstance(value, (str, unicode)):
try:
value = fields.Datetime.from_string(value)
except ValueError:
# Probably failed due to value being only time
value = datetime.strptime(value, DEFAULT_SERVER_TIME_FORMAT)
# Time-only fields are floats for Odoo
elif isinstance(value, float):
# Patch values >= 24 hours
if value >= 24:
template = template.replace("%H", "%d" % value)
# Convert to time
value = (datetime.min + timedelta(hours=value)).time()
return value.strftime(template)

BIN
datetime_formatter/static/description/icon.png

After

Width: 128  |  Height: 128  |  Size: 9.2 KiB

4
datetime_formatter/tests/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_best_matcher, test_formatter

75
datetime_formatter/tests/test_best_matcher.py

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp.tests.common import TransactionCase
from .. import exceptions
class BasicCase(TransactionCase):
def setUp(self):
super(BasicCase, self).setUp()
self.langs = ("en_US", "es_ES", "it_IT", "pt_PT", "zh_CN")
self.rl = self.env["res.lang"]
for lang in self.langs:
if not self.rl.search([("code", "=", lang)]):
self.rl.load_lang(lang)
def test_explicit(self):
"""When an explicit lang is used."""
for lang in self.langs:
self.assertEqual(self.rl.best_match(lang).code, lang)
def test_record(self):
"""When called from a ``res.lang`` record."""
rl = self.rl.with_context(lang="it_IT")
rl.env.user.lang = "pt_PT"
for lang in self.langs:
self.assertEqual(
rl.search([("code", "=", lang)]).best_match().code,
lang)
def test_context(self):
"""When called with a lang in context."""
self.env.user.lang = "pt_PT"
for lang in self.langs:
self.assertEqual(
self.rl.with_context(lang=lang).best_match().code,
lang)
def test_user(self):
"""When lang not specified in context."""
for lang in self.langs:
self.env.user.lang = lang
# Lang is False in context
self.assertEqual(
self.rl.with_context(lang=False).best_match().code,
lang)
# Lang not found in context
self.assertEqual(
self.rl.with_context(dict()).best_match().code,
lang)
def test_first_installed(self):
"""When falling back to first installed language."""
first = self.rl.search([("active", "=", True)], limit=1)
self.env.user.lang = False
self.assertEqual(
self.rl.with_context(lang=False).best_match().code,
first.code)
def test_unavailable(self):
"""When matches to an unavailable language."""
self.env.user.lang = False
self.rl = self.rl.with_context(lang=False)
first = self.rl.search([("active", "=", True)], limit=1)
# Safe mode
self.assertEqual(self.rl.best_match("fake_LANG").code, first.code)
# Unsafe mode
with self.assertRaises(exceptions.BestMatchedLanguageNotFoundError):
self.rl.best_match("fake_LANG", failure_safe=False)

92
datetime_formatter/tests/test_formatter.py

@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import datetime
from random import random
from openerp.tests.common import TransactionCase
from openerp.tools import (DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_TIME_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
from ..models import MODE_DATE, MODE_TIME, MODE_DATETIME
class FormatterCase(TransactionCase):
def setUp(self):
super(FormatterCase, self).setUp()
self.rl = self.env["res.lang"]
self.bm = self.rl.best_match()
self.dt = datetime.datetime.now()
self.d_fmt = self.bm.date_format or DEFAULT_SERVER_DATE_FORMAT
self.t_fmt = self.bm.time_format or DEFAULT_SERVER_TIME_FORMAT
self.kwargs = dict()
def tearDown(self):
# This should be returned
self.expected = self.dt.strftime(self.format)
# Pass a datetime object
self.assertEqual(
self.expected,
self.rl.datetime_formatter(
self.dt,
**self.kwargs))
# When the date comes as a string
if isinstance(self.dt, datetime.datetime):
self.dt_str = self.dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
elif isinstance(self.dt, datetime.date):
self.dt_str = self.dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
elif isinstance(self.dt, datetime.time):
self.dt_str = self.dt.strftime(DEFAULT_SERVER_TIME_FORMAT)
# Pass a string
self.assertEqual(
self.expected,
self.rl.datetime_formatter(
self.dt_str,
**self.kwargs))
# Pass a unicode
self.assertEqual(
self.expected,
self.rl.datetime_formatter(
unicode(self.dt_str),
**self.kwargs))
super(FormatterCase, self).tearDown()
def test_datetime(self):
"""Format a datetime."""
self.format = "%s %s" % (self.d_fmt, self.t_fmt)
self.kwargs = {"template": MODE_DATETIME}
def test_date(self):
"""Format a date."""
self.format = self.d_fmt
self.kwargs = {"template": MODE_DATE}
self.dt = self.dt.date()
def test_time(self):
"""Format times, including float ones."""
self.format = self.t_fmt
self.kwargs = {"template": MODE_TIME}
self.dt = self.dt.time()
# Test float times
for n in range(50):
n = n + random()
# Patch values with >= 24 hours
fmt = self.format.replace("%H", "%02d" % n)
time = (datetime.datetime.min +
datetime.timedelta(hours=n)).time()
self.assertEqual(
time.strftime(fmt),
self.rl.datetime_formatter(n, **self.kwargs))
def test_custom_separator(self):
"""Format a datetime with a custom separator."""
sep = "T"
self.format = "%s%s%s" % (self.d_fmt, sep, self.t_fmt)
self.kwargs = {"template": MODE_DATETIME, "separator": sep}
Loading…
Cancel
Save