Code Monkey home page Code Monkey logo

django-rest-hooks's Introduction

Travis CI Build PyPI Download PyPI Status

What are Django REST Hooks?

REST Hooks are fancier versions of webhooks. Traditional webhooks are usually managed manually by the user, but REST Hooks are not! They encourage RESTful access to the hooks (or subscriptions) themselves. Add one, two or 15 hooks for any combination of event and URLs, then get notificatied in real-time by our bundled threaded callback mechanism.

The best part is: by reusing Django's great signals framework, this library is dead simple. Here's how to get started:

  1. Add 'rest_hooks' to installed apps in settings.py.
  2. Define your HOOK_EVENTS in settings.py.
  3. Start sending hooks!

Using our built-in actions, zero work is required to support any basic created, updated, and deleted actions across any Django model. We also allow for custom actions (IE: beyond CRUD) to be simply defined and triggered for any model, as well as truly custom events that let you send arbitrary payloads.

By default, this library will just POST Django's JSON serialization of a model, but you can alternatively provide a serialize_hook method to customize payloads.

Please note: this package does not implement any UI/API code, it only provides a handy framework or reference implementation for which to build upon. If you want to make a Django form or API resource, you'll need to do that yourself (though we've provided some example bits of code below).

Changelog

Version 1.6.0:

Improvements:

  • Default handler of raw_hook_event uses the same logic as other handlers (see "Backwards incompatible changes" for details).

  • Lookup of event_name by model+action_name now has a complexity of O(1) instead of O(len(settings.HOOK_EVENTS))

  • HOOK_CUSTOM_MODEL is now similar to AUTH_USER_MODEL: must be of the form app_label.model_name (for django 1.7+). If old value is of the form app_label.models.model_name then it's automatically adapted.

  • rest_hooks.models.Hook is now really "swappable", so table creation is skipped if you have different settings.HOOK_CUSTOM_MODEL

  • rest_hooks.models.AbstractHook.deliver_hook now accepts a callable as payload_override argument (must accept 2 arguments: hook, instance). This was added to support old behavior of raw_custom_event.

Fixes:

  • HookAdmin.form now honors settings.HOOK_CUSTOM_MODEL

  • event_name determined from action+model is now consistent between runs (see "Backwards incompatible changes")

Backwards incompatible changes:

  • Dropped support for django 1.4
  • Custom HOOK_FINDER-s should accept and handle new argument payload_override. Built-in finder rest_hooks.utls.find_and_fire_hook already does this.
  • If several event names in settings.HOOK_EVENTS share the same 'app_label.model.action' (including 'app_label.model.action+') then django.core.exceptions.ImproperlyConfigured is raised
  • Receiver of raw_hook_event now uses the same logic as receivers of other signals: checks event_name against settings.HOOK_EVENTS, verifies model (if instance is passed), uses HOOK_FINDER. Old behaviour can be achieved by using trust_event_name=True, or instance=None to fire a signal.
  • If you have settings.HOOK_CUSTOM_MODEL of the form different than app_label.models.model_name or app_label.model_name, then it must be changed to app_label.model_name.

Development

Running the tests for Django REST Hooks is very easy, just:

git clone https://github.com/zapier/django-rest-hooks && cd django-rest-hooks

Next, you'll want to make a virtual environment (we recommend using virtualenvwrapper but you could skip this we suppose) and then install dependencies:

mkvirtualenv django-rest-hooks
pip install -r devrequirements.txt

Now you can run the tests!

python runtests.py

Requirements

  • Python 2 or 3 (tested on 2.7, 3.3, 3.4, 3.6)
  • Django 1.5+ (tested on 1.5, 1.6, 1.7, 1.8, 1.9, 1.10, 1.11, 2.0)

Installing & Configuring

We recommend pip to install Django REST Hooks:

pip install django-rest-hooks

Next, you'll need to add rest_hooks to INSTALLED_APPS and configure your HOOK_EVENTS setting:

### settings.py ###

INSTALLED_APPS = (
    # other apps here...
    'rest_hooks',
)

