fix: add WuttaDateWidget and associated logic
				
					
				
			This commit is contained in:
		
							parent
							
								
									e3c432aa37
								
							
						
					
					
						commit
						9e0e36d536
					
				
					 4 changed files with 103 additions and 7 deletions
				
			
		|  | @ -546,10 +546,13 @@ class Form: | |||
|         This is generally only possible if :attr:`model_class` is set | ||||
|         to a valid SQLAlchemy mapped class. | ||||
| 
 | ||||
|         As of writing this only looks for | ||||
|         :class:`sqlalchemy:sqlalchemy.types.DateTime` fields and if | ||||
|         any are found, they are configured to use | ||||
|         :class:`~wuttaweb.forms.widgets.WuttaDateTimeWidget()`. | ||||
|         This only checks for a couple of data types, with mapping as | ||||
|         follows: | ||||
| 
 | ||||
|         * :class:`sqlalchemy:sqlalchemy.types.Date` -> | ||||
|           :class:`~wuttaweb.forms.widgets.WuttaDateWidget` | ||||
|         * :class:`sqlalchemy:sqlalchemy.types.DateTime` -> | ||||
|           :class:`~wuttaweb.forms.widgets.WuttaDateTimeWidget` | ||||
|         """ | ||||
|         from wuttaweb.forms import widgets | ||||
| 
 | ||||
|  | @ -565,8 +568,9 @@ class Form: | |||
|                 prop = getattr(attr, 'prop', None) | ||||
|                 if prop and isinstance(prop, orm.ColumnProperty): | ||||
|                     column = prop.columns[0] | ||||
|                     if isinstance(column.type, sa.DateTime): | ||||
|                         # self.set_renderer(key, self.render_datetime) | ||||
|                     if isinstance(column.type, sa.Date): | ||||
|                         self.set_widget(key, widgets.WuttaDateWidget(self.request)) | ||||
|                     elif isinstance(column.type, sa.DateTime): | ||||
|                         self.set_widget(key, widgets.WuttaDateTimeWidget(self.request)) | ||||
| 
 | ||||
|     def set_grid(self, key, grid): | ||||
|  |  | |||
|  | @ -36,6 +36,7 @@ in the namespace: | |||
| * :class:`deform:deform.widget.CheckboxWidget` | ||||
| * :class:`deform:deform.widget.SelectWidget` | ||||
| * :class:`deform:deform.widget.CheckboxChoiceWidget` | ||||
| * :class:`deform:deform.widget.DateInputWidget` | ||||
| * :class:`deform:deform.widget.DateTimeInputWidget` | ||||
| * :class:`deform:deform.widget.MoneyInputWidget` | ||||
| """ | ||||
|  | @ -49,7 +50,7 @@ import humanize | |||
| from deform.widget import (Widget, TextInputWidget, TextAreaWidget, | ||||
|                            PasswordWidget, CheckedPasswordWidget, | ||||
|                            CheckboxWidget, SelectWidget, CheckboxChoiceWidget, | ||||
|                            DateTimeInputWidget, MoneyInputWidget) | ||||
|                            DateInputWidget, DateTimeInputWidget, MoneyInputWidget) | ||||
| from webhelpers2.html import HTML | ||||
| 
 | ||||
| from wuttjamaican.conf import parse_list | ||||
|  | @ -153,6 +154,43 @@ class WuttaCheckboxChoiceWidget(CheckboxChoiceWidget): | |||
|         self.app = self.config.get_app() | ||||
| 
 | ||||
| 
 | ||||
| class WuttaDateWidget(DateInputWidget): | ||||
|     """ | ||||
|     Custom widget for :class:`python:datetime.date` fields. | ||||
| 
 | ||||
|     The main purpose of this widget is to leverage | ||||
|     :meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_date()` | ||||
|     for the readonly display. | ||||
| 
 | ||||
|     It is automatically used for SQLAlchemy mapped classes where the | ||||
|     field maps to a :class:`sqlalchemy:sqlalchemy.types.Date` column. | ||||
|     For other (non-mapped) date fields, or mapped datetime fields for | ||||
|     which a date widget is preferred, use | ||||
|     :meth:`~wuttaweb.forms.base.Form.set_widget()`. | ||||
| 
 | ||||
|     This is a subclass of | ||||
|     :class:`deform:deform.widget.DateInputWidget` and uses these | ||||
|     Deform templates: | ||||
| 
 | ||||
|     * ``dateinput`` | ||||
|     """ | ||||
| 
 | ||||
|     def __init__(self, request, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|         self.request = request | ||||
|         self.config = self.request.wutta_config | ||||
|         self.app = self.config.get_app() | ||||
| 
 | ||||
|     def serialize(self, field, cstruct, **kw): | ||||
|         """ """ | ||||
|         readonly = kw.get('readonly', self.readonly) | ||||
|         if readonly and cstruct: | ||||
|             dt = datetime.datetime.fromisoformat(cstruct) | ||||
|             return self.app.render_date(dt) | ||||
| 
 | ||||
|         return super().serialize(field, cstruct, **kw) | ||||
| 
 | ||||
| 
 | ||||
| class WuttaDateTimeWidget(DateTimeInputWidget): | ||||
|     """ | ||||
|     Custom widget for :class:`python:datetime.datetime` fields. | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ | |||
| from unittest import TestCase | ||||
| from unittest.mock import MagicMock, patch | ||||
| 
 | ||||
| import sqlalchemy as sa | ||||
| 
 | ||||
| import colander | ||||
| import deform | ||||
| from pyramid import testing | ||||
|  | @ -188,6 +190,18 @@ class TestForm(TestCase): | |||
|         self.assertIn('created', form.widgets) | ||||
|         self.assertIsInstance(form.widgets['created'], MyWidget) | ||||
| 
 | ||||
|         # mock up a table with all relevant column types | ||||
|         class Whatever(model.Base): | ||||
|             __tablename__ = 'whatever' | ||||
|             id = sa.Column(sa.Integer(), primary_key=True) | ||||
|             date = sa.Column(sa.Date()) | ||||
|             date_time = sa.Column(sa.DateTime()) | ||||
| 
 | ||||
|         # widget set for all known types | ||||
|         form = self.make_form(model_class=Whatever) | ||||
|         self.assertIsInstance(form.widgets['date'], widgets.WuttaDateWidget) | ||||
|         self.assertIsInstance(form.widgets['date_time'], widgets.WuttaDateTimeWidget) | ||||
| 
 | ||||
|     def test_set_grid(self): | ||||
|         form = self.make_form(fields=['foo', 'bar']) | ||||
|         self.assertNotIn('foo', form.widgets) | ||||
|  |  | |||
|  | @ -87,6 +87,46 @@ class TestObjectRefWidget(WebTestCase): | |||
|             self.assertNotIn('url', values) | ||||
| 
 | ||||
| 
 | ||||
| class TestWuttaDateWidget(WebTestCase): | ||||
| 
 | ||||
|     def make_field(self, node, **kwargs): | ||||
|         # TODO: not sure why default renderer is in use even though | ||||
|         # pyramid_deform was included in setup?  but this works.. | ||||
|         kwargs.setdefault('renderer', deform.Form.default_renderer) | ||||
|         return deform.Field(node, **kwargs) | ||||
| 
 | ||||
|     def make_widget(self, **kwargs): | ||||
|         return mod.WuttaDateWidget(self.request, **kwargs) | ||||
| 
 | ||||
|     def test_serialize(self): | ||||
|         node = colander.SchemaNode(colander.Date()) | ||||
|         field = self.make_field(node) | ||||
| 
 | ||||
|         # first try normal date | ||||
|         widget = self.make_widget() | ||||
|         dt = datetime.date(2025, 1, 15) | ||||
| 
 | ||||
|         # editable widget has normal picker html | ||||
|         result = widget.serialize(field, str(dt)) | ||||
|         self.assertIn('<wutta-datepicker', result) | ||||
| 
 | ||||
|         # readonly is rendered per app convention | ||||
|         result = widget.serialize(field, str(dt), readonly=True) | ||||
|         self.assertEqual(result, '2025-01-15') | ||||
| 
 | ||||
|         # now try again with datetime | ||||
|         widget = self.make_widget() | ||||
|         dt = datetime.datetime(2025, 1, 15, 8, 35) | ||||
| 
 | ||||
|         # editable widget has normal picker html | ||||
|         result = widget.serialize(field, str(dt)) | ||||
|         self.assertIn('<wutta-datepicker', result) | ||||
| 
 | ||||
|         # readonly is rendered per app convention | ||||
|         result = widget.serialize(field, str(dt), readonly=True) | ||||
|         self.assertEqual(result, '2025-01-15') | ||||
| 
 | ||||
| 
 | ||||
| class TestWuttaDateTimeWidget(WebTestCase): | ||||
| 
 | ||||
|     def make_field(self, node, **kwargs): | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue