{ "info": { "author": "Pawel Krzyzaniak", "author_email": "pawelk@arabel.la", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Internet :: WWW/HTTP" ], "description": "Chat API\n========\n\n--------------\n\nLibrary for surveys & chat with supporting automated & assisted conversations.\n\n\nChapters\n--------\n* `Known Issues and areas to improve`_\n* `Setup and running`_\n* `Configuration`_\n* `Schemas`_\n* `Chat modes`_\n* `UserThreads`_\n* `Notifications`_\n* `Surveys`_\n* `API description`_\n\n\n--------------\n\nKnown Issues and areas to improve\n---------------------------------\n\nNo context & variables for scripted/assisted questions\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nCurrently all questions are static, meaning there is no possibility of injecting any variables like user first_name, etc. when generating message from question. This functionality should be quite broad and extensible. For example for question with meta X or show_as_type Y there should be a possibility to generate context based on:\n\n- user\n- previous responses (variables pool?)\n- other objects in the DB\n\nTODO:\n\n- design\n- task out\n- implement\n\nAssisted mode has no test coverage\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAssisted mode should work (with exception of making other user enter scripted mode), but it is not covered with tests yet.\n\nTODO:\n\n- first implement the APIs for thread control\n- task out\n- implement\n\nRepetition is not implemented\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn order to use scripted chat mode as a tracker, repetition must be implemented. It was designed, and models contains necessary fields, but there is no test coverage and no scripts to restart schema.\n\nTODO:\n\n- task out\n- implement\n\nNotifications - add email, sms & push\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nCurrently only WebSocket notifications are sent out. It would be good to add some mechanisms to handle other kinds of notifications in a generic & highly customizable way.\n\nTODO:\n\n- design\n- task out\n- implement\n\n\nOther small issues and places for improvement\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- Schemas (so also scripted/assited chat mode & surveys) does not support multiple languages.\n- add pagination to schema list\n- custom validation, pre save, thumbnail generation etc. functions for attachments should receive also serializer object (so it has access to context & instance for example)\n- add push notifications\n- add email/sms notifications\n- add resolvers to meta (and other places perhaps) - so you can use values from constance\n\n\nSetup and running\n-----------------\n\n\nRequirements\n~~~~~~~~~~~~\n\n- Python 3.5+\n- Django 1.8+, DRF & bunch of Arrabela tech like DRF Tweaks & Universal Notifications\n- Celery\n\n\nInstallation\n~~~~~~~~~~~~\n\n\nAdd chat_api & universal_notifications to INSTALLED_APPS, define CELERY_APP_PATH:\n\n.. code:: python\n\n INSTALLED_APPS = (\n ...\n \"chat_api\",\n \"universal_notifications\"\n )\n CELERY_APP_PATH = \"tests.celery.app\",\n\nAlso, add settings related to MIME types & extensions:\n\n.. code:: python\n\n ACCEPTED_IMAGE_FILES = (\"gif\", \"png\", \"jpg\", \"jpeg\")\n ACCEPTED_IMAGE_MIME = (\"image/gif\", \"image/png\", \"image/jpg\", \"image/jpeg\")\n\n MIME_TO_EXT = {\n \"image/gif\": \"gif\",\n \"image/png\": \"png\",\n \"image/jpg\": \"jpg\",\n \"image/jpeg\": \"jpg\",\n \"application/pdf\": \"pdf\",\n \"application/x-pdf\": \"pdf\",\n \"application/vnd.pdf\": \"pdf\",\n \"text/pdf\": \"pdf\"\n }\n\nAdd urls to the components you want to use:\n\n.. code:: python\n\n urlpatterns = [\n ...\n url(r\"^\", include(\"chat_api.chat.api_urls\")),\n url(r\"^\", include(\"chat_api.schemas.api_urls\")),\n url(r\"^\", include(\"chat_api.surveys.api_urls\")),\n ]\n\nIn order to speed up schemas & automated flows, you need to setup django-cachalot.\n\n.. code:: python\n\n INSTALLED_APPS = (\n ...\n \"cachalot\"\n ...\n )\n\n CACHALOT_ONLY_CACHABLE_TABLES = [\n \"chat_api_answer\", \"chat_api_attachmenttemplate\", \"chat_api_group\", \"chat_api_group\", \"chat_api_question\",\n \"chat_api_schema\"\n ]\n\n\nREMI: please describe setting up search\n\n\nConfiguration\n-------------\n\nChat settings\n~~~~~~~~~~~~~\n\nChat settings are accesible through the chat_settings singleton\n\n.. code:: python\n\n from chat_api.settings import chat_settings\n\n account_serializer_cls = chat_settings.ACCOUNT_SERIALIZER\n\n\nChat settings can be configured in settings:\n\n.. code:: python\n\n CHAT_SETTINGS = {\n \"ACCOUNT_SERIALIZER\": \"my_project.accounts.serializers.MyAccountClass\"\n }\n\nChat settings can be overriden in tests using django's override_settings. However, the permissions classes are resolved earlier, so they will not be affected by this.\n\nAccount serializer\n~~~~~~~~~~~~~~~~~~\n\nDefault account serializer contains:\n\n- id\n- first_name\n- last_name\n- avatar\n\nHowever there are no assumptions regarding fields that should be enclosed in this serializer, so it is fully customizable.\n\n\nSchemas settings\n~~~~~~~~~~~~~~~~\n\nSchema types: each schema must have a type. Types can be freely defined in each project. Default types are \"survey\" and \"automated_flow\", but in the given project it is recommended to make them more descriptive, for instance: \"onboarding_flow\", \"cancel_subscription_flow\", \"health_survey\", etc.\n\n.. code:: python\n\n \"TYPES_SCHEMA\": ((\"schema_type\", \"Schema Type Label\"), (\"other_type\", \"Other Label\")),\n\n\nSchemas types that can be listed / obtained through API: Getting schemas / listing schemas through API can be limited to some selection (or compeletly). For example, we ant FE to be able to obtain full survey schema, but we don't want any automated flow to be obtainable.\n\n.. code:: python\n\n \"TYPES_SCHEMA_LIST_THROUGH_API\": (\"health_survey\", ),\n \"TYPES_SCHEMA_GET_THROUGH_API\": (\"health_survey\", ),\n\n\nAllowing published schemas to be edited: This funtionality should be user **ONLY** in developement environment. By default, schemas that are published are not editable. They can be copied to a new, unpublished version, edited, and once published - they'll make previous version obsolete. But if user X started schema Y in some version Z, he should be able to finish this schema version or else it will result in unpredicted behaviour. However, while developing, copying & publishing a new version each time something has to be adjusted would be too unconvenient.\n\n.. code:: python\n\n \"ALLOW_EDIT_PUBLISHED_SCHEMAS\": False,\n\n\nThreads types\n~~~~~~~~~~~~~\n\nType: each thread has it's own type. Types are for describing (and helping to define) certain distinct chat functionalities. For example: \"onboarding\", \"one_on_one_chat\", \"group_chat\". You may configure which types of chat will be listable through API, and which ones will be returned only through some other endpoints.\n\n.. code:: python\n\n \"TYPES_CHAT\": ((\"chat\", \"Chat\"), (\"survey\", \"Survey\"), (\"tracker\", \"Tracker\")),\n \"TYPES_CHAT_DEFAULT\": \"chat\",\n \"TYPES_CHAT_LIST_THROUGH_API\": (\"chat\", \"survey\", \"tracker\"),\n\n\nMessages types\n~~~~~~~~~~~~~~\n\nEach message in a given thread has a type. Default type is simply \"message\", but any type can be assigned. Some message types can be restricted for some types of users. To achieve two settings must be defined:\n\n.. code:: python\n\n # settings.py\n CHAT_SETTINGS = {\n ...\n \"CHAT_MESSAGE_FILTER_QUERYSET\": \"path.to.my_message_queryset_filter\",\n \"CHAT_MESSAGE_USER_FILTER\": \"path.to.my_message_user_filter\",\n }\n\n # path/to.py\n def my_message_queryset_filter(queryset, user):\n if not user.is_superuser:\n return queryset.exclude(type=\"secret\")\n\n return queryset\n\n def my_message_user_filter(message, user):\n if user.is_superuser:\n return True\n\n return message.type != \"secret\"\n\n\nThe queryset filter is used when listing messages, the single message filter is used when sending WS and assigning last_message to an UserThread.\n\n**IMPORTANT** Once last message is assigned, it stays assigned (and obtainable through ThreadSerializer as last_message_data) until a new one is assigned, so if you change filters and you want be 100% sure noone has a last message that he should not see after that change, a migration would be required.\n\n\nSchemas for surveys\n~~~~~~~~~~~~~~~~~~~\n\nYou can limit which schemas types can be assigned to a survey.\n\n.. code:: python\n\n \"SURVEYS_ALLOWED_SCHEMA_TYPES\": (\"survey\", ),\n\n\nPagination\n~~~~~~~~~~\n\nThreads, Messages List & Surveys can be paginated. By default the NoCountLimitOffsetPagination is used (since it is the fastets and most convenient to user for endless scroll), but those can be overriden:\n\n.. code:: python\n\n \"PAGINATION_THREAD_LIST\": \"any.pagination.class.YouLike\",\n \"PAGINATION_MESSAGES_LIST\": \"any.pagination.class.YouLike\",\n \"PAGINATION_GLOBAL_SURVEY_LIST\": \"any.pagination.class.YouLike\",\n \"PAGINATION_ACCOUNT_SURVEY_LIST\": \"any.pagination.class.YouLike\",\n\n\nPermissions\n~~~~~~~~~~~\n\nEach API endpoint has a unique permission class that can be overriden through settings. This allows full customization of chat. For example, by default access to reading/writing given thread have only the thread's members with correct permissions defined in UserThread. But if you want for example given user type to read all threads, overwriting permission classes is a way to go.\n\n.. code:: python\n\n \"PERMISSIONS_MESSAGES_LIST_BY_THREAD_ID\": \"any.permission.class.YouLike\",\n \"PERMISSIONS_MESSAGE_OBJECT_BY_THREAD_ID\": \"any.permission.class.YouLike\",\n\n\nAttachments settings\n~~~~~~~~~~~~~~~~~~~~\n\nThere are a predefined types of attachments with predefined behaviour:\n\n- image\n- youtube (url)\n- object_reference\n\nHowever this list can be extended by defining new types and behaviours:\n\n.. code:: python\n\n # settings.py\n CHAT_SETTINGS = {\n \"TYPES_CUSTOM_ATTACHMENTS\": ((\"pdf\", \"PDF File\"), ), # (\"type\", \"Label\")),\n \"CUSTOM_ATTACHMENTS_VALIDATION\": {\"pdf\": \"my.validation_func\"},\n \"CUSTOM_ATTACHMENTS_PRE_SAVE\": {\"pdf\": \"my.pre_save_func\"},\n \"CUSTOM_ATTACHMENTS_THUMBNAIL_GENERATOR\": {\"pdf\": \"my.thumbnail_generator_func\"},\n \"CUSTOM_ATTACHMENTS_GET_SRC\": {\"pdf\": \"my.get_src_func\"},\n \"CUSTOM_ATTACHMENTS_GET_THUMBNAIL\": {\"pdf\": \"my.get_thumbnail_func\"},\n }\n\n # my.py\n def validation_func(data):\n if \"src\" not in data:\n raise serializer.ValidationError(\"I want this field!\")\n return data\n\n def pre_save_func(validated_data):\n # save PDF from src to some location\n return validated_data\n\n def thumbnail_generator_func(validated_data):\n # generate thumbnail of the pdf\n return validate_data\n\n def get_src_func(obj):\n return obj.src + \"?some_magic_key=dsaokpdsa\"\n\n def get_thumbnail_func(obj):\n return obj.thumbnail[\"src\"]\n\n\nYou can also define a default attachment thumbnail size:\n\n.. code:: python\n\n \"ATTACHMENT_THUMBNAIL_SIZE\": (100, 100),\n\n\nSearch settings\n~~~~~~~~~~~~~~~\n\nREMI: please describe\n\nSchemas\n-------\n\nSchemas can be used both for chat (scripted or assisted mode) or for surveys.\n\nMore details about schemas configuration may be found here:\nhttps://docs.google.com/document/d/1d_beZNNWrHSGjMApa-9LoRpzpe1p3xFbDFmJpRbhEsY/edit\n\n\nChat modes\n----------\n\nChat modes are defined per each user in a given thread. It means that one user can be in scripted mode, other user can be in an assisted mode, and yet another user can be in closed mode. Mode (state) for a given user in a given thread is defined in UserThread.\n\nOpen\n~~~~\n\nIn this mode there is no schema assigned to an UserThread. Thread member in such state can write messages freely.\n\nClosed\n~~~~~~\n\nIn this mode there is no schema assigned to an UserThread. Thread member in such state can not write any messages.\n\nScripted\n~~~~~~~~\n\nIn this mode schema must be assigned to an UserThread. In this mode user can only send a message that is a correct answer to the current question (UserThread.question that generated UserThread.related_message), based on it's type, parameters and answers if applies.\n\nOnce user answers the last question (next_qid == -1) the schema is either:\n\n- repeated (UserThread.on_finish == UserThread.ON_FINISH_REPEAT)\n- opened (UserThread.on_finish == UserThread.ON_FINISH_OPEN)\n- closed (UserThread.on_finish == UserThread.ON_FINISH_CLOSE)\n\nScripted with FE Control\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis mode works almost the same as the Scripted state, with following exceptions:\n\n- Answer is send along with question_id. This question must belong to a given schema and the answer must be a valid answer for the question. Question may differ from UserThread.question - in this mode there is assumption, that FE knows better.\n- Messages from questions are not spawned in a moment that a schema is transitioning to another question, but along with message.\n\nAssisted\n~~~~~~~~\n\nIn this mode schema must be assigned to an UserThread. There is no current question in the UserThread, there is also no related message. Assisted mode is just passing a schema_id to FE, so the user may choose a message from it and send it to chat. Once implemented, it will also allow such user to make other user enter the scripted state.\n\nExample use case:\n\n.. code::\n\n [patient, open state] Hi, I'm not feeling well\n [doctor, assisted state] Hi, I'll need some more information\n [doctor, assisted state, chooses \"basic_assessment\" part of the schema]\n [automated message from doctor] Do you have elevated temperature?\n [patient, scripted state, chooses from answers] Yes\n\nBreaking scripted states\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf user A is in a scripted mode, and user B is in open mode, sending a message by user B will not affect user A. He will see it, but he'll not be able to react to it - since he is in a scripted mode and must provide answer to the current question from the schema. User B, if he has PERMISSION_THREAD_BREAK_STATE in his UserThread.permissions, can break user's A state by sending him message along \"break_state\": True. This will move user A to an open state.\n\n\nUserThreads\n-----------\n\nThere are only few thread properties that are shared by all thread members:\n\n- thread id\n- title\n- updated & created timestamps\n\nAll the other important properties are unique for each thread member:\n- state & all related properties\n- permissions & notifications\n- last message\n\nAlso, permissions for various APIs are determined based on UserThread. Therefore even if object contains a FK to a thread, if thread_data is going to be serialized it will be UserThread, not Thread serialized there. Chat is doing those replacement during the serialization, but if you add FK to a thread in some other object and you want to serialize it properly, you should copy solution from chat.\n\nDefault UserThread\n~~~~~~~~~~~~~~~~~~\n\nSince you can override permissions, giving access to threads to users that are not it's members, there is a necessity to define in such cases some default UserThread before serialization. Example:\n\n.. code:: python\n\n default_thread = UserThread(\n notifications=UserThread.NOTIFICATIONS_WS, state=UserThread.STATE_OPEN, updated=thread.updated,\n permissions=UserThread.PERMISSION_MESSAGES_READ | UserThread.PERMISSION_THREAD_READ, thread=thread,\n created=thread.created, last_message=thread.last_message\n )\n\n\nNotifications\n-------------\n\nEach user, in UserThread may be assigned a custom notification level. By default he will get unread states and websocket notifications. There is also flag for Push notifications, however it is not yet implemented. Email/SMS/other notifications should be added in the future.\n\n.. code:: python\n\n UserThread.objects.create(\n notifications=UserThread.NOTIFICATIONS_WS | UserThread.NOTIFIACTIONS_UNREAD, ...\n )\n\n\nUnread states\n~~~~~~~~~~~~~\n\nEach member of a given thread may have an unread state for each message if he has UserThread.NOTIFICATIONS_UNREAD flag on in his notifications flags in his UserThread. Enabling or disabling the flag is not affecting the previous messages - they either keep their unread state or lack of it.\n\nFor optimization reasons, the unread states are a separate objects, that are deleted when the user marks the message as read.\n\nMessages are marked as read in two cases:\n\n- when they are obtained by API (GET /api/threads/{thread_id}/messages) by given user\n- when the command \"mark_message_as_read\" is send through WS Multiplex\n\n.. code:: python\n\n ws_received.send(message_data={\n \"message\": \"mark_message_as_read\",\n \"data\": {\n \"message\": self.message1.id\n }\n }, channel_emails=[self.user2.email], sender=None)\n\n\nWebsockets\n~~~~~~~~~~\n\nThere are currently two websockets send out:\n\n- message_created_or_updated\n- thread_someone_is_writing (\"marching ants\")\n\n\nMessage created or updated\n//////////////////////////\n\nThis WS is send to any subscribed user for a given thread whenever message is created or updated. It contains also attachments & serialized user thread (so information about number of unread messages).\n\nThread someone is writing\n/////////////////////////\n\nWhis WS is send to any subscribed user for a given thread whenever any of the thread-member is sending the command through websocek multiplex.\n\n.. code:: python\n\n ws_received.send(message_data={\n \"message\": \"thread_someone_is_writing\",\n \"data\": {\n \"thread\": self.thread.id,\n \"length\": 1000 # in miliseconds\n }\n }, channel_emails=[self.user2.email], sender=None)\n\n\n\nWebsockets for non-members\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn case when user is not a thread member, but wishes to receive WS from a given thread (sample use cases: admin user checks what are happening in coversations; the whole group of user, eg. doctors have access to the same conversation, and it would be inefficient to add everyone of them to a thread) there is an api to subscribe to a given threads websockets: POST /api/threads/{thread_id}/ws-subscribe.\n\nOne user can be subscribed to only one thread he is not a member of. Subscribing to a new thread automaticaly replaces the old subscription. By default this API is blocked - it's permission class must be overriden in order to use it.\n\n\nUsing Search\n------------\n\nREMI: please describe\n\n\nSurveys\n-------\n\nObtaining schema & answers\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIn order to display questions, a given schema from survey object should be obtained through API (GET /api/schemas/{pk}).\n\nWhile listing answers (GET /api/surveys/{survey_id}/items), the questions for them are also returned (in related_question_data), however questions that were not answered are not returned with this method.\n\nState\n~~~~~\n\nEach survey contains a state objects, that is a dictionary conatining two things:\n\n- visibility: list of ids of questions that are currently visible\n- completed: True if all visible required fields are answered, otherwise False\n\nDetermining visibility\n~~~~~~~~~~~~~~~~~~~~~~\n\nVisibility is defined by following algorithm:\n\n- We assume the first question (with lowest position) is visible.\n- If a question is visible, the question that it is referring to (by next_qid) is also visible.\n- If there is an answer added to visible question, the question the answer is referring to (by next_qid) is also visible.\n\nChanging answers may affect visibility of a question (if the question looses all references to it) or even a whole group of questions. Visibility is determined after each time answer is saved and returned in state[\"visibility\"] in a Survey object.\n\nRequired fields\n~~~~~~~~~~~~~~~\n\n- Each question in schema may be marked as required.\n- A question is only really required if it is visible (see the section above).\n\nUpdating previous answers\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThere is only one answer allowed for a given question id. This means, that to update an answer you have to either put/patch the previous answer (PATCH /api/surveys/{survey_id}/items/{pk}) or first delete it (DELETE /api/surveys/{survey_id}/items/{pk}) and then add a new one.\n\nAPI description\n---------------\n\nAll API functions can be found in swagger after plugging the chat library to a project.\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://bitbucket.org/arabellatech/chat_api", "keywords": "", "license": "Proprietary", "maintainer": "", "maintainer_email": "", "name": "chat_api", "package_url": "https://pypi.org/project/chat_api/", "platform": "", "project_url": "https://pypi.org/project/chat_api/", "project_urls": { "Homepage": "https://bitbucket.org/arabellatech/chat_api" }, "release_url": "https://pypi.org/project/chat_api/0.8.8/", "requires_dist": null, "requires_python": "", "summary": "Automated Chat & Surveys API", "version": "0.8.8" }, "last_serial": 3212426, "releases": { "0.8.8": [ { "comment_text": "", "digests": { "md5": "fba1271d8a3587aca37b9cd0f622fc62", "sha256": "acc0a6132ae9821b12a6a6b5c98626f43114342de9c0a68949b308a79819e942" }, "downloads": -1, "filename": "chat_api-0.8.8.tar.gz", "has_sig": false, "md5_digest": "fba1271d8a3587aca37b9cd0f622fc62", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 58191, "upload_time": "2017-09-29T08:30:14", "url": "https://files.pythonhosted.org/packages/9a/20/d56fa3897e7ef527354b6cb70a0cbeac77fe5fbfeeb5cced2530be2ee0c3/chat_api-0.8.8.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "fba1271d8a3587aca37b9cd0f622fc62", "sha256": "acc0a6132ae9821b12a6a6b5c98626f43114342de9c0a68949b308a79819e942" }, "downloads": -1, "filename": "chat_api-0.8.8.tar.gz", "has_sig": false, "md5_digest": "fba1271d8a3587aca37b9cd0f622fc62", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 58191, "upload_time": "2017-09-29T08:30:14", "url": "https://files.pythonhosted.org/packages/9a/20/d56fa3897e7ef527354b6cb70a0cbeac77fe5fbfeeb5cced2530be2ee0c3/chat_api-0.8.8.tar.gz" } ] }