HOOK_EVENTS = {
    # 'any.event.name': 'App.Model.Action' (created/updated/deleted)
    'book.added':       'bookstore.Book.created',
    'book.changed':     'bookstore.Book.updated+',
    'book.removed':     'bookstore.Book.deleted',
    # and custom events, no extra meta data needed
    'book.read':         'bookstore.Book.read',
    'user.logged_in':    None
}

### bookstore/models.py ###

class Book(models.Model):
    # NOTE: it is important to have a user property
    # as we use it to help find and trigger each Hook
    # which is specific to users. If you want a Hook to
    # be triggered for all users, add '+' to built-in Hooks
    # or pass user_override=False for custom_hook events
    user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    # maybe user is off a related object, so try...
    # user = property(lambda self: self.intermediary.user)

    title = models.CharField(max_length=128)
    pages = models.PositiveIntegerField()
    fiction = models.BooleanField()

    # ... other fields here ...

    def serialize_hook(self, hook):
        # optional, there are serialization defaults
        # we recommend always sending the Hook
        # metadata along for the ride as well
        return {
            'hook': hook.dict(),
            'data': {
                'id': self.id,
                'title': self.title,
                'pages': self.pages,
                'fiction': self.fiction,
                # ... other fields here ...
            }
        }

    def mark_as_read(self):
        # models can also have custom defined events
        from rest_hooks.signals import hook_event
        hook_event.send(
            sender=self.__class__,
            action='read',
            instance=self # the Book object
        )

For the simplest experience, you'll just piggyback off the standard ORM which will handle the basic created, updated and deleted signals & events:

>>> from django.contrib.auth.models import User
>>> from rest_hooks.models import Hook
>>> jrrtolkien = User.objects.create(username='jrrtolkien')
>>> hook = Hook(user=jrrtolkien,
                event='book.added',
                target='http://example.com/target.php')
>>> hook.save()     # creates the hook and stores it for later...
>>> from bookstore.models import Book
>>> book = Book(user=jrrtolkien,
                title='The Two Towers',
                pages=327,
                fiction=True)
>>> book.save()     # fires off 'bookstore.Book.created' hook automatically
...

NOTE: If you try to register an invalid event hook (not listed on HOOK_EVENTS in settings.py) you will get a ValidationError.

Now that the book has been created, http://example.com/target.php will get:

POST http://example.com/target.php \
    -H Content-Type: application/json \
    -d '{"hook": {
           "id":      123,
           "event":   "book.added",
           "target":  "http://example.com/target.php"},
         "data": {
           "title":   "The Two Towers",
           "pages":   327,
           "fiction": true}}'

You can continue the example, triggering two more hooks in a similar method. However, since we have no hooks set up for 'book.changed' or 'book.removed', they wouldn't get triggered anyways.

...
>>> book.title += ': Deluxe Edition'
>>> book.pages = 352
>>> book.save()     # would fire off 'bookstore.Book.updated' hook automatically
>>> book.delete()   # would fire off 'bookstore.Book.deleted' hook automatically

You can also fire custom events with an arbitrary payload:

from rest_hooks.signals import raw_hook_event

user = User.objects.get(id=123)
raw_hook_event.send(
    sender=None,
    event_name='user.logged_in',
    payload={
        'username': user.username,
        'email': user.email,
        'when': datetime.datetime.now().isoformat()
    },
    user=user # required: used to filter Hooks
)

How does it work?

Django has a stellar signals framework, all REST Hooks does is register to receive all post_save (created/updated) and post_delete (deleted) signals. It then filters them down by:

  1. Which App.Model.Action actually have an event registered in settings.HOOK_EVENTS.
  2. After it verifies that a matching event exists, it searches for matching Hooks via the ORM.
  3. Any Hooks that are found for the User/event combination get sent a payload via POST.

How would you interact with it in the real world?

Let's imagine for a second that you've plugged REST Hooks into your API. One could definitely provide a user interface to create hooks themselves via a standard browser & HTML based CRUD interface, but the real magic is when the Hook resource is part of an API.

The basic target functionality is:

