diff --git a/chatbot/admin.py b/chatbot/admin.py index 770758e..ef06580 100644 --- a/chatbot/admin.py +++ b/chatbot/admin.py @@ -3,3 +3,4 @@ from . import models admin.site.register(models.MessageHistory) admin.site.register(models.TelegramBot) +admin.site.register(models.MessageQueue) diff --git a/chatbot/locale/ru/LC_MESSAGES/django.po b/chatbot/locale/ru/LC_MESSAGES/django.po index 0543256..5b94f42 100644 --- a/chatbot/locale/ru/LC_MESSAGES/django.po +++ b/chatbot/locale/ru/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-03-24 23:49+0300\n" +"POT-Creation-Date: 2017-12-14 15:04+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Dmitry Novikov \n" "Language-Team: LANGUAGE \n" @@ -20,15 +20,59 @@ msgstr "" "%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" "%100>=11 && n%100<=14)? 2 : 3);\n" -#: chatbot/telebot.py:61 +#: models.py:13 +msgid "Employee" +msgstr "Сотрудник" + +#: models.py:14 +msgid "Telegram chat id" +msgstr "Номер чата из telegram" + +#: models.py:21 +msgid "Telegram bot" +msgstr "Telegram бот" + +#: models.py:22 +msgid "Telegram bots" +msgstr "Telegram боты" + +#: models.py:35 +msgid "Message history" +msgstr "История собщений" + +#: models.py:36 +msgid "Message histories" +msgstr "Истории собщений" + +#: models.py:53 +msgid "Target employee" +msgstr "Сотрудник" + +#: models.py:54 +msgid "Message" +msgstr "Сообщение" + +#: models.py:59 +msgid "Status of message" +msgstr "Статус сообщения" + +#: models.py:61 +msgid "App tag" +msgstr "Тэг приложения" + +#: models.py:70 models.py:71 +msgid "Message queue" +msgstr "Очередь оповещений" + +#: telebot.py:63 msgid "Let's get acquainted, what is your name? Write your login from billing." msgstr "Давай знакомиться, как тебя зовут? Напиши свой логин из биллинга." -#: chatbot/telebot.py:81 +#: telebot.py:84 msgid "I do not know the answer to this yet." msgstr "Я пока не знаю ответа на это" -#: chatbot/telebot.py:100 +#: telebot.py:104 msgid "" "You are not found in the database, check that it correctly pointed out its " "LOGIN. Try again" @@ -36,25 +80,29 @@ msgstr "" "Ты не найден в базе, проверь что правильно указал именно свой ЛОГИН. " "Попробуй ещё" -#: chatbot/telebot.py:123 +#: telebot.py:120 +msgid "Let's ping, write ip. It will be necessary to wait 10 seconds" +msgstr "Давай пинганём, напиши ip. Нужно будет подождать 10 сек" + +#: telebot.py:127 msgid "It's not like ip address, try again" msgstr "Это не похоже на ip адрес, попробуй ещё" -#: chatbot/telebot.py:126 +#: telebot.py:130 #, python-format msgid "You're '%s', right?" msgstr "Ты ведь %s ?" -#: chatbot/telebot.py:136 +#: telebot.py:138 +msgid "Telegram bot token not found" +msgstr "Токен для бота Telegram не найден" + +#: telebot.py:143 #, python-format msgid "Recipient '%s' does not subscribed on notifications" msgstr "%s не подписан на оповещения" -msgid "Let's ping, write ip. It will be necessary to wait 10 seconds" -msgstr "Давай пинганём, напиши ip. Нужно будет подождать 10 сек" - -msgid "Yes, it's nice to meet% s, I will notify you about events in billing. Successful work;)" -msgstr "Да, приятно познакомиться %s, я буду оповещать тебя о событиях в биллинге. Удачной работы ;)" - -msgid "Telegram bot token not found" -msgstr "Токен для бота Telegram не найден" +msgid "" +"Yes, it's nice to meet %(username)s, I will notify you about events in billing. Successful work ;)" +msgstr "" +"Да, приятно познакомиться %(username)s, я буду оповещать тебя о событиях в биллинге. Удачной работы ;)" diff --git a/chatbot/migrations/0002_auto_20171214_1517.py b/chatbot/migrations/0002_auto_20171214_1517.py new file mode 100644 index 0000000..4918f4c --- /dev/null +++ b/chatbot/migrations/0002_auto_20171214_1517.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2017-12-14 15:17 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('chatbot', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='MessageQueue', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('message', models.CharField(max_length=255, verbose_name='Message')), + ('status', models.CharField(choices=[('n', 'New'), ('r', 'Read')], default='n', max_length=1, verbose_name='Status of message')), + ('tag', models.CharField(default='none', max_length=6, verbose_name='App tag')), + ('target_employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Target employee')), + ], + options={ + 'verbose_name': 'Message queue', + 'verbose_name_plural': 'Message queue', + 'db_table': 'chat_message_queue', + }, + ), + migrations.AlterModelOptions( + name='messagehistory', + options={'verbose_name': 'Message history', 'verbose_name_plural': 'Message histories'}, + ), + migrations.AlterModelOptions( + name='telegrambot', + options={'verbose_name': 'Telegram bot', 'verbose_name_plural': 'Telegram bots'}, + ), + migrations.AlterField( + model_name='telegrambot', + name='chat_id', + field=models.PositiveIntegerField(default=0, verbose_name='Telegram chat id'), + ), + migrations.AlterField( + model_name='telegrambot', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Employee'), + ), + migrations.AlterModelTable( + name='messagehistory', + table='chat_message_history', + ), + migrations.AlterModelTable( + name='telegrambot', + table='chat_telegram_bot', + ), + ] diff --git a/chatbot/models.py b/chatbot/models.py index 27726ee..d704e15 100644 --- a/chatbot/models.py +++ b/chatbot/models.py @@ -1,3 +1,4 @@ +from django.utils.translation import ugettext as _ from django.db import models from django.conf import settings @@ -9,11 +10,16 @@ class ChatException(Exception): class TelegramBot(models.Model): - user = models.ForeignKey(AUTH_USER_MODEL) - chat_id = models.PositiveIntegerField(default=0) + user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_('Employee')) + chat_id = models.PositiveIntegerField(_('Telegram chat id'), default=0) def __str__(self): - return self.user.get_full_name() + ' - ' + str(self.chat_id) + return "%s - %d" % (self.user.get_full_name(), self.chat_id) + + class Meta: + db_table = 'chat_telegram_bot' + verbose_name = _('Telegram bot') + verbose_name_plural = _('Telegram bots') class MessageHistory(models.Model): @@ -23,3 +29,43 @@ class MessageHistory(models.Model): def __str__(self): return self.message + + class Meta: + db_table = 'chat_message_history' + verbose_name = _('Message history') + verbose_name_plural = _('Message histories') + + +class MessageQueueManager(models.Manager): + + def pop(self, user, tag='none'): + msgs = self.filter(target_employee=user, status='n', tag=tag)[:1].only('message').values('id', 'message') + if len(msgs) > 0: + self.filter(id=msgs[0]['id']).delete() + return msgs[0]['message'] + + def push(self, msg, user, tag='none'): + msg = self.create(target_employee=user, message=msg, tag=tag) + return msg + + +class MessageQueue(models.Model): + target_employee = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_('Target employee')) + message = models.CharField(_('Message'), max_length=255) + STATUSES = ( + ('n', 'New'), + ('r', 'Read') + ) + status = models.CharField(_('Status of message'), max_length=1, choices=STATUSES, default='n') + # tag каждое приложение ставит своим чтоб делить сообщения между этими приложениями + tag = models.CharField(_('App tag'), max_length=6, default='none') + + objects = MessageQueueManager() + + def __str__(self): + return self.message + + class Meta: + db_table = 'chat_message_queue' + verbose_name = _('Message queue') + verbose_name_plural = _('Message queue') diff --git a/chatbot/telebot.py b/chatbot/telebot.py index 5e9acb6..e41454a 100755 --- a/chatbot/telebot.py +++ b/chatbot/telebot.py @@ -5,7 +5,7 @@ import socket import collections from django.utils.translation import ugettext as _ from urllib3.exceptions import ProtocolError -from .models import TelegramBot, ChatException +from .models import TelegramBot, ChatException, MessageQueue from chatbot.models import MessageHistory from accounts_app.models import UserProfile from django.conf import settings @@ -19,7 +19,7 @@ class DjingTelebot(helper.ChatHandler): _chat_id = 0 def __init__(self, seed_tuple, **kwargs): - super().__init__(seed_tuple, **kwargs) + super(DjingTelebot, self).__init__(seed_tuple, **kwargs) self.cmds = { 'ping': self.ping, 'iam': self.say_me @@ -104,8 +104,8 @@ class DjingTelebot(helper.ChatHandler): self._question(_("You are not found in the database, check that it correctly pointed out its LOGIN. Try again"), self.question_name) return - self._sent_reply("Yes, it's nice to meet %s, I will notify you about events in billing. Successful work;)" - % profile.get_full_name()) + self._sent_reply(_("Yes, it's nice to meet %(username)s, I will notify you about events in billing. Successful work ;)") + % {'username': profile.get_full_name()}) # заканчивается время диалога # ex - время ожидания (timeout=ex в pave_event_space) @@ -130,9 +130,10 @@ class DjingTelebot(helper.ChatHandler): self._sent_reply(_("You're '%s', right?") % self._current_user.get_full_name()) -# Просто отправляем текст оповещения указанному админу -def send_notify(msg_text, account): +# Просто отправляем текст оповещения указанной учётке +def send_notify(msg_text, account, tag='none'): try: + MessageQueue.objects.push(msg=msg_text, user=account, tag=tag) if token is None: raise ChatException(_('Telegram bot token not found')) tb = TelegramBot.objects.get(user=account) @@ -142,4 +143,3 @@ def send_notify(msg_text, account): raise ChatException(_("Recipient '%s' does not subscribed on notifications") % account.get_full_name()) except ProtocolError as e: raise ChatException(e) - diff --git a/dialing_app/models.py b/dialing_app/models.py index 307cd8c..3f939b4 100644 --- a/dialing_app/models.py +++ b/dialing_app/models.py @@ -64,3 +64,4 @@ class AsteriskCDR(models.Model): class Meta: db_table = 'cdr' + managed = False diff --git a/docs/dev.md b/docs/dev.md index 37ffba4..bfb6f20 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -289,3 +289,72 @@ def add_user_range(self, user_list): def add_tariff_range(self, tariff_list): pass ``` + + +## Отправляем оповещения +Для того чтоб оправить важное сообщение работнику через все возможные настроенные системы(смс, телеграм, браузер) мы можем +воспользоваться одной процедурой из модуля **chatbot**. +```python +from chatbot.telebot import send_notify + +send_notify(msg_text='Text message',account=employee_profile, tag='apptag') +``` +Процедура **send_notify** принимает 3 параметра. 2 первых обязательны а последний не обязаелен. + + +*msg_text* - Текст сообщения + +*account* - Учётка работника которому отправляем сообщение + +*tag* - Тэг сообщения, это поле предназначено для фильтрации ваших сообщений в вашем приложении. Каждое приложение в пределах +своих вызовов использует один и тот жеж уникальный тэг. Для примера приложение личных сообщений видит сообщения только для себя +с помощью тега *msgapp*, и вы не спутаете ваши сообщения с сообщениями из модуля, например, задач который использует тэг *taskap*. + + +#### Получение оповещения +```python +from chatbot.models import MessageQueue + +msg = MessageQueue.objects.pop(user=employee_profile, tag='apptag') +``` + +Метод **pop** плучает первое сообщение и удаляет его. + + +#### Отображаем оповещение в браузере + +Чтоб отобразить **WebNotification** добавте в файле скриптов *static/js/my.js* строку вида: + +```javascript +$(document).notifys({news_url: '/url/to/your_view', check_interval: 60}); +``` +Тут *news_url* это путь к вашему представления которое возвращает новые оповещения, а *check_interval* - это интервал обращения к вашему представлению. + + +Представление для Web оповещений выглядит примерно так: +```python +@login_required +@only_admins +def check_news(request): + msg = MessageQueue.objects.pop(user=request.user, tag='apptag') + if msg is not None: + r = { + 'exist': True, + 'content': msg, + 'title': 'Message title' + } + else: + r = {'exist': False} + return HttpResponse(dumps(r)) +``` + +Убедитесь что вашему представлению не будет доступа от абонентов, об этом позаботится декоратор *only_admins* из *mydefs*. *mydefs* лежит в корне проекта. + +После получения сообщения надо вернуть словарь с параметрами: + +*exist* - Логическое значение, обозначает есть или нет информации в ответе. Если *exist* == True тогда возвращае ещё *content* и *title*. + + +*content* - Соответственно содержимое оповещения. + +*title* - Заголовок оповещения. diff --git a/msg_app/models.py b/msg_app/models.py index a1d0eaa..7d2142d 100644 --- a/msg_app/models.py +++ b/msg_app/models.py @@ -1,6 +1,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from accounts_app.models import UserProfile +from chatbot.telebot import send_notify class MessageError(Exception): @@ -166,6 +167,7 @@ class Conversation(models.Model): if participant == author: continue MessageStatus.objects.create(msg=msg, user=participant) + send_notify(msg_text=text,account=author, tag='msgapp') return msg def remove_message(self, msg): diff --git a/msg_app/urls.py b/msg_app/urls.py index ff34b05..d0e27a7 100644 --- a/msg_app/urls.py +++ b/msg_app/urls.py @@ -6,5 +6,6 @@ urlpatterns = [ url(r'^$', views.home, name='home'), url(r'^new$', views.new_conversation, name='new_conversation'), url(r'^(?P\d+)/$', views.to_conversation, name='to_conversation'), - url(r'^(?P\d+)/(?P\d+)/del$', views.remove_msg, name='remove_msg') + url(r'^(?P\d+)/(?P\d+)/del$', views.remove_msg, name='remove_msg'), + url(r'^check_news$', views.check_news, name='check_news') ] diff --git a/msg_app/views.py b/msg_app/views.py index 5e6d62c..2eafba9 100644 --- a/msg_app/views.py +++ b/msg_app/views.py @@ -1,9 +1,12 @@ +from json import dumps from django.contrib.auth.decorators import login_required from django.contrib.gis.shortcuts import render_to_text from django.core.exceptions import PermissionDenied +from django.http import HttpResponse from django.utils.translation import ugettext_lazy as _ from django.contrib import messages from django.shortcuts import render, redirect, get_object_or_404 +from chatbot.models import MessageQueue from mydefs import pag_mn from .models import Conversation, MessageError, Message @@ -70,3 +73,17 @@ def remove_msg(request, conv_id, msg_id): conversation_id = msg.conversation.pk msg.delete() return redirect('msg_app:to_conversation', conversation_id) + + +@login_required +def check_news(request): + msg = MessageQueue.objects.pop(user=request.user, tag='msgapp') + if msg is not None: + r = { + 'exist': True, + 'content': msg, + 'title': _('Task') + } + else: + r = {'exist': False} + return HttpResponse(dumps(r)) diff --git a/static/img/noticon.png b/static/img/noticon.png new file mode 100644 index 0000000..fe70377 Binary files /dev/null and b/static/img/noticon.png differ diff --git a/static/js/my.js b/static/js/my.js index 9591a70..197400f 100644 --- a/static/js/my.js +++ b/static/js/my.js @@ -173,6 +173,52 @@ $(document).ajaxError(function (ev, jqXHR, ajaxSettings, thrownError) { })(jQuery); +(function($){ + $.fn.notifys = function(opt){ + var settings = $.extend({ + news_url: null, + check_interval: 60 + }, opt); + + var notifShow = function(title, content){ + var perm = Notification.permission.toLowerCase(); + if(perm == "granted"){ + curnotify = new Notification(title, { + tag: 'djing-notify', + body: content, + icon: '/static/img/noticon.png'} + ); + }else if(perm == "default"){ + Notification.requestPermission(on_ask_perm); + } + } + + var on_ask_perm = function(r){ + notifShow("Thanks for letting notify you"); + } + + var check_news = function(){ + var perm = Notification.permission.toLowerCase(); + if(perm == "granted" && settings.news_url){ + $.getJSON(settings.news_url, function(r){ + if(r.exist){ + notifShow(r.title, r.content); + }/*else console.log('No news from '+settings.news_url);*/ + }); + } + } + + if(settings.news_url){ + // прверяем новости раз в минуту + var tiid = setInterval(check_news, settings.check_interval*1000); + + Notification.requestPermission(on_ask_perm); + } + } +})(jQuery); + + + $(document).ready(function () { // Live html5 image preview @@ -247,4 +293,7 @@ $(document).ready(function () { $('.btn_ajloader').ajloader({'dst_block': '#id_block_devices'}); + $(document).notifys({news_url: '/tasks/check_news', check_interval: 10}); + $(document).notifys({news_url: '/msg/check_news', check_interval: 55}); + }); diff --git a/taskapp/handle.py b/taskapp/handle.py index e31303f..671093d 100644 --- a/taskapp/handle.py +++ b/taskapp/handle.py @@ -40,11 +40,11 @@ def handle(task, author, recipients, abon_group): if task.state == 'F' or task.state == 'C': # Если задача завершена или провалена то отправляем одно оповещение автору try: - send_notify(fulltext, author) + send_notify(fulltext, author, tag='taskap') except ChatException as e: raise TaskException(e) else: - send_notify(fulltext, dst_account) + send_notify(fulltext, dst_account, tag='taskap') except ChatException as e: errors.append(e) if len(errors) > 0: diff --git a/taskapp/urls.py b/taskapp/urls.py index e921f3b..d3fef6f 100644 --- a/taskapp/urls.py +++ b/taskapp/urls.py @@ -16,5 +16,6 @@ urlpatterns = [ url(r'^finished$', views.finished_tasks, name='finished_tasks'), url(r'^own$', views.own_tasks, name='own_tasks'), url(r'^my$', views.my_tasks, name='my_tasks'), - url(r'^all$', views.all_tasks, name='all_tasks') + url(r'^all$', views.all_tasks, name='all_tasks'), + url(r'^check_news$', views.check_news, name='check_news') ] diff --git a/taskapp/views.py b/taskapp/views.py index 285f38f..a99aa53 100644 --- a/taskapp/views.py +++ b/taskapp/views.py @@ -1,12 +1,15 @@ # coding=utf-8 +from json import dumps from django.contrib.auth.decorators import login_required from django.core.exceptions import PermissionDenied +from django.http import HttpResponse from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages from abonapp.models import Abon from django.utils.translation import ugettext as _ from datetime import date from guardian.decorators import permission_required_or_403 as permission_required +from chatbot.models import MessageQueue from .handle import TaskException from .models import Task @@ -202,3 +205,18 @@ def remind(request, task_id): except TaskException as e: messages.error(request, e) return redirect('taskapp:home') + + +@login_required +@only_admins +def check_news(request): + msg = MessageQueue.objects.pop(user=request.user, tag='taskap') + if msg is not None: + r = { + 'exist': True, + 'content': msg, + 'title': _('Task') + } + else: + r = {'exist': False} + return HttpResponse(dumps(r))