Specify DateTime format on zope.schema.Date on Plone - plone

I'm working on a form with Formlib that looks like this:
from zope.schema import Choice, Float, Int, Date, TextLine
from Products.Five.formlib.formbase import PageForm
class ISimuladorForm(Interface):
"""
Zope Interface for the financial simulator for sofomanec.
"""
start_date = Date(title=_(u'Start Date'),
description=_(u'Loan start date.'),
required=False)
.
.
.
class SimuladorForm(PageForm):
form_fields = form.FormFields(ISimuladorForm)
The default input format for start_date is "mm/dd/yy", but users need to input the start_date in this format: "dd/mm/yy".
How do I change the default Date format for this Interface/Schema/Form?

You can use the DateI18nWidget instead of the default DateWidget.
It takes a displayStyle attribute that controls the formatting of the value, and it'll use the request locale to format the date. displayStyle must be one of 'full', 'long', 'medium', 'short', or None and refers to the date formats defined in zope.i18n; the default is None, which I think means 'short' but this is unclear from the code.
The exact formatting is taken from the request locale, which in turn is based on the language set for the Plone site by the portal_languages tool. Thus setting the language of the site also determines what date formats the DateI18nWidget will use; these are defined in the zope.i18n package in the locales/data directory, in a set of XML files (look for the <dateFormats> element).
If this isn't satisfactory then you'll have to create a custom browser widget. Your best bet is to subclass the DateWidget yourself and provide a new _toFormValue method to format the dates the way you want.

This might be helpful to add a custom date widget to your formlib form:
http://plone.org/documentation/manual/developer-manual/forms/using-zope.formlib/customizing-the-template-and-the-widgets
I suggest to write your own date widget by deriving from one of the existing
date widget classes:
http://svn.zope.org/zope.formlib/trunk/src/zope/formlib/textwidgets.py?rev=113031&view=markup
A custom conversion of the date format using the
_toFieldValue()
_fromFieldValue()
hooks is pretty easy...look at the existing code.

This is what I did:
from zope.app.form.browser import DateI18nWidget
from zope.i18n.format import DateTimeParseError
from zope.app.form.interfaces import ConversionError
class MyDateI18nWidget(DateI18nWidget):
displayStyle = None
def _toFieldValue(self, input):
if input == self._missing:
return self.context.missing_value
else:
try:
formatter = self.request.locale.dates.getFormatter(
self._category, (self.displayStyle or None))
return formatter.parse(input.lower())
except (DateTimeParseError, ValueError), v:
raise ConversionError(_("Invalid datetime data"),
"%s (%r)" % (v, input))
class SimuladorForm(PageForm):
...
form_fields['start_date'].custom_widget = MyDateI18nWidget

Related

How to create a momentJS object with a specfic time and a specific timezone(other than the default local time zone)?

I will receive an string(with time and date) from the frontend. The string format is this "2021-08-16T23:15:00.000Z". I intend to declare a moment object with the input string, along with a specific timezone(other than the local one).
import moment from "moment";
import "moment-timezone";
// The input string I receive from the frontend.
let strTime = "2021-08-16T23:15:00.000Z";
console.log(strTime); //2021-08-16T23:15:00.000Z
let time = moment.tz(strTime, "YYYY-MM-DDTHH:mm:ss.SSSZ","America/Boise");
console.log(time); // Moment<2021-08-16T17:15:00-06:00>, undesired value
let UTCtime = moment.utc(time);
console.log(UTCtime);
As far as what I understood from this question, console.log(time) should output a moment object of time 23:15:00, but with timezone "America/Boise".
What I intend is time to have the same time i.e "23:15:00.000", with "America/Boise" as timezone.
So that when I later convert that time to UTC, I need to get the right value w.r.t the timezone "America/Boise" and not my local timezone. How can I do this.
I figured out a solution.
const momenttz = require("moment-timezone");
const moment = require("moment");
// The input string I receive from the frontend.
let strTime = "2021-08-16T23:15:00.000Z";
console.log(strTime); //2021-08-16T23:15:00.000Z
let time = moment.utc(strTime);
time.tz("America/Boise", true);
console.log(time.tz());
console.log(time); // Moment<2021-08-16T23:15:00-06:00>, desired value
let UTCtime = moment.utc(time);
console.log(UTCtime); // Moment<2021-08-17T05:15:00Z>
In the above code, at console.log(time),time has the value "23:15:00.000" with required timezone "America/Boise". This makes it possible for you to get the right value , when you later convert it to UTC.
This is made possible by passing an optional second parameter to .tz mutator of moment-timezone as true which changes only the timezone (and its corresponding offset), and does not affect the time value.
time.tz(timezone, true);
A sample example of using this is given in the answer code above.
You can read more about it here in the Moment Timezone Documentation

