Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shorter format datetime-local defaults #761

Merged
merged 4 commits into from Jan 13, 2023

Conversation

russellfinlay
Copy link
Contributor

Describe the issue you are attempting to fix

This issue was previously raised in #450.

The HTML standard for datetime-local inputs set the default step attribute to 60, meaning that the lowest denomination of input can be expressed in minutes, not seconds.

https://html.spec.whatwg.org/#local-date-and-time-state-(type=datetime-local)

The step attribute is expressed in seconds. The step scale factor is 1000 (which converts the seconds to milliseconds, as used in the other algorithms). The default step is 60 seconds.

In such cases, it seems that common browser behaviour is to then send the input value, without any seconds, on form submission, in the format -

2022-11-02T12:23

The current default of WTForms is to validate against two possible date formats, both that require seconds -

def __init__(self, *args, **kwargs):
        kwargs.setdefault("format", ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"])

If seconds are not found, the validation will fail -

Not a valid datetime value.

Current fixes

This issue can be fixed by adding the correct format to a DateTimeLocalField() instance -

example_date_time = DateTimeLocalField('Example datetime', format="%Y-%m-%dT%H:%M")

or by change input step size to allow seconds (in HTML)**

<input type="datetime-local" step="1">

Proposal

Very simple PR to bring WTForms in line with the default behaviour of some browsers, by adding two formats (without seconds) to the default formats of DateTimeLocalField.

    def __init__(self, *args, **kwargs):
        kwargs.setdefault("format", ["%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%dT%H:%M"])

Allows validation of short datetime-local formats (%Y-%m-%dT%H:%M) by default, as it the default in common browsers (where step size is unchanged).

Relates to wtforms#450
@azmeuk
Copy link
Member

azmeuk commented Dec 24, 2022

Thank you for your contribution.
Indeed I suppose this field should work out-of-the-box with common browsers.
Can you add a CHANGELOG entry and fix the style tests?

@azmeuk
Copy link
Member

azmeuk commented Dec 25, 2022

Thank you. The style test is still failing though.

@russellfinlay
Copy link
Contributor Author

That should make the tox style testing pass now. It seems that black wanted to re-format that line.

I've also seen that two tests are failing from test_datetime.py. Would you like me to look at those before merge?

@azmeuk
Copy link
Member

azmeuk commented Dec 26, 2022

The GHA tests look OK. Are you seeing issues with test_datetime.py when you locally run the tests? What error message do you see?

@russellfinlay
Copy link
Contributor Author

Two tests failing when invoking pytest, one in tests\fields\test_datetime.py and the other in tests\fields\test_datetimelocal.py.

I've just re-run the tests without the changes from this pull request and I'm still seeing the same failures, so I imagine that it doesn't have anything to do with this commit.

tests\fields\test_datetime.py

__________________________________________________________________ test_basic ___________________________________________________________________ 

    def test_basic():
        d = datetime(2008, 5, 5, 4, 30, 0, 0)
        # Basic test with both inputs
        form = F(
            DummyPostData(
                a=["2008-05-05", "04:30:00"], b=["2008-05-05 04:30"], c=["5/5/2008 4:30"]
            )
        )
        assert form.a.data == d
        assert (
            form.a()
            == """<input id="a" name="a" type="datetime" value="2008-05-05 04:30:00">"""
        )
        assert form.b.data == d
        assert (
            form.b()
            == """<input id="b" name="b" type="datetime" value="2008-05-05 04:30">"""
        )
        assert form.c.data == d
        assert (
            form.c() == """<input id="c" name="c" type="datetime" value="5/5/2008 4:30">"""
        )
        assert form.validate()

        # Test with a missing input
        form = F(DummyPostData(a=["2008-05-05"]))
        assert not form.validate()
        assert form.a.errors[0] == "Not a valid datetime value."

        form = F(a=d, b=d, c=d)
        assert form.validate()
        assert form.a._value() == "2008-05-05 04:30:00"
        assert form.b._value() == "2008-05-05 04:30"
>       assert form.c._value() == "5/5/2008 4:30"

tests\fields\test_datetime.py:52:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <wtforms.fields.datetime.DateTimeField object at 0x0000018C16BD84D0>

    def _value(self):
        if self.raw_data:
            return " ".join(self.raw_data)
>       return self.data and self.data.strftime(self.format[0]) or ""
E       ValueError: Invalid format string

src\wtforms\fields\datetime.py:36: ValueError

tests\fields\test_datetimelocal.py

__________________________________________________________________ test_basic ___________________________________________________________________ 

    def test_basic():
        d = datetime(2008, 5, 5, 4, 30, 0, 0)
        # Basic test with both inputs
        form = F(
            DummyPostData(
                a=["2008-05-05", "04:30:00"], b=["2008-05-05 04:30"], c=["5/5/2008 4:30"]
            )
        )
        assert form.a.data == d
        assert (
            form.a()
            == '<input id="a" name="a" type="datetime-local" value="2008-05-05 04:30:00">'
        )
        assert form.b.data == d
        assert (
            form.b()
            == '<input id="b" name="b" type="datetime-local" value="2008-05-05 04:30">'
        )
        assert form.c.data == d
        assert (
            form.c()
            == '<input id="c" name="c" type="datetime-local" value="5/5/2008 4:30">'       
        )
        assert form.validate()

        assert form.validate()
        assert form.a._value() == "2008-05-05 04:30:00"
        assert form.b._value() == "2008-05-05 04:30"
>       assert form.c._value() == "5/5/2008 4:30"

tests\fields\test_datetimelocal.py:53:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <wtforms.fields.datetime.DateTimeLocalField object at 0x0000018C16A64250>    

    def _value(self):        if self.raw_data:
            return " ".join(self.raw_data)
>       return self.data and self.data.strftime(self.format[0]) or ""
E       ValueError: Invalid format string

src\wtforms\fields\datetime.py:36: ValueError

@azmeuk azmeuk merged commit 22e766c into wtforms:master Jan 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants