Repository: line/line-bot-sdk-python
Pull request #61 (2017-09-07 13:28:21)

Add datetime picker action
LINE newer version supports new template action `datetime picker`. (mentioned by @tokuhirom in #60)
https://devdocs.line.me/en/#datetime-picker-action

I define all arguments which describe date or/and time as `str`, not python objects like `date`/`time`/`datetime`, according to the implementation of other SDKs.

Regarding this, postback event from webhook has additional item `postback.params`.
https://devdocs.line.me/en/#postback-params-object

---

ref)
* Java SDK implementation: https://github.com/line/line-bot-sdk-java/pull/129
* Go SDK implementation: https://github.com/line/line-bot-sdk-go/pull/51 & https://github.com/line/line-bot-sdk-go/pull/53 (WIP)


Patch:
diff --git a/README.rst b/README.rst
index b7deac7..0280f45 100644
--- a/README.rst
+++ b/README.rst
@@ -145,7 +145,7 @@ get\_group\_member\_profile(self, group\_id, user\_id, timeout=None)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Gets the user profile of a member of a group that the bot is in. This can be
-the user ID of a user who has not added the bot as a friend or has blocked 
+the user ID of a user who has not added the bot as a friend or has blocked
 the bot.
 
 https://devdocs.line.me/en/#get-group-room-member-profile
@@ -577,6 +577,10 @@ Event
     - reply\_token
     - postback: Postback
         - data
+        - params: Params
+          - date
+          - time
+          - datetime
 - BeaconEvent
     - type
     - timestamp