Java 8 Parse ISO-8601 date ignoring presence (or absence) of timezone

My application should be able to parse date ignoring timezone (I always know for sure that it is UTC). The problem is that the date might come in both following forms -
2017-09-11T12:44:07.793Z
0001-01-01T00:00:00
I can parse the first one using LocalDateTime, and the second one using Instant class. Is there a way to do that using a single mechanism?
P.S. I'm trying to avoid hardcoding Z at the end of the input string
If the Z offset is optional, you can use a java.time.format.DateTimeFormatterBuilder with an optional section:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// date/time
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
// optional offset
.optionalStart().appendOffsetId()
// create formatter
.toFormatter();
Then you can use the parseBest method, with a list of TemporalQuery's that tries to create the correspondent object. Then you check the return type and act accordingly:
Instant instant = null;
// tries to create Instant, and if it fails, try a LocalDateTime
TemporalAccessor parsed = fmt.parseBest("2017-09-11T12:44:07.793Z", Instant::from, LocalDateTime::from);
if (parsed instanceof Instant) {
instant = (Instant) parsed;
} else if (parsed instanceof LocalDateTime) {
// convert LocalDateTime to UTC instant
instant = ((LocalDateTime) parsed).atOffset(ZoneOffset.UTC).toInstant();
}
System.out.println(instant); // 2017-09-11T12:44:07.793Z
Running with the second input (0001-01-01T00:00:00) produces the Instant equivalent to 0001-01-01T00:00:00Z.
In the example above, I used just Instant::from and LocalDateTime::from, so the formatter tries to first create an Instant. If it's not possible, then it tries to create a LocalDateTime. You can add as many types you want to that list (for example, I could add ZonedDateTime::from, and if a ZonedDateTime is created, I could just convert to Instant using toInstant() method).
As you know for sure that the input is always in UTC, you can also set it directly in the formatter:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// date/time
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
// optional offset
.optionalStart().appendOffsetId()
// create formatter with UTC
.toFormatter().withZone(ZoneOffset.UTC);
So you can parse it directly to Instant:
System.out.println(Instant.from(fmt.parse("2017-09-11T12:44:07.793Z"))); // 2017-09-11T12:44:07.793Z
System.out.println(Instant.from(fmt.parse("0001-01-01T00:00:00"))); // 0001-01-01T00:00:00Z
You can "parseBest", like this:
DateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[Z]");
Temporal parsed = parser.parseBest(inputString, Instant::from, LocalDateTime::from);
Then you should check what did get parsed, and act accordingly.
The parseBest method will work with any type of TemporalQuery, including most of from methods available on java.time classes. So you can lengthen that list with LocalDate.from, for example.
You can also use that method and lambdas to coerse parse results to the type you want without having instanceof checks that are external for result resolution (although not without one cast):
Instant parsed = (Instant) parser.parseBest(inputString,
Instant::from,
interResult -> LocalDateTime.from(interResult).atZone(ZoneOffset.UTC).toInstant())
Notice that second option uses lambda that converts LocalDateTime to ZonedDateTime and then to Instant, so the parse results are always coersed to Instant.

How to set default css classes for all built-in form widgets in Django

Short version: is it possible to define a set of default css classes that Django should use whenever rendering a form ?
Long version:
The context is as follows: I would like to use the css classes defined in the w3.css framework for all my forms (http://www.w3schools.com/w3css/default.asp). I have seen that it is possible to do that in Django at form class definition or at form rendering, but it requires in both cases an explicit declaration of all the form fields. That means that I loose all the benefit of automatic form generation for ModelForms. I would like something as follows instead:
Define somewhere (e.g. in the settings file) a default mapping between form fields / widgets and css classes, e.g. 'textinput': 'my_default_css_class_for_text_inputs'
By default, for all automatic generation and rendering of forms, the default css classes defined in (1) are used, with no or minimal modification of the existing form classes
For specific forms, I can overload the defaults with other values
As far as I've understood, such behaviour is not possible in django. The crispy-forms package seems to go in that direction, but it seems to do much more than just that, and I am not sure that I want all the extra complexity (I'm still a newbie around here). An alternative would be to use javascript to add the classes on the client side. It looks like an ugly bad practice to me.
Could anyone confirm my understanding of this issue and point me towards elegant solutions, if any ?
Thanks !
Jonathan
I've managed to find the answer to my question and I'm posting it here for posterity. For the label class, I had some inspiration from here and here (answer from user2732686). The first link suggests to redefine the label_tag method of the BoundField class at run-time. It's a less verbose solution than the one suggested in the 2nd link, but at the cost of a project-wide hack, which I would not recommend. Here, I follow Django's subclassing mania, as suggested in the 2nd link for the labels.
In the projects settings.py, add:
# Default css classes for widgets and labels
DEFAULT_CSS = {
'error': 'w3-panel w3-red', # displayed in the label
'errorlist': 'w3-padding-8 w3-red', # encloses the error list
'required': 'w3-text-indigo', # used in the label and label + input enclosing box. NB: w3-validate only works if the input precedes the label!
'label': 'w3-label',
'Textarea': 'w3-input w3-border',
'TextInput': 'w3-input w3-border',
'Select': 'w3-select w3-border',
}
NB: apart from the 4 first keys, the keys must match Django's widget names.
In your forms.py (or elsewhere), add:
from django.forms import ModelForm, inlineformset_factory, Form, BoundField
from django.forms.utils import ErrorList
from django.utils.html import format_html, force_text
from django.conf import settings
class CustErrorList(ErrorList):
# custom error list format to use defcss
def __str__(self):
return self.as_div()
def as_div(self):
if not self:
return ''
return format_html('<div class="{}">{}</div>',
settings.DEFAULT_CSS['errorlist'],
' '.join( [ force_text(e) for e in self ] )
)
class CustBoundField(BoundField):
# overload label_tag to include default css classes for labels
def label_tag(self, contents=None, attrs=None, label_suffix=None):
newcssclass = settings.DEFAULT_CSS['label']
if attrs is None:
attrs = {}
elif 'class' in attrs:
newcssclass = ' '.join( [ attrs['class'], newcssclass ] ) # NB: order has no impact here (but it does in the style sheet)
attrs.update( { 'class': newcssclass } )
# return the output of the original method with the modified attrs
return super( CustBoundField, self ).label_tag( contents, attrs, label_suffix )
def custinit(self, subclass, *args, **kwargs):
# overload Form or ModelForm inits, to use default CSS classes for widgets
super( subclass, self ).__init__(*args, **kwargs)
self.error_class = CustErrorList # change the default error class
# Loop on fields and add css classes
# Warning: must loop on fields, not on boundfields, otherwise inline_formsets break
for field in self.fields.values():
thiswidget = field.widget
if thiswidget .is_hidden:
continue
newcssclass = settings.DEFAULT_CSS[ thiswidget.__class__.__name__ ]
thisattrs = thiswidget.attrs
if 'class' in thisattrs:
newcssclass = ' '.join( [ thisattrs['class'], newcssclass ] ) # NB: order has no impact here (but it does in the style sheet)
thisattrs.update( { 'class': newcssclass } )
def custgetitem(self, name):
# Overload of Form getitem to use the custom BoundField with
# css-classed labels. Everything here is just a copy of django's version,
# apart from the call to CustBoundField
try:
field = self.fields[name]
except KeyError:
raise KeyError(
"Key '%s' not found in '%s'. Choices are: %s." % (
name,
self.__class__.__name__,
', '.join(sorted(f for f in self.fields)),
)
)
if name not in self._bound_fields_cache:
self._bound_fields_cache[name] = CustBoundField( self, field, name )
# In the original version, field.get_bound_field is called, but
# this method only calls BoundField. It is much easier to
# subclass BoundField and call it directly here
return self._bound_fields_cache[name]
class DefaultCssModelForm(ModelForm):
# Defines the new reference ModelForm, with default css classes
error_css_class = settings.DEFAULT_CSS['error']
required_css_class = settings.DEFAULT_CSS['required']
def __init__(self, *args, **kwargs):
custinit(self, DefaultCssModelForm, *args, **kwargs)
def __getitem__(self, name):
return custgetitem(self, name)
class DefaultCssForm(Form):
# Defines the new reference Form, with default css classes
error_css_class = settings.DEFAULT_CSS['error']
required_css_class = settings.DEFAULT_CSS['required']
def __init__(self, *args, **kwargs):
custinit(self, DefaultCssForm, *args, **kwargs)
def __getitem__(self, name):
return custgetitem(self, name)
NB: replace <MY_PROJECT> with your project name
Then, you simply subclass DefaultCssModelForm and DefaultCssForm instead of ModelForm and Form when defining your forms. For formsets, use these classes as base classes. To illustrate:
class MyForm(DefaultCssModelForm):
class Meta:
model = MyModel
fields = '__all__'
MyFormSet = inlineformset_factory( ..., ..., form=DefaultCssModelForm, ... )

Plone Dexterity forms- Is it possible to set the default of a field equal to a value returned from a function in Add form?

I have a dexterity content type and I have been following along with the schema-driven types tutorial from the Dexterity Manual (http://docs.plone.org/external/plone.app.dexterity/docs/schema-driven-types.html#the-schema) and I am trying to change the default value of a field.
The value being returned is based on a portal catalog search. The field is an int type in which I want it equal to the highest number + 1 of the field in question. Because this is a field I want to hide, I figured maybe it would be possible to set the default value through returning a value from a function
value into the default parameter of a field.
Here is my Interface:
class ISupplier(model.Schema):
"""
Supplier of an asset or services
"""
supplier_id = schema.Int(
title=_(u"Supplier ID"),
description=_(u"ID that links to the database"),
required=True,
default=newID
)
...
Here is my function that I am trying to use to return a value. It is outside of classes.
#grok.provider(IContextSourceBinder)
def newID(context):
limit = 1
catalog = getToolByName(context, 'portal_catalog')
result = catalog.searchResults(portal_type='gpcl.supplier.supplier',
sort_on='supplier_id',
sort_order='descending',
sort_limit=limit
)[:1]
return result.supplier_id + 1
The reason I thought it'd be possible to do something like this is because in a choice type field I set the source equal to a value returned from a function:
form.widget(supplierType=CheckBoxFieldWidget)
supplierType = schema.List(title=u'Types',
value_type=schema.Choice(source=supplierTypes),
required=True
supplierTypes, the function, starts off like this:
#grok.provider(IContextSourceBinder)
def supplierTypes(context):
"""
"""
...
I did try using default_value:
#form.default_value(field=ISupplier['supplier_id'])
def supplierIDDefaultValue(data):
defaultID = newID
return defaultID
This did not work unfortunately. I believe I actually may be having a misunderstanding altogether. If I could have any help, I would greatly appreciate it.

SimpleTerm title not being set

I have a form with a SelectFieldWidget, that is currently using a static vocabularly, which is basically this:
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
primary_contacts = SimpleVocabulary([
SimpleTerm( unicode(token), title=unicode(token.upper()), token=token ) for token in [
'one','two','three','four','five','six','seven','eight','nine','ten',
]
])
The vocabulary is assigned to the field in the form schema:
form.widget( primary_contact_person=SelectFieldWidget )
primary_contact_person = schema.List(
title = u'Nominate Primary Contact',
required = False,
value_type = schema.Choice(
vocabulary=primary_contacts,
)
)
The schema is then serialized using plone.supermodel & then deserialized when needed by the form (this is for another requirement).
The form is using a custom, handwritten template, and I'm in the process of adding the tal statements to generate the select field options. I had thought I could do this through referencing the widgets on the form, but when I do that I hit a problem:
(Pdb) self # break point in form
<Products.Five.metaclass.edit_metadata object at 0xc1ce450>
(Pdb) select = self.widgets['primary_contact_person']
(Pdb) first = [t for t in select.terms][0]
(Pdb) first.token
'one'
(Pdb) first.value
u'one'
(Pdb) first.title
(Pdb)
The title is None on the term when it's accessed through the widget. I've tried looking it up through the vocabulary:
(Pdb) select.terms.getTermByToken('one').title
(Pdb)
But again, it's None. However, it is there for terms in the original vocabulary object:
(Pdb) from my.package import primary_contacts
(Pdb) [t for t in primary_contacts][0].title
u'ONE'
So while I could use the source vocab object directly to provide the values the template needs, the plan is for this vocabulary to eventually be dynamic, at which point I would expect I'd need to interrogate the widget itself.
What am I doing wrong here, why is title not being defined?
The problem was with plone.supermodel. I should have mentioned more clearly that I'm using the serialized schema to produce the form, and I apologise for this.
Basically, plone.supermodel provides an export/import process, which can only deal with simple lists of values.
# line 263 in plone.supermodel.exportimport
term = SimpleTerm(token = encoded, value = value, title = value)
The solution was to use named vocabularies, which serializes the reference to the vocabulary rather than the vocabulary itself.
Sorry again for the lack of information that made this harder to debug.

Resources