Add "falafel" custom date/time field type and widget
finally able to edit datetime fields, but feels like a lot of assumptions to make, just to determine time zone..so keeping naive UTC on the backend still, and naive local on the frontend in general this needs more polish, but is a start..
This commit is contained in:
parent
99065548ff
commit
a807a0f50c
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2018 Lance Edgar
|
||||
# Copyright © 2010-2023 Lance Edgar
|
||||
#
|
||||
# This file is part of Rattail.
|
||||
#
|
||||
|
@ -24,8 +24,7 @@
|
|||
Forms Library
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
from . import types
|
||||
# nb. import widgets before types, b/c types may refer to widgets
|
||||
from . import widgets
|
||||
from . import types
|
||||
from .core import Form, SimpleFileImport
|
||||
|
|
|
@ -590,8 +590,18 @@ class Form(object):
|
|||
self.schema[key] = node
|
||||
|
||||
def set_type(self, key, type_, **kwargs):
|
||||
|
||||
if type_ == 'datetime':
|
||||
self.set_renderer(key, self.render_datetime)
|
||||
|
||||
elif type_ == 'datetime_falafel':
|
||||
self.set_renderer(key, self.render_datetime)
|
||||
self.set_node(key, types.FalafelDateTime(request=self.request))
|
||||
if kwargs.get('helptext'):
|
||||
app = self.request.rattail_config.get_app()
|
||||
timezone = app.get_timezone()
|
||||
self.set_helptext(key, f"NOTE: all times are local to {timezone}")
|
||||
|
||||
elif type_ == 'datetime_local':
|
||||
self.set_renderer(key, self.render_datetime_local)
|
||||
elif type_ == 'date_plain':
|
||||
|
@ -871,6 +881,9 @@ class Form(object):
|
|||
if field.cstruct is colander.null:
|
||||
return '[]'
|
||||
|
||||
if isinstance(field.schema.typ, types.FalafelDateTime):
|
||||
return field.cstruct
|
||||
|
||||
try:
|
||||
return self.jsonify_value(field.cstruct)
|
||||
except Exception as error:
|
||||
|
|
|
@ -26,6 +26,7 @@ Form Schema Types
|
|||
|
||||
import re
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from rattail.db import model
|
||||
from rattail.gpc import GPC
|
||||
|
@ -33,6 +34,7 @@ from rattail.gpc import GPC
|
|||
import colander
|
||||
|
||||
from tailbone.db import Session
|
||||
from tailbone.forms import widgets
|
||||
|
||||
|
||||
class JQueryTime(colander.Time):
|
||||
|
@ -72,6 +74,50 @@ class DateTimeBoolean(colander.Boolean):
|
|||
return datetime.datetime.utcnow()
|
||||
|
||||
|
||||
class FalafelDateTime(colander.DateTime):
|
||||
"""
|
||||
Custom schema node type for rattail UTC datetimes
|
||||
"""
|
||||
widget_maker = widgets.FalafelDateTimeWidget
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
request = kwargs.pop('request')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
|
||||
def serialize(self, node, appstruct):
|
||||
if not appstruct:
|
||||
return colander.null
|
||||
|
||||
# cant use isinstance; dt subs date
|
||||
if type(appstruct) is datetime.date:
|
||||
appstruct = datetime.datetime.combine(appstruct, datetime.time())
|
||||
|
||||
if not isinstance(appstruct, datetime.datetime):
|
||||
raise colander.Invalid(node, f'"{appstruct}" is not a datetime object')
|
||||
|
||||
if appstruct.tzinfo is None:
|
||||
appstruct = appstruct.replace(tzinfo=self.default_tzinfo)
|
||||
|
||||
app = self.request.rattail_config.get_app()
|
||||
dt = app.localtime(appstruct, from_utc=True)
|
||||
|
||||
return json.dumps({
|
||||
'date': str(dt.date()),
|
||||
'time': str(dt.time()),
|
||||
})
|
||||
|
||||
def deserialize(self, node, cstruct):
|
||||
if not cstruct:
|
||||
return colander.null
|
||||
|
||||
app = self.request.rattail_config.get_app()
|
||||
result = datetime.datetime.strptime(cstruct, '%Y-%m-%dT%H:%M:%S')
|
||||
result = app.localtime(result)
|
||||
result = app.make_utc(result)
|
||||
return result
|
||||
|
||||
|
||||
class GPCType(colander.SchemaType):
|
||||
"""
|
||||
Schema type for product GPC data.
|
||||
|
|
|
@ -33,7 +33,6 @@ from deform import widget as dfwidget
|
|||
from webhelpers2.html import tags, HTML
|
||||
|
||||
from tailbone.db import Session
|
||||
from tailbone.forms.types import ProductQuantity
|
||||
|
||||
|
||||
class ReadonlyWidget(dfwidget.HiddenWidget):
|
||||
|
@ -119,6 +118,8 @@ class CasesUnitsWidget(dfwidget.Widget):
|
|||
return field.renderer(template, **values)
|
||||
|
||||
def deserialize(self, field, pstruct):
|
||||
from tailbone.forms.types import ProductQuantity
|
||||
|
||||
if pstruct is colander.null:
|
||||
return colander.null
|
||||
|
||||
|
@ -235,6 +236,13 @@ class JQueryTimeWidget(dfwidget.TimeInputWidget):
|
|||
)
|
||||
|
||||
|
||||
class FalafelDateTimeWidget(dfwidget.DateTimeInputWidget):
|
||||
"""
|
||||
Custom widget for rattail UTC datetimes
|
||||
"""
|
||||
template = 'datetime_falafel'
|
||||
|
||||
|
||||
class JQueryAutocompleteWidget(dfwidget.AutocompleteInputWidget):
|
||||
"""
|
||||
Uses the jQuery autocomplete plugin, instead of whatever it is deform uses
|
||||
|
|
|
@ -9,15 +9,55 @@ const TailboneTimepicker = {
|
|||
'placeholder="Click to select ..."',
|
||||
'icon-pack="fas"',
|
||||
'icon="clock"',
|
||||
':value="value ? parseTime(value) : null"',
|
||||
'hour-format="12"',
|
||||
'@input="timeChanged"',
|
||||
':time-formatter="formatTime"',
|
||||
'>',
|
||||
'</b-timepicker>'
|
||||
].join(' '),
|
||||
|
||||
props: {
|
||||
name: String,
|
||||
id: String
|
||||
}
|
||||
id: String,
|
||||
value: String,
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
formatTime(time) {
|
||||
if (time === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
let h = time.getHours()
|
||||
let m = time.getMinutes()
|
||||
let s = time.getSeconds()
|
||||
|
||||
h = h < 10 ? '0' + h : h
|
||||
m = m < 10 ? '0' + m : m
|
||||
s = s < 10 ? '0' + s : s
|
||||
|
||||
return h + ':' + m + ':' + s
|
||||
},
|
||||
|
||||
parseTime(time) {
|
||||
|
||||
if (time.getHours) {
|
||||
return time
|
||||
}
|
||||
|
||||
let found = time.match(/^(\d\d):(\d\d):\d\d$/)
|
||||
if (found) {
|
||||
return new Date(null, null, null,
|
||||
parseInt(found[1]), parseInt(found[2]))
|
||||
}
|
||||
},
|
||||
|
||||
timeChanged(time) {
|
||||
this.$emit('input', time)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Vue.component('tailbone-timepicker', TailboneTimepicker)
|
||||
|
|
23
tailbone/templates/deform/datetime_falafel.pt
Normal file
23
tailbone/templates/deform/datetime_falafel.pt
Normal file
|
@ -0,0 +1,23 @@
|
|||
<div tal:omit-tag=""
|
||||
tal:define="name name|field.name;
|
||||
vmodel vmodel|'field_model_' + name;">
|
||||
|
||||
<b-field grouped>
|
||||
${field.start_mapping()}
|
||||
|
||||
<b-field label="Date">
|
||||
<tailbone-datepicker name="date"
|
||||
v-model="${vmodel}.date">
|
||||
</tailbone-datepicker>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Time">
|
||||
<tailbone-timepicker name="time"
|
||||
v-model="${vmodel}.time">
|
||||
</tailbone-timepicker>
|
||||
</b-field>
|
||||
|
||||
${field.end_mapping()}
|
||||
</b-field>
|
||||
|
||||
</div>
|
Loading…
Reference in a new issue