Metadata-Version: 2.1
Name: djangochannelsrestframework
Version: 0.0.4
Summary: RESTful API for WebSockets using django channels.
Home-page: https://github.com/hishnash/djangochannelsrestframework
Author: Matthaus Woolard
Author-email: matthaus.woolard@gmail.com
License: MIT
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Requires-Dist: Django (>=1.11)
Requires-Dist: channels (>=2.1.1)
Requires-Dist: djangorestframework (>=3.0)
Provides-Extra: tests
Requires-Dist: pytest (~=3.7.1) ; extra == 'tests'
Requires-Dist: pytest-django (~=3.4.1) ; extra == 'tests'
Requires-Dist: pytest-asyncio (~=0.9) ; extra == 'tests'
Requires-Dist: coverage (~=4.4) ; extra == 'tests'

==============================
Django Channels Rest Framework
==============================

Django Channels Rest Framework provides a DRF like interface for building channels-v2 websocket consumers.


This project can be used alongside HyperMediaChannels_ and ChannelsMultiplexer_ to create a Hyper Media Style api over websockets. However Django Channels Rest Framework is also a free standing framwork with the goal of providing an api that is familiar to DRF users. 

.. _HyperMediaChannels: https://github.com/hishnash/hypermediachannels
.. _ChannelsMultiplexer: https://github.com/hishnash/channelsmultiplexer

.. image:: https://travis-ci.org/hishnash/djangochannelsrestframework.svg?branch=master
    :target: https://travis-ci.org/hishnash/djangochannelsrestframework

Thanks to
---------


DCRF is based of a fork of `Channels Api <https://github.com/linuxlewis/channels-api>`_ and of course inspired by `Django Rest Framework <http://www.django-rest-framework.org/>`_.


Install
-------

.. code-block:: bash

  pip install djangochannelsrestframework


How to Use
==========



Observing a Model instance
--------------------------

Consumer that accepts subscribtions to an instance.

.. code-block:: python

   class TestConsumer(ObserverModelInstanceMixin, GenericAsyncAPIConsumer):
       queryset = get_user_model().objects.all()
       serializer_class = UserSerializer

this exposes the `retrieve` and `subscribe_instance` actions to that instance.

to subscribe send:


.. code-block:: python

   {
       "action": "subscribe_instance",
       "pk": 42,  # the id of the instance you are subscribing to
       "request_id": 4  # this id will be used for all resultent updates.
   }


Actions will be sent down out from the server:

.. code-block:: python

	{
		"action": "update",
		"errors": [],
		"response_status": 200,
		"request_id": 4,
		"data": {'email': '42@example.com', 'id': 42, 'username': 'thenewname'},
	}

Adding Custom actions
---------------------


.. code-block:: python

   class UserConsumer(GenericAsyncAPIConsumer):
       queryset = get_user_model().objects.all()
       serializer_class = UserSerializer

       @action()
       async def send_email(self, pk=None, to=None, **kwargs):
           user = await database_sync_to_async(self.get_object)(pk=pk)
           # ... do some stuff
           # remember to wrap all db actions in `database_sync_to_async`
           return {}, 200  # return the contenct and the response code.

       @action()  # if the method is not async it is already wrapped in `database_sync_to_async`
       def publish(self, pk=None, **kwargs):
           user = self.get_object(pk=pk)
	   # ...
	   return {'pk': pk}, 200

You can also use any of:

*  ``CreateModelMixin``
*  ``ListModelMixin``
*  ``RetrieveModelMixin``
*  ``UpdateModelMixin``
*  ``PatchModelMixin``
*  ``DeleteModelMixin``

just as you would in DRF.

.. code-block:: python

  from djangochannelsrestframework.generics import GenericAsyncAPIConsumer
  from djangochannelsrestframework.mixins import (
      RetrieveModelMixin,
      UpdateModelMixin
  )

  class UserConsumer(RetrieveModelMixin, UpdateModelMixin, GenericAsyncAPIConsumer):
      queryset = get_user_model().objects.all()
      serializer_class = UserSerializer


Consumers that are not bound to Models
--------------------------------------


You can also create consumers that are not at all related to any models.

.. code-block:: python

  from djangochannelsrestframework.decorators import action
  from djangochannelsrestframework.consumers import AsyncAPIConsumer

  class MyConsumer(AsyncAPIConsumer):

      @action()
      async def an_async_action(self, some=None, **kwargs):
          # do something async
	  return {'response with': 'some message'}, 200

      @action()
      def a_sync_action(self, pk=None, **kwargs):
          # do something sync
	  return {'response with': 'some message'}, 200

Using your normal views over a websocket connection
---------------------------------------------------

.. code-block:: python

  from djangochannelsrestframework.consumers import view_as_consumer

  application = ProtocolTypeRouter({
      "websocket": AuthMiddlewareStack(
          URLRouter([
	      url(r"^front(end)/$", view_as_consumer(YourDjangoView)),
	  ])
      ),
   })


Creating a fully-functional custom Consumer
-------------------------------------------

This package offers Django Rest Framework capabilities via mixins. To utilize these mixins, one must inherit from the ``GenericAsyncAPIConsumer``.

One may use the same exact querysets and ``serializer_classes`` utilized in their DRF Views, but must omit the DRF permissions. 

Permissions are to be imported from djangochannelsrestframework, which provides the standard ``AllowAny`` and ``IsAuthenticated`` permissions.


.. code-block:: python

    from . import models
    from . import serializers
    from djangochannelsrestframework import permissions
    from djangochannelsrestframework.generics import GenericAsyncAPIConsumer
    from djangochannelsrestframework.mixins import (
        ListModelMixin,
        PatchModelMixin,
        UpdateModelMixin,
        CreateModelMixin,
        DeleteModelMixin,
    )

    class LiveConsumer(ListModelMixin, GenericAsyncAPIConsumer):
        queryset = models.Test.objects.all()
        serializer_class = serializers.TestSerializer
        permission_classes = (permissions.IsAuthenticated,)


Because this class uses the ``ListModelMixin``, one has access to the ``list`` action.

One can access this action from the client with a payload, or from within a method:

Access action from Client ``payload: {action: "list", "request_id": 42}``

Note: Mixin - available action

``ListModelMixin`` - ``list``
``PatchModelMixin`` - ``patch``
``CreateModelMixin`` - ``create``
``RetrieveModelMixin`` - ``retrieve``
``UpdateModelMixin`` - ``update``
``DeleteModelMixin`` - ``delete``


Subscribing to all instances of a model
---------------------------------------

One can subscribe to all instances of a model by utilizing the ``model_observer``.

.. code-block:: python

    from djangochannelsrestframework.observer import model_observer

    @model_observer(models.Test)
    async def model_activity(self, message, observer=None, **kwargs):
        # send activity to your frontend
        await self.send_json(message)

This method will send messages to the client on all CRUD operations made through the Django ORM.

Note: These notifications do not include bulk updates, such as ``models.Test.objects.filter(name="abc").update(name="newname")``


Creating consumer operation
---------------------------

To create consumer operations, one can choose between using the traditional ``receive_json`` method utilized in typical consumers or djangochannelsrestframework actions. 

Actions are created by adding the ``action`` <decorator> to a method.

.. code-block:: python

    from djangochannelsrestframework.decorators import action

    # Subscribe to model via action
    @action()
    async def subscribe_to_model(self, **kwargs):
        await LiveConsumer.model_activity.subscribe(self)

    # Subscribe to model via receive_json
    async def receive_json(self, content):
        await super().receive_json(content)
        await LiveConsumer.model_activity.subscribe(self)

Both the action and ``receive_json`` make use of the ``model_activity`` method in the ``LiveConsumer`` class, referred to above, subscribing to all CRUD operations of the model specified in the ``@model_observer``.

Note: If utilizing ``receive_json``, one must ``super().receive_json(content)`` to avoid the disruption of other actions not declared in the ``receive_json``.


Initiating operation on consumer connect
----------------------------------------

One may initiate operations on consumer connects by overriding the ``websocket_connect`` method.

.. code-block:: python

    async def websocket_connect(self, message):

        # Super Save
        await super().websocket_connect(message)

        # Initialized operation
        await type(self).activities_change.subscribe(self)


This method utilizes the previously mentioned ``model_activity`` method to subscribe to all instances of the current Consumer's model. 

Note: Notice the use of ``type(self)``, rather than ``LiveConsumer``. This is a more dynamic approach, most likely used in a custom Consumer mixin, allowing one to subscribe to the current consumer rather than a specific one.