POST http://your-app.com/api/hooks?username=me&api_key=abcdef \
    -H Content-Type: application/json \
    -d '{"target":    "http://example.com/target.php",
         "event":     "book.added"}'

Now, whenever a Book is created (either via an ORM, a Django form, admin, etc...), http://example.com/target.php will get:

POST http://example.com/target.php \
    -H Content-Type: application/json \
    -d '{"hook": {
           "id":      123,
           "event":   "book.added",
           "target":  "http://example.com/target.php"},
         "data": {
           "title":   "Structure and Interpretation of Computer Programs",
           "pages":   657,
           "fiction": false}}'

It is important to note that REST Hooks will handle all of this hook callback logic for you automatically.

But you can stop it anytime you like with a simple:

DELETE http://your-app.com/api/hooks/123?username=me&api_key=abcdef

If you already have a REST API, this should be relatively straightforward, but if not, Tastypie is a great choice.

Some reference Tastypie or Django REST framework: + REST Hook code is below.

Tastypie

### resources.py ###

from tastypie.resources import ModelResource
from tastypie.authentication import ApiKeyAuthentication
from tastypie.authorization import Authorization
from rest_hooks.models import Hook

class HookResource(ModelResource):
    def obj_create(self, bundle, request=None, **kwargs):
        return super(HookResource, self).obj_create(bundle,
                                                    request,
                                                    user=request.user)

    def apply_authorization_limits(self, request, object_list):
        return object_list.filter(user=request.user)

    class Meta:
        resource_name = 'hooks'
        queryset = Hook.objects.all()
        authentication = ApiKeyAuthentication()
        authorization = Authorization()
        allowed_methods = ['get', 'post', 'delete']
        fields = ['event', 'target']

### urls.py ###

from tastypie.api import Api

v1_api = Api(api_name='v1')
v1_api.register(HookResource())

urlpatterns = patterns('',
    (r'^api/', include(v1_api.urls)),
)

Django REST framework (3.+)

### serializers.py ###

from django.conf import settings
from rest_framework import serializers, exceptions

from rest_hooks.models import Hook


class HookSerializer(serializers.ModelSerializer):
    def validate_event(self, event):
        if event not in settings.HOOK_EVENTS:
            err_msg = "Unexpected event {}".format(event)
            raise exceptions.ValidationError(detail=err_msg, code=400)
        return event    
    
    class Meta:
        model = Hook
        fields = '__all__'
        read_only_fields = ('user',)

### views.py ###

from rest_framework import viewsets

from rest_hooks.models import Hook

from .serializers import HookSerializer


class HookViewSet(viewsets.ModelViewSet):
    """
    Retrieve, create, update or destroy webhooks.
    """
    queryset = Hook.objects.all()
    model = Hook
    serializer_class = HookSerializer

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

### urls.py ###

from rest_framework import routers

from . import views

router = routers.SimpleRouter(trailing_slash=False)
router.register(r'webhooks', views.HookViewSet, 'webhook')

urlpatterns = router.urls

Some gotchas:

Instead of doing blocking HTTP requests inside of signals, we've opted for a simple Threading pool that should handle the majority of use cases.

However, if you use Celery, we'd really recommend using a simple task to handle this instead of threads. A quick example:

### settings.py ###

HOOK_DELIVERER = 'path.to.tasks.deliver_hook_wrapper'


### tasks.py ###

from celery.task import Task

import json
import requests


class DeliverHook(Task):
    max_retries = 5

    def run(self, target, payload, instance_id=None, hook_id=None, **kwargs):
        """
        target:     the url to receive the payload.
        payload:    a python primitive data structure
        instance_id:   a possibly None "trigger" instance ID
        hook_id:       the ID of defining Hook object
        """
        try:
            response = requests.post(
                url=target,
                data=json.dumps(payload),
                headers={'Content-Type': 'application/json'}
            )
            if response.status_code >= 500:
                response.raise_for_status()
        except requests.ConnectionError:
            delay_in_seconds = 2 ** self.request.retries
            self.retry(countdown=delay_in_seconds)


def deliver_hook_wrapper(target, payload, instance, hook):
    # instance is None if using custom event, not built-in
    if instance is not None:
        instance_id = instance.id
    else:
        instance_id = None
    # pass ID's not objects because using pickle for objects is a bad thing
    kwargs = dict(target=target, payload=payload,
                  instance_id=instance_id, hook_id=hook.id)
    DeliverHook.apply_async(kwargs=kwargs)

We also don't handle retries or cleanup. Generally, if you get a 410 or a bunch of 4xx or 5xx, you should delete the Hook and let the user know.

Extend the Hook model:

The default Hook model fields can be extended using the AbstractHook model. For example, to add a is_active field on your hooks:

### settings.py ###

HOOK_CUSTOM_MODEL = 'path.to.models.CustomHook'

### models.py ###

from django.db import models
from rest_hooks.models import AbstractHook

class CustomHook(AbstractHook):
    is_active = models.BooleanField(default=True)

The extended CustomHook model can be combined with a the HOOK_FINDER setting for advanced QuerySet filtering.

### settings.py ###

HOOK_FINDER = 'path.to.find_and_fire_hook'

### utils.py ###

from .models import CustomHook

def find_and_fire_hook(event_name, instance, **kwargs):
    filters = {
        'event': event_name,
        'is_active': True,
    }

    hooks = CustomHook.objects.filter(**filters)
    for hook in hooks:
        hook.deliver_hook(instance)

django-rest-hooks's People

Contributors

arnaudlimbourg avatar avelis avatar bcooksey avatar bryanhelmig avatar coady avatar erikcw avatar gvangool avatar gvrv avatar imposeren avatar imsickofmaps avatar jvd10 avatar mainanick avatar mannysz avatar nagesh4193 avatar pk026 avatar psaniko avatar sebastibe avatar tdruez avatar waldyrious avatar zupta avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-rest-hooks's Issues

Question :: why do we need the user property on each model?

I was looking for django apps for web hooks when I found this project. I find it useful but I was wondering why you need a user field/property/attribute on the model to hook it. I understand you use it to filter and trigger hooks. I just don't get the reasoning.

If you have two hooks

Hook(
    user=jrrtolkien,
    event='book.created',
    target='http://example.com/target1'
).save()
Hook(
    user=grrmartin,
    event='book.created',
    target='http://example.com/target2'
).save()

when a new book is created

Book(
    user=jrrtolkien,
    title='The Two Towers',
    pages=327,
    fiction=True
).save()

only the user jrrtolkien will get a notification. Which means a user cannot "subscribe" events related to other users (grrmartin won't know jrrtolkien wrote a new book).
Am I wrong?

Django 1.8+ support

Another compatibility ticket! Tests fail in Django 1.8+ with the following error:

Traceback (most recent call last):
  File "runtests.py", line 44, in <module>
    django.setup()
  File "/Users/briankung/Envs/django-rest-hooks/lib/python3.4/site-packages/django/__init__.py", line 18, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/Users/briankung/Envs/django-rest-hooks/lib/python3.4/site-packages/django/apps/registry.py", line 85, in populate
    app_config = AppConfig.create(entry)
  File "/Users/briankung/Envs/django-rest-hooks/lib/python3.4/site-packages/django/apps/config.py", line 86, in create
    module = import_module(entry)
  File "/Users/briankung/Envs/django-rest-hooks/lib/python3.4/importlib/__init__.py", line 109, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 2254, in _gcd_import
  File "<frozen importlib._bootstrap>", line 2237, in _find_and_load
  File "<frozen importlib._bootstrap>", line 2224, in _find_and_load_unlocked
ImportError: No module named 'django_comments'

Specifically, tests fail in 1.8.0, 1.8.4, and 1.8.7, and pass with 1.7.7 and 1.7.11.

Hook Data Templates

Hi!

We've been thinking about our shiny new RestHooks and thought that it would be neat if there was another column in the Hooks table that let you define an option "template" for the hook data if the hook destination needed it in an alternative format.

Very Simple example. Say I have this normal hook response defined in serialize_hook, but the data destination is expecting "phone_number" instead of "from_number":

data  = {
   'from_number': self.from_number
}

With hook templates I could change that to:

{
    'phone_number': {from_number}
}

Thoughts?

-Lyle

Add ability to extend and customize the Hook model

Provide an AbstractHook abstract model class that could be easily implemented in the local Django project to support all kind of customizations.

This would also require a settings to override the default usage of the Hook model to retrieve the proper Hook objects from the custom model.

Passing instance forces pickle

Because of the last two params on this line here: https://github.com/zapier/django-rest-hooks/blob/master/rest_hooks/models.py#L88 if you want to use a celery task to move the processing to an async task with celery you are forced to use pickle for task serialization which is bad: http://docs.celeryproject.org/en/latest/faq.html#isn-t-using-pickle-a-security-concern

Given the payload is the json serialized object, do we need to pass through the instance? Also, if the hook must be passed in, could it be hook.id?

Django 1.7 simplejson Import error

Traceback (most recent call last):
  File "manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/lib/python2.7/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "lib/python2.7/site-packages/django/core/management/__init__.py", line 354, in execute
    django.setup()
  File "/lib/python2.7/site-packages/django/__init__.py", line 21, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/lib/python2.7/site-packages/django/apps/registry.py", line 108, in populate
    app_config.import_models(all_models)
  File "/lib/python2.7/site-packages/django/apps/config.py", line 197, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/local/Cellar/python/2.7.8_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/lib/python2.7/site-packages/rest_hooks/models.py", line 6, in <module>
    from django.utils import simplejson as json
ImportError: cannot import name simplejson

Django 2.0 compatibility

Django 2.0 is now available https://www.djangoproject.com/weblog/2017/dec/02/django-20-released/

django-rest-hooks cannot be used with Django 2.0 at the moment:

[...]
lib/python3.6/site-packages/rest_hooks/models.py", line 43, in AbstractHook
    user = models.ForeignKey(AUTH_USER_MODEL, related_name='%(class)ss')
TypeError: __init__() missing 1 required positional argument: 'on_delete'

From https://docs.djangoproject.com/en/2.0/releases/2.0/#features-removed-in-2-0:

The on_delete argument for ForeignKey and OneToOneField is now required in models and migrations.

A value need to be set for on_delete on the user ForeignKey of the AbstractHook
model: https://github.com/zapier/django-rest-hooks/blob/master/rest_hooks/models.py#L43

@avelis any input on what should be a safe default value? CASCADE?

raw_custom_event should share logic with other hooks

Ideally raw_custom_event would share more logic with the other event types. Specifically the HOOK_FINDER setting /find_and_fire_hook.

For additional background, my particular use case is creating a set of admin hook subscriptions. I want admins to get webhook notifications for events on any user (basically the + option for all hook types). If there is a preferred way to go about this, I'd love to hear any input. Otherwise, what are the thoughts on integrating HOOK_FINDER into raw_custom_event?

Python 3 support?

The tests seem to be passing just fine with Python 3 and Django 1.7.7. I'm not sure if this means that it is compatible with Python3, however. Is it python3 compatible?

ManyToMany Updates Not Passed

Hi!

We're implementing this and noticed that if you update a ManyToMany field, the "updated" event is triggered, but the reference to the ManyToMany field in the serialize_hook method does not contain the updated values for the ManyToMany field.

Any advice?

-Lyle

Hooks do not fire reliably with "+" option

I noticed that hooks with the "+" option were not being triggered for all users as I'd expected from the documentation - at least, not reliably.

From the documentation:

If you want a Hook to be triggered for all users, add '+' to built-in Hooks or pass user_override=False for custom_hook events

However, in utils.py, user_override looks like it is set to True if there is a plus sign: https://github.com/zapier/django-rest-hooks/blob/master/rest_hooks/utils.py#L82

            if model == maybe_model and action == maybe_action[0]:
                event_name = maybe_event_name
                if len(maybe_action) == 2:
                    user_override = True

    if event_name:
        find_and_fire_hook(event_name, instance, user_override=user_override)

In find_and_fire_hook, meanwhile, user_override=True is not accounted for:

    if user_override is not False:
        if user_override:
            filters['user'] = user_override
        elif hasattr(instance, 'user'):
            filters['user'] = instance.user
        elif isinstance(instance, User):
            filters['user'] = instance
        else:
            raise Exception(
                '{} has no `user` property. REST Hooks needs this.'.format(repr(instance))
            )

       # ...comments...

    hooks = Hook.objects.filter(**filters)
    for hook in hooks:
        hook.deliver_hook(instance)

Setting filters['user'] = user_override results in filters['user'] = True. This means that Hook.objects.filter(**filters) will filter for user=True. I was very confused what that meant - I thought it reasonable to assume that it would might be equivalent to user__isnull=False, but the actual result is stranger. I tested it with a few of our own models.

It turns out that if you ask for SoAndSoModel.objects.filter(related_object=True), Django casts related_object=True to related_object_id=1 and related_object=False to related_object_id=0 in the query.

pprint(connection.queries)
# For SoAndSoModel.objects.filter(related_object=True)
[{'sql': 'SELECT "thing_SoAndSoModel"."id", '
         # ...
         'WHERE "thing_SoAndSoModel"."related_object_id" = 1 LIMIT 21',
  'time': '0.004'},

# For SoAndSoModel.objects.filter(related_object=False)
 {'sql': 'SELECT "thing_SoAndSoModel"."id", '
         # …
         'WHERE "thing_SoAndSoModel"."related_object_id" = 0 LIMIT 21',
  'time': '0.001’}]

Which means that rest_hooks is only returning hooks where user_id=1.

That's my theory, anyway. If I have a moment later, I'll write a test to confirm.

Is this project maintained?

I see some indicators that this project may no longer be maintained. I am considering incorporating this into our code base and I wanted to know if it would be possible to get some sort of confirmation from Zapier that there is an intention to maintain this project over the long term.

It appears that JIRA integration may be 'broken' which is preventing tests from passing and/or interested maintainers from merging PRs (see #67 and #68).

Any comment from Zapier on this would be greatly appreciated.

@bryanhelmig @bcooksey

New setting pointing to custom Model

The built in Signals (in models.py) don’t work with a custom Hook model because they are hard coded to use the built in model.

Seems like the right way to handle this is to add an optional setting pointing to the custom model, and then use the value of that setting instead of hardcoded references to models.Hook.

Filter When Hook Sent?

Hi Guys!

We're very interested in implementing this to make our Zapier integration snappier!

We have a couple of "brainstorming" question:
Because our system is dealing with phone calls that could be in various states, we only want to trigger events for things with a status of "completed". We wouldn't want the obj.created hook to trigger until the call actually completes.

I'm assuming the only way to do this is to manually fire the obj.created hook when the status of the call changes to "completed". Is that the best way? Is there some way to possibly filter when obj.updated hooks are sent through this API?

Thanks,
Lyle

Hooks bound to objects

I am trying to building something similar to the webhooks available for GitHub organizations.

I note that GitHub binds its hooks to their organization model i.e. send me all status updates of things within this organization.

I don't see any affordance for this functionality here, is that the case, is it worth adding? I think this is particularly useful combined with user overriding +, or at least I think that's what I want to be doing.

Zapier sends 'target_url' in the request and 'target' is expected.

When Zapier POSTs to my /api/v1/hooks/ uri, it sends this as the data...

{"subscription_url": "https://zapier.com/hooks/standard/1453504/8a7907ee05a54e68be99bcdc969155c6/", "target_url": "https://zapier.com/hooks/standard/1453504/8a7907ee05a54e68be99bcdc969155c6/", "event": "entry.created"}

To create a Hook Resource you need to provide user, event, and target.

The target is specified but it is target_url not target. (It also appears to be in subscription_url)

So the new resource is created but the target field is empty.

I realize I can just overwrite my Hook Resource's obj_create method and map target_url to target but it seems that these key names should match up. :)

Is my understanding correct?

Question

I think django-rest-hooks would be a good fit for a project I'm working on but would like to be sure before I head down the path of using it.

I have a django rest framework serializer already working from a view input.

I'd like to have this done as a scheduled event rather than manually.

  1. read JSON data in using requests to get a list of imdb ids and showtimes from our POS system
  2. serializer would then add the films into django.

Is this a good fit?

Automatic deployments on PyPI fail because mainteiner does not have verified email

travis can't publish new versions on PyPI:
https://travis-ci.org/zapier/django-rest-hooks/jobs/532372176#L796

HTTPError: 400 Client Error: User 'bryanhelmig' does not have a verified primary email address. Please add a verified primary email before attempting to upload to PyPI. See https://pypi.org/help/#verified-email for more information.for more information. for url: https://upload.pypi.org/legacy/

@avelis , @bryanhelmig Can someone fix this?

Last release not available through pip

While trying to install the lib:

Could not find a version that satisfies the requirement django-rest-hooks==1.6.0 (from -r /opt/app/setup/base_requirements.txt (line 152)) (from versions: 1.0.4, 1.0.5, 1.1.0, 1.1.1, 1.2.0, 1.2.1, 1.2.2, 1.3.0, 1.3.1, 1.4.0, 1.4.3, 1.5.0)

Using target url's result.

Hi.
Using this package, I developed simple project that send notification to client.
In other words, a user created a book, and then someone updated it.
Then origin user received notification "Someone changed your book.".
But in your doc, there is no way to receive data made by target url.
Help.

Add encryption option to webhooks.

Not sure if there is a better standard practice but using the webhooks I've noted that I need to setup an open POST hook endpoint on the server receiving the webhooks without any security. As a result anyone can send a webhook to me and I'd have to blindly accept it.

Another webhook system I've used would encrypt the request body using a shared secret created when setting up the webhook. That way no one could send a webhook unless they knew the shared secret.

I'd propose a method of adding a shared secret to this package - as an option in settings.

If the setting is present we add a new field to the AbstractHook and in the deliver_hook method we can encrypt the payload.

This could be done as a customisation but I reckon it adds more value as part of this package - it should be a common use case as open ended hook endpoints are not great.

Happy to make the contribution unless it's not wanted/there is a better practice.

Potential for Mis-use

If you want a Hook to be triggered for all users, add '+' to built-in Hooks

IMO that needs to be emphasized in the docs more clearly. Has serious potential for accidental misuse given the examples shown.

Client doesn't use connection pooling.

If requests.Sessions are used instead of the module level methods, then the client will automatically take advantage of connection pooling.

Relatedly, requests_futures also provides a FutureSession which would make the threaded client unnecessary.

Dynamic HOOK_EVENTS

I am implementing a webhook-like system, and your library looks to fit the bill – however, I need to define hooks on the fly – they're based on statuses stored in the database that the users/admins can customise – just wondering why you require settings be defined in advance in settings.py, and what problems I'd run into ripping out that validation.

Bind hooks to groups or users

Similar to issue #33 I'm interested in this project but need send webhooks based on an object belonging to a group and not just a user.

By the look of this we could do it by adding a group property to the AbstractHook model. Making both user and group optional but that at least one must be filled.

And then amending the find_and_fire_hook function to look for groups as well.

I can set up a fork to get this working. Would you be interested in merging it in if I do? Or should I roll my own version?

Fire hook on specific FIELD/s changed rather than any field

I´ll start by asking. Is it possible? I couldn't´t found a way to do that. I´ll explain what:

I have a model, say A with fields a, b, c. An instance of this model will be modified at some point. I would like to be able to set up a hook so it is only fired when field a or b are modified.

Is it possible to accomplish using current version? if not, it would be a good enhancement.

Greetings, and keep up the good work.

Easy to follow documentation

I have followed the documentation and to be honest I am a bit lost. I wanted to test a simple event where the user logs in an a query is sent to a server.

I have registered my hooks

HOOK_EVENTS = {
    # 'any.event.name': 'App.Model.Action' (created/updated/deleted)
    'user.logged_in':       'django.contrib.auth.models.User.created+',
    'user.added':       'django.db.models.signals.pre_save+',
    'user.changed':       'django.db.models.signals.pre_save+'
}

They are visible in django admin and I added the actions in the database. The request is not being sent however, what I'm I missing?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.