[forms] Fix edition of TZDatetime attributes
Parse the submitted web form values into a timezone-aware datetime
(by default, UTC) when such an attribute is edited. This is done using
an automatically-selected (by `web.formfield.guess_field`) field named
`TZDatetimeField`.
Closes #12666598.
--- a/cubicweb/web/formfields.py Mon May 02 17:14:43 2016 +0200
+++ b/cubicweb/web/formfields.py Wed May 04 17:07:41 2016 +0200
@@ -42,6 +42,7 @@
.. autoclass:: cubicweb.web.formfields.BooleanField()
.. autoclass:: cubicweb.web.formfields.DateField()
.. autoclass:: cubicweb.web.formfields.DateTimeField()
+.. autoclass:: cubicweb.web.formfields.TZDatetimeField()
.. autoclass:: cubicweb.web.formfields.TimeField()
.. autoclass:: cubicweb.web.formfields.TimeIntervalField()
@@ -65,6 +66,8 @@
from datetime import datetime, timedelta
+import pytz
+
from six import PY2, text_type, string_types
from logilab.mtconverter import xml_escape
@@ -1015,6 +1018,18 @@
etype = 'Datetime'
+class TZDatetimeField(DateTimeField):
+ """ Use this field to edit a timezone-aware datetime (`TZDatetime` yams
+ type). Note the posted values are interpreted as UTC, so you may need to
+ convert them client-side, using some javascript in the corresponding widget.
+ """
+
+ def _ensure_correctly_typed(self, form, value):
+ tz_naive = super(TZDatetimeField, self)._ensure_correctly_typed(
+ form, value)
+ return tz_naive.replace(tzinfo=pytz.utc)
+
+
class TimeField(DateField):
"""Use this field to edit time (`Time` yams type).
@@ -1267,7 +1282,7 @@
'Date': DateField,
'Datetime': DateTimeField,
- 'TZDatetime': DateTimeField,
+ 'TZDatetime': TZDatetimeField,
'Time': TimeField,
'TZTime': TimeField,
'Interval': TimeIntervalField,
--- a/cubicweb/web/test/data/schema.py Mon May 02 17:14:43 2016 +0200
+++ b/cubicweb/web/test/data/schema.py Wed May 04 17:07:41 2016 +0200
@@ -17,7 +17,7 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
from yams.buildobjs import (EntityType, RelationDefinition, SubjectRelation,
- String, Int, Datetime, Boolean, Float)
+ String, Int, Datetime, Boolean, Float, TZDatetime)
from yams.constraints import IntervalBoundConstraint
from cubicweb import _
@@ -58,6 +58,7 @@
tel = Int()
fax = Int()
datenaiss = Datetime()
+ tzdatenaiss = TZDatetime()
test = Boolean()
description = String()
salary = Float()
--- a/cubicweb/web/test/unittest_form.py Mon May 02 17:14:43 2016 +0200
+++ b/cubicweb/web/test/unittest_form.py Wed May 04 17:07:41 2016 +0200
@@ -17,6 +17,8 @@
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
import time
+from datetime import datetime
+import pytz
from xml.etree.ElementTree import fromstring
from lxml import html
@@ -30,7 +32,8 @@
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.web.formfields import (IntField, StringField, RichTextField,
PasswordField, DateTimeField,
- FileField, EditableFileField)
+ FileField, EditableFileField,
+ TZDatetimeField)
from cubicweb.web.formwidgets import PasswordInput, Input, DateTimePicker
from cubicweb.web.views.forms import EntityFieldsForm, FieldsForm
from cubicweb.web.views.workflow import ChangeStateForm
@@ -260,6 +263,31 @@
<p><b>You can either submit a new file using the browse button above, or choose to remove already uploaded file by checking the "detach attached file" check-box, or edit file content online with the widget below.</b></p>
<textarea cols="80" name="data-subject:%(eid)s" onkeyup="autogrow(this)" rows="3" tabindex="4">new widgets system</textarea>''' % {'eid': file.eid})
+ def _modified_tzdatenaiss(self, eid, datestr, timestr):
+ ctx = {'tzdatenaiss-subjectdate:%d' % eid: datestr,
+ 'tzdatenaiss-subjecttime:%d' % eid: timestr}
+ with self.admin_access.web_request(**ctx) as req:
+ form = EntityFieldsForm(req, None, entity=req.entity_from_eid(eid))
+ field = TZDatetimeField(name='tzdatenaiss', eidparam=True,
+ role='subject')
+ form.append_field(field)
+ form.build_context({})
+ return field.has_been_modified(form)
+
+ def test_tzdatetimefield(self):
+ """ Comparison of the tz-aware database-stored value and the posted data
+ should not crash, and the posted data should be considered UTC """
+ tzd = datetime.now(pytz.utc).replace(second=0, microsecond=0)
+ datestr, timestr = tzd.strftime('%Y/%m/%d %H:%M').split()
+ with self.admin_access.web_request() as req:
+ eid = req.create_entity('Personne', nom=u'Flo', tzdatenaiss=tzd).eid
+ req.cnx.commit()
+
+ modified = self._modified_tzdatenaiss(eid, datestr, timestr)
+ self.assertFalse(modified)
+
+ modified = self._modified_tzdatenaiss(eid, '2016/05/04', '15:07')
+ self.assertTrue(modified)
def test_passwordfield(self):
class PFForm(EntityFieldsForm):
--- a/cubicweb/web/test/unittest_magicsearch.py Mon May 02 17:14:43 2016 +0200
+++ b/cubicweb/web/test/unittest_magicsearch.py Wed May 04 17:07:41 2016 +0200
@@ -293,6 +293,7 @@
'Any X WHERE X is Personne, X test A',
'Any X WHERE X is Personne, X titre A',
'Any X WHERE X is Personne, X travaille A',
+ 'Any X WHERE X is Personne, X tzdatenaiss A',
'Any X WHERE X is Personne, X web A',
],
self.suggestions('Any X WHERE X is Personne, X '))
@@ -300,6 +301,7 @@
'Any X WHERE X is Personne, X test A',
'Any X WHERE X is Personne, X titre A',
'Any X WHERE X is Personne, X travaille A',
+ 'Any X WHERE X is Personne, X tzdatenaiss A',
],
self.suggestions('Any X WHERE X is Personne, X t'))
# try completion on selected
@@ -307,6 +309,7 @@
'Any X WHERE X is Personne, Y is Societe, X test A',
'Any X WHERE X is Personne, Y is Societe, X titre A',
'Any X WHERE X is Personne, Y is Societe, X travaille Y',
+ 'Any X WHERE X is Personne, Y is Societe, X tzdatenaiss A',
],
self.suggestions('Any X WHERE X is Personne, Y is Societe, X t'))
# invalid relation should not break
--- a/cubicweb/web/test/unittest_views_editforms.py Mon May 02 17:14:43 2016 +0200
+++ b/cubicweb/web/test/unittest_views_editforms.py Wed May 04 17:07:41 2016 +0200
@@ -121,6 +121,7 @@
('tel', 'subject'),
('fax', 'subject'),
('datenaiss', 'subject'),
+ ('tzdatenaiss', 'subject'),
('test', 'subject'),
('description', 'subject'),
('salary', 'subject'),