diff --git a/linebot/models/__init__.py b/linebot/models/__init__.py
index fcc4dc1..a25aabe 100644
--- a/linebot/models/__init__.py
+++ b/linebot/models/__init__.py
@@ -31,6 +31,7 @@ from .events import (  # noqa
     PostbackEvent,
     BeaconEvent,
     Postback,
+    Params,
     Beacon,
 )
 from .imagemap import (  # noqa
@@ -79,6 +80,7 @@ from .template import (  # noqa
     PostbackTemplateAction,
     MessageTemplateAction,
     URITemplateAction,
+    DatetimePickerTemplateAction,
     ImageCarouselTemplate,
     ImageCarouselColumn,
 )
diff --git a/linebot/models/events.py b/linebot/models/events.py
index 65711b8..0e4463f 100644
--- a/linebot/models/events.py
+++ b/linebot/models/events.py
@@ -265,15 +265,54 @@ class Postback(Base):
     https://devdocs.line.me/en/#postback-event
     """
 
-    def __init__(self, data=None, **kwargs):
+    def __init__(self, data=None, params=None, **kwargs):
         """__init__ method.
 
-        :param str data:
+        :param str data: Postback data
+        :param params: JSON object with the date and time
+            selected by a user through a datetime picker action.
+            Only returned for postback actions via the datetime picker.
+        :type params: T <= :py:class:`linebot.models.events.Params`
         :param kwargs:
         """
         super(Postback, self).__init__(**kwargs)
 
         self.data = data
+        self.params = self.get_or_new_from_json_dict(
+            params, Params
+        )
+
+
+class Params(Base):
+    """Params.
+
+    Object with the date and time selected by a user
+    through a datetime picker action.
+    The format for the full-date, time-hour, and time-minute
+    as shown below follow the RFC3339 protocol.
+
+    https://devdocs.line.me/en/#postback-params-object
+    """
+
+    def __init__(self, date=None, time=None, datetime=None, **kwargs):
+        """__init__ method.
+
+        :param str date: Date selected by user.
+            Only included in the date mode.
+            Format: full-date
+        :param str time: Time selected by the user.
+            Only included in the time mode.
+            Format: time-hour":"time-minute
+        :param str datetime: Date and time selected by the user.
+            Only included in the datetime mode.
+            Format: full-date"T"time-hour":"time-minute
+        :param kwargs:
+        """
+        super(Params, self).__init__(**kwargs)
+
+        self.date = date
+        self.time = time
+        self.datetime = datetime
 
 
 class Beacon(Base):
diff --git a/linebot/models/template.py b/linebot/models/template.py
index 671db7b..f569321 100644
--- a/linebot/models/template.py
+++ b/linebot/models/template.py
@@ -29,7 +29,8 @@ def _get_action(action):
         action, {
             'postback': PostbackTemplateAction,
             'message': MessageTemplateAction,
-            'uri': URITemplateAction
+            'uri': URITemplateAction,
+            'datetimepicker': DatetimePickerTemplateAction,
         }
     )
     return action_obj
@@ -361,3 +362,43 @@ class URITemplateAction(TemplateAction):
         self.type = 'uri'
         self.label = label
         self.uri = uri
+
+
+class DatetimePickerTemplateAction(TemplateAction):
+    """DatetimePickerTemplateAction.
+
+    https://devdocs.line.me/en/#template-messages
+
+    When this action is tapped, a postback event is returned via webhook
+    with the date and time selected by the user from the date and time selection dialog.
+    """
+
+    def __init__(self, label=None, data=None, mode=None,
+                 initial=None, max=None, min=None, **kwargs):
+        """__init__ method.
+
+        :param str label: Label for the action
+            Max: 20 characters
+        :param str data: String returned via webhook
+            in the postback.data property of the postback event
+            Max: 300 characters
+        :param str mode: 	Action mode
+            date: Pick date
+            time: Pick time
+            datetime: Pick date and time
+        :param str initial: Initial value of date or time
+        :param str max: Largest date or time value that can be selected.
+            Must be greater than the min value.
+        :param str min: Smallest date or time value that can be selected.
+            Must be less than the max value.
+        :param kwargs:
+        """
+        super(DatetimePickerTemplateAction, self).__init__(**kwargs)
+
+        self.type = 'datetimepicker'
+        self.label = label
+        self.data = data
+        self.mode = mode
+        self.initial = initial
+        self.max = max
+        self.min = min


Test patch:
diff --git a/tests/api/test_send_template_message.py b/tests/api/test_send_template_message.py
index d46d24f..9a92ed4 100644
--- a/tests/api/test_send_template_message.py
+++ b/tests/api/test_send_template_message.py
@@ -24,13 +24,17 @@ from linebot import (
 )
 from linebot.models import (
     TemplateSendMessage, ButtonsTemplate,
-    PostbackTemplateAction, MessageTemplateAction, URITemplateAction,
+    PostbackTemplateAction, MessageTemplateAction,
+    URITemplateAction, DatetimePickerTemplateAction,
     ConfirmTemplate, CarouselTemplate, CarouselColumn,
     ImageCarouselTemplate, ImageCarouselColumn
 )
 
 
 class TestLineBotApi(unittest.TestCase):
+
+    maxDiff = None
+
     def setUp(self):
         self.tested = LineBotApi('channel_secret')
 
@@ -161,6 +165,37 @@ class TestLineBotApi(unittest.TestCase):
                                 uri='http://example.com/2'
                             )
                         ]
+                    ),
+                    CarouselColumn(
+                        thumbnail_image_url='https://example.com'
+                                            '/item3.jpg',
+                        title='this is menu3', text='description3',
+                        actions=[
+                            DatetimePickerTemplateAction(
+                                label="datetime picker date",
+                                data="action=sell&itemid=2&mode=date",
+                                mode="date",
+                                initial="2013-04-01",
+                                min="2011-06-23",
+                                max="2017-09-08"
+                            ),
+                            DatetimePickerTemplateAction(
+                                label="datetime picker time",
+                                data="action=sell&itemid=2&mode=time",
+                                mode="time",
+                                initial="10:00",
+                                min="00:00",
+                                max="23:59"
+                            ),
+                            DatetimePickerTemplateAction(
+                                label="datetime picker datetime",
+                                data="action=sell&itemid=2&mode=datetime",
+                                mode="datetime",
+                                initial="2013-04-01T10:00",
+                                min="2011-06-23T00:00",
+                                max="2017-09-08T23:59"
+                            )
+                        ]
                     )
                 ]
             )
@@ -219,6 +254,41 @@ class TestLineBotApi(unittest.TestCase):
                                 "uri": "http://example.com/2"
                             }
                         ]
+                    },
+                    {
+                        "thumbnailImageUrl":
+                            "https://example.com/item3.jpg",
+                        "title": "this is menu3",
+                        "text": "description3",
+                        "actions": [
+                            {
+                                "type": "datetimepicker",
+                                "label": "datetime picker date",
+                                "data": "action=sell&itemid=2&mode=date",
+                                "mode": "date",
+                                "initial": "2013-04-01",
+                                "min": "2011-06-23",
+                                "max": "2017-09-08"
+                            },
+                            {
+                                "type": "datetimepicker",
+                                "label": "datetime picker time",
+                                "data": "action=sell&itemid=2&mode=time",
+                                "mode": "time",
+                                "initial": "10:00",
+                                "min": "00:00",
+                                "max": "23:59"
+                            },
+                            {
+                                "type": "datetimepicker",
+                                "label": "datetime picker datetime",
+                                "data": "action=sell&itemid=2&mode=datetime",
+                                "mode": "datetime",
+                                "initial": "2013-04-01T10:00",
+                                "min": "2011-06-23T00:00",
+                                "max": "2017-09-08T23:59"
+                            }
+                        ]
                     }
                 ]
             }
diff --git a/tests/test_webhook.py b/tests/test_webhook.py
index 930a98b..f277563 100644
--- a/tests/test_webhook.py
+++ b/tests/test_webhook.py
@@ -27,6 +27,7 @@ from linebot.models import (
     TextMessage, ImageMessage, VideoMessage, AudioMessage,
     LocationMessage, StickerMessage,
     SourceUser, SourceRoom, SourceGroup,
+    Params,
 )
 
 
@@ -195,6 +196,7 @@ class TestWebhookParser(unittest.TestCase):
         self.assertEqual(events[10].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
         self.assertEqual(events[10].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
         self.assertEqual(events[10].postback.data, 'action=buyItem&itemId=123123&color=red')
+        self.assertEqual(events[10].postback.params, None)
 
         # BeaconEvent, SourceUser
         self.assertIsInstance(events[11], BeaconEvent)
@@ -254,6 +256,45 @@ class TestWebhookParser(unittest.TestCase):
         self.assertEqual(events[14].message.type, 'text')
         self.assertEqual(events[14].message.text, 'Hello, world')
 
+        # PostbackEvent, SourceUser, with date params
+        self.assertIsInstance(events[15], PostbackEvent)
+        self.assertEqual(events[15].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA')
+        self.assertEqual(events[15].type, 'postback')
+        self.assertEqual(events[15].timestamp, 1462629479859)
+        self.assertIsInstance(events[15].source, SourceUser)
+        self.assertEqual(events[15].source.type, 'user')
+        self.assertEqual(events[15].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[15].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[15].postback.data, 'action=buyItem&itemId=123123&color=red')
+        self.assertIsInstance(events[15].postback.params, Params)
+        self.assertEqual(events[15].postback.params.date, '2013-04-01')
+
+        # PostbackEvent, SourceUser, with date params
+        self.assertIsInstance(events[16], PostbackEvent)
+        self.assertEqual(events[16].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA')
+        self.assertEqual(events[16].type, 'postback')
+        self.assertEqual(events[16].timestamp, 1462629479859)
+        self.assertIsInstance(events[16].source, SourceUser)
+        self.assertEqual(events[16].source.type, 'user')
+        self.assertEqual(events[16].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[16].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[16].postback.data, 'action=buyItem&itemId=123123&color=red')
+        self.assertIsInstance(events[15].postback.params, Params)
+        self.assertEqual(events[16].postback.params.time, '10:00')
+
+        # PostbackEvent, SourceUser, with date params
+        self.assertIsInstance(events[17], PostbackEvent)
+        self.assertEqual(events[17].reply_token, 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA')
+        self.assertEqual(events[17].type, 'postback')
+        self.assertEqual(events[17].timestamp, 1462629479859)
+        self.assertIsInstance(events[17].source, SourceUser)
+        self.assertEqual(events[17].source.type, 'user')
+        self.assertEqual(events[17].source.user_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[17].source.sender_id, 'U206d25c2ea6bd87c17655609a1c37cb8')
+        self.assertEqual(events[17].postback.data, 'action=buyItem&itemId=123123&color=red')
+        self.assertIsInstance(events[15].postback.params, Params)
+        self.assertEqual(events[17].postback.params.datetime, '2013-04-01T10:00')
+
 
 class TestWebhookHandler(unittest.TestCase):
     def setUp(self):
@@ -321,6 +362,11 @@ class TestWebhookHandler(unittest.TestCase):
         self.assertEqual(self.calls[10], '6 postback')
         self.assertEqual(self.calls[11], '7 beacon')
         self.assertEqual(self.calls[12], '7 beacon')
+        self.assertEqual(self.calls[13], '1 message_text')
+        self.assertEqual(self.calls[14], '1 message_text')
+        self.assertEqual(self.calls[15], '6 postback')
+        self.assertEqual(self.calls[16], '6 postback')
+        self.assertEqual(self.calls[17], '6 postback')
 
 
 if __name__ == '__main__':
diff --git a/tests/text/webhook.json b/tests/text/webhook.json
index cad9415..7a0bea8 100644
--- a/tests/text/webhook.json
+++ b/tests/text/webhook.json
@@ -187,6 +187,51 @@
         "type": "text",
         "text": "Hello, world"
       }
+    },
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "postback",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "postback": {
+        "data": "action=buyItem&itemId=123123&color=red",
+        "params": {
+          "date": "2013-04-01"
+        }
+      }
+    },
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "postback",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "postback": {
+        "data": "action=buyItem&itemId=123123&color=red",
+        "params": {
+          "time": "10:00"
+        }
+      }
+    },
+    {
+      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
+      "type": "postback",
+      "timestamp": 1462629479859,
+      "source": {
+        "type": "user",
+        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
+      },
+      "postback": {
+        "data": "action=buyItem&itemId=123123&color=red",
+        "params": {
+          "datetime": "2013-04-01T10:00"
+        }
+      }
     }
   ]
 }