Skip to main content

A Python 3 port of python-slack-events-api to pure Django.

Project description

Django Slack Events

A light, Python 3 (only) reimplementation of python-slack-events-api in pure Django leveraging Signals.

Installation

pip install django_slack_events

Getting Started

You should be able to follow along with the app setup and development workflow portions of the original package directions to get things going on the Slack side. When you reach the Flask instructions, jump back over here to see how to proceed with Django.

Quick Start

Once you've installed the package, you add it to your INSTALLED_APPS as you might expect:

    INSTALLED_APPS = [
        ...
        'django_slack_events',
    ]

Then in your project's URLs you can set your endpoint name. Following the /slack/events convention, that looks like:

    from django_slack_events import SlackEventHandler

    urlpatterns = [
        ...
        path('slack/events/', SlackEventHandler.as_view()),
        ...
    ]

In order to properly verify incoming requests from Slack, the last thing you will need to do is set an environment variable pointing to the signing secret corresponding to your Slack app. If you're only running app, this is DJANGO_SLACK_EVENT_SIGNING_SECRET. Otherwise, see Multiple Apps below.

That's it! Your Django project is ready to handle incoming events from Slack.

Listening to Events

Events are handled as Django Signals, like so:

    from django.dispatch import receiver

    from django_slack_events import SlackEvent

    @receiver(SlackEvent)
    def my_callback(slack_payload, **kw):
        ...

The sender is helpfully set to the event type for you, so you can leverage Django's built-in filtering to listen to specific events:

    from django.dispatch import receiver

    from django_slack_events import SlackEvent, SlackEventType

    @receiver(SlackEvent, sender=SlackEventType.REACTION_ADDED)
    def reaction_added(slack_payload, **kw):
        payload = kw['slack_payload']
        ...

If you like this pattern of naming module-level functions after the Slack events they serve, it's encapsulated in the @slackevent decorator for you. Thus, the above can be rewritten:

    from django_slack_events.decorators import slackevent

    @slackevent
    def reaction_added(slack_payload, **kw):
        ...

Note that the more useful Slack payload comes in place of sender as your first positional argument in this case.

Full Example

Put this code in any file available in your app's ready context and set the appropriate environment variables, et voila!

    import os

    from django_slack_events.decorators import slackevent
    import slack


    slack_client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])


    @slackevent
    def app_home_opened(slack_payload, **kw):
        response = slack_client.views_publish(view={
            "type": "home",
            "blocks": [{
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": "[GENERAL KENOBI VOICE] Hello, there! ;)"
                }
            }]
        }, user_id=slack_payload['event']['user'])
        assert response['ok']

BUT WAIT, THERE'S MORE

Slack Interactions are extremely similar to events, in that they have a type, a payload, and can be handled in many cases by a simple function with access to the Slack API client.

Here's the above example extended to provide and handle a button:

    import os

    from django.dispatch import receiver
    from django_slack_events import SlackEvent, SlackInteractionType
    from django_slack_events.decorators import slackevent
    import slack


    slack_client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])


    @slackevent
    def app_home_opened(slack_payload, **kw):
        payload = kw['slack_payload']
        response = slack_client.views_publish(view={
            "type": "home",
            "blocks": [{
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": "You can add a button alongside text in your message. "
                },
                "accessory": {
                    "action_id": "click_me_123",
                    "type": "button",
                    "text": {
                            "type": "plain_text",
                            "text": "Button",
                            "emoji": True
                    },
                    "value": "click_me_123"
                }
            }]
        }, user_id=slack_payload['event']['user'])
        assert response['ok']


    @receiver(SlackEvent, sender=SlackInteractionType.BLOCK_ACTIONS)
    def block_actions(slack_payload, **kw):
        response = slack_client.views_publish(view={
            "type": "home",
            "blocks": [{
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": "[GENERAL KENOBI VOICE] Hello there!",
                },
            }]
        }, user_id=slack_payload['user']['id'])
        assert response['ok']

Note: For now, only BLOCK_ACTIONS, VIEW_CLOSED, and VIEW_SUBMISSION are supported. More functionality is planned in the future.

The problem with registering BLOCK_ACTIONS and many other interactive signals this way is that you would end up with a giant function with many if/else branches trying to handle all the buttons and knobs in your app. Slack lets you set an action_id on your payloads, so we can reuse our @slackevent pattern by using @slackaction instead and naming the function after the action ID.

The block_actions function above could then be rewritten:

    from django_slack_events.decorators import slackaction

    ...

    @slackaction
    def click_me_123(slack_payload, **kw):
        response = slack_client.views_publish(view={
            "type": "home",
            "blocks": [{
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": "[GENERAL KENOBI VOICE] Hello there!",
                },
            }]
        }, user_id=slack_payload['user']['id'])
        assert response['ok']

Multiple Apps

You can support multiple Django apps in the same project having Slack functionality by defining app-specific environment variables like DJANGO_SLACK_EVENT_<YOURBOT>_SIGNING_SECRET, where <YOURBOT> is your app's name in all uppercase. Each of these should point to the signing secret for the corresponding Slack app.

The project name can be determined in one of two ways:

  • Your events/interactions URL can append ?for=<botname>
  • It can be read from the first "directory" in your URL, like /botname/slack/events

In all cases, you must change this both in your project (your environment variables) and in Slack (your event and interaction URLs for each supported app). Some reserved names (currently slack and event(s)) are not valid for bots and are therefore ignored.

Shared events

Some events like app_home_opened may be used by many of your apps. In order to distinguish cleanly, pass the app name as derived above as your first argument to slackevent, like so:

    @slackevent('my_bot')
    def reaction_added(payload, **kw):
        ...

The @slackaction decorator can also be made app-specific in this way, an advisable precaution if you are not 100% sure you can keep your action IDs unique across all your apps.

Options Load URL

Slack offers the ability to dynamically load options for a select menu to give filtered search suggestions as the user types.

To handle Options Load requests you need to define a new URL just like you did to handle events, except using SlackOptionsLoadHandler:

    from django_slack_events import SlackEventHandler, SlackOptionsLoadHandler


    urlpatterns = [
        ...
        path('slack/events/', SlackEventHandler.as_view()),
        path('slack/options/', SlackOptionsLoadHandler.as_view()),
        ...
    ]

These use action_ids so you can just use @slackaction to handle them. Here's a sample handler that returns the example from the docs:

    @slackaction
    def search_glossary_term(payload, **kw):
        return {
          "options": [
            {
              "text": {
                "type": "plain_text",
                "text": "*this is plain_text text*"
              },
              "value": "value-0"
            },
            {
              "text": {
                "type": "plain_text",
                "text": "*this is plain_text text*"
              },
              "value": "value-1"
            },
            {
              "text": {
                "type": "plain_text",
                "text": "*this is plain_text text*"
              },
              "value": "value-2"
            }
          ]
        }

And here is the button handler to open a modal with this dynamic select:

    @slackaction
    def search_glossary(payload, **kw):
        slack_client.views_open(view={
            "type": "modal",
            "title": {
                "type": "plain_text",
                "text": "Search the Glossary",
            },
            "submit": {
                    "type": "plain_text",
                    "text": "Search",
            },
            "close": {
                    "type": "plain_text",
                    "text": "Cancel",
            },
            "blocks": [{
                "type": "input",
                "element": {
                    "type": "external_select",
                    "action_id": "search_glossary_term",
                    "placeholder": {
                            "type": "plain_text",
                            "text": "Search for term",
                    }
                },
                "label": {
                    "type": "plain_text",
                    "text": "Search Terms",
                }
            }]
        }, trigger_id=payload['trigger_id'])

Note: Only the BlockKit type (external_select) is currently supported.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for django-slack-events, version 0.15.10
Filename, size File type Python version Upload date Hashes
Filename, size django-slack-events-0.15.10.tar.gz (9.4 kB) File type Source Python version None Upload date Hashes View

Supported by

Pingdom Pingdom Monitoring Google Google Object Storage and Download Analytics Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page