From bd18deaf32e58b33bd40e86f804b615aa9e647b7 Mon Sep 17 00:00:00 2001 From: bashmak Date: Thu, 14 Dec 2017 15:49:14 +0300 Subject: [PATCH] Make up web notifications --- chatbot/admin.py | 1 + chatbot/locale/ru/LC_MESSAGES/django.po | 78 ++++++++++++++---- chatbot/migrations/0002_auto_20171214_1517.py | 59 +++++++++++++ chatbot/models.py | 52 +++++++++++- chatbot/telebot.py | 14 ++-- dialing_app/models.py | 1 + docs/dev.md | 69 ++++++++++++++++ msg_app/models.py | 2 + msg_app/urls.py | 3 +- msg_app/views.py | 17 ++++ static/img/noticon.png | Bin 0 -> 11942 bytes static/js/my.js | 49 +++++++++++ taskapp/handle.py | 4 +- taskapp/urls.py | 3 +- taskapp/views.py | 18 ++++ 15 files changed, 341 insertions(+), 29 deletions(-) create mode 100644 chatbot/migrations/0002_auto_20171214_1517.py create mode 100644 static/img/noticon.png 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 0000000000000000000000000000000000000000..fe70377165620fdea128a93555b5e9639da7d055 GIT binary patch literal 11942 zcmZ{K1ymc~)^BhqS{#ZK+=@$!Yj9d9P~0_8+*$}yTuLaeMT>hV#oeVigci5r?&Rfv zzqQ`F_q*@S%FLX}I%j_S?0wE`jndXs#(zfr3;+P&gH#mWAn%?3oYzid~EC2uv zpslW}hqPwPhCn_c{yE@qI1C1ZLZM(V7z6?Vfj~F_00RJ^000;O0096%!O!vHWS015_xK>!dC`KJFG_TTUb7z_@D!oXlC2m}TK z5l|=`42FR~P-GhnhJ!#bWCH|(Bd!(kv86gdwt0uF-1kn;l};K+IZGx|TlkevBbW{ga5A)ASet0hQdK$qzs?{ zAoxG0`_CGIB4A)R6a+&`4g>%r=lP$j0R;F@`5=h|z@Y#b7=Y}K0Knh?C=39GB9|=; z0f54hBmj^=|2if(QPBYa1^`GwR@ZmwFxv;BJGfBI zHRByfUXSxkc#H@9InTLPk;4|Uw=ezDsGKKD0>Q-300`MnqUXtmvI6j_g;4A#^KfKB zF*N|xLO|||Q%`=MhdXgl083QN9^Zr6a@!-o?_RGUnrQ+ILC^rv* zwiEXRsUxAl+4efq+BG(Z1qh8>|M92{`6Rgt+9<@NK&4v-V;DvB3n2~4Sp@zdPFCED zM24_VM`4c<{8S4eN@{%oU3J3B!)XW22$y1Yx!-k{qlgM`LdSZHGr5)4s^uziUQpzN zNTn9^1RG$}DP_)6gKlu-c^|K|p_)v>&bgQldN@zGwi2Ow(Lrd=PgQFaCrk_}dj?8j zLBVdBO=CqDMJ0h)e9Mq3n-s1Bk`}6dRKvgwhuuTuGW%}uU&^O!Re#og^e(Rz@F~OY zoC5S#*8{?c$l!*@@8!OieE<-u5EBAunZ6j8Z3jE37)MS9XrlF_ZRqJOGN4a@i0kE+ z6M=gd!!^arG1&kjY?N8!P~E6yQPdzb|FB_w);1;gN_*Cb4X zUEy+Q@MR|TC+k&ObKXgL=Js&vXvWbbole!&L<1uX_s6h+_ah9!KuBPx`zpa$;V*4M z#xzI=7MBi(@XaemVWI55FNs(=!DM4^2H%k|LkNQ}RL3$f@h?klT+x3K+)#Z`U{ib3 zi0M0up&fjOMkYQhUSaVqcTwXyuOevw^~K--Wg74kr40oGgnB1| z#4=_?uFP&=`NRrSQwAd$mCk(`<+ulGY7qMTCPlL3b8S*qjmMUmT~s2}3-_84qpBo2 zr4L>oJ^=O_`eoq_m1|8e2XfLL24g5X5K{$~Q=yk6vXxfBbdMwZS?uhJ)k!~t8wgaU zIN9*ymL79 zNI7OV#R`y*Bo{HbdR8Hge|P%Vhp5DwByTGJ0%T-pOD>JM8hg-Xw50^R(` zyBI`;suRLlk8lCYblB)=r^f?|>KHHGR8=fOxSm@(6n}^HMJe0g(02I4>_#>6s1Sa} z=xw>{;o9c~>Bv{UMPLWlVE5_)nlx|ANd-{=%eRyD9G&K455I`)DEBV!(r$Adn2J$l zB6F}4P57CWkW6q8<+$)WG9i@Aq!A>s??wwslxvI6VIs{cOwU^)S2lX}T)l$GgHKz( zvT>FuViGNzOMk9W{G{S$HF+f>KUdM{(9kLJt#yQ$YA9|VbVJxAV4QmW!)qC)G?$_| z4=qPgdKqjFMeVX}v?(GjV(gvtXd7I>AQ|y=v7bXE-;9Hc8jl7;++}01?%uH=f~tso zI>%iS9=s$a`RTAjPF}ZL%_0R?7bUtZzYJB6jJ+&6A9H}0Oi%ag{E&NJ3AY+qZXHlh zRfc;q#UmbH`ErS*oNrB54+BN9Zyz`BJ12uhQ-%u6NCi}vXx+44W8}8Ly?ZI(orz%x zJ-J2uv26PB2~f1fJ3RdR>sJX5UJsqW7pqryzlMnYvtx(8R};mshKGmRqRuW=;LM!_ zD)wz$l4$Ef56C5&MhcB~oR@5x^5x;&P$&)XmGeRcxwWCv+|b}`;a5T!IcG3L&LKUi zPJj*Lb03YEdq|J6R-Mxdzb5(D^{ZR{7nf;x{+<$4VOQ_|_UMeEOYGxgaU`GzLqeuE zl%c?Jhg8|zqwL-3a@=eQ>!g&7*goUyTUiN1CAJaD3x94~l1hQ;U4P90UdkCPMs%Ak zP0da6x`1Ow5%5Y6?6>kD7k7{Sqy<0K7aNWlfrbwhCe9jp2?{&I9QNU zFpmxG{xZ*F0JZ?0Qxa$dLTS8_Klh)Wt7Rh*iDm}9@wr#^wmsf+&VS@BN^x8IB7fy6 zH`u@6NI|pU*=$yQGg-yw6TrWFiT7e5TP-|mpjqb=3?F zQtVTIM|&?;QhM!SJpAxu!zHh@9h>`f?2H&@>f8M4U!9B8j`BNe&Va(Cu=JO-f>l)D)qQ~n= z)hTt}`+*1K%$RNMiemhhw9gVDje(Q2dsmp0;+w|(Ca<7XI}O$6x5L@I(Q3o&?A&V# zE>eECiZtRmS{80pyiHiC0}*98Pc&n!lJDxNB7UpF=NuZwhZzK!ljjT_fZ_**g}`q~ zPEwN&LX?z9*vGrglb)rq(Tt`@$p{Y+%?x_!WgH=G@GrLRbO3bMQFBA|`k~e4YtwSikk`B}<16a`RE>{l;+szMCJ{5{_<17K}c{&Y;%(l-aMY!TPd`u@g z&;4g_7S^ExbFNEWP_h>W9fttZT>A1w2om49K}Xzr?W!Pig?0sg|vhV_f{i7~keS*}h?oFVB7aww3T+Z5p0|##+3M@7Cx7zHF&n zm}NMSW^NS%A9}F-lXcEhFJ(3yiRpdNLZM3ULNXn@p5-$%(!$*+%aLz$5~9<(Z~3&QY+eKFL>ZRd{D#~szebYB!uGHVGF$000*bo6hbm!z%jAIr>MIZ)7<&Yv( z+k|FEBnNiOY}NkfBN$3dXz-^;NYraaU_y65*G7~J=-kI(dB*5AXmhY-uB z>!;W44^PT(Xvq(3N#wwPiXlX(ZJMl-L&G6O(sf?*vc-R1#iKmtm;7qoyVKeA65wlp zq$&laY?ngbo7Nk{JqE7HTLW^%@9&QUhtVM0SppY|1Zp2;B*NA?$tU;dDt5d7|akC$L;lF+u zyZpJ^eKnN?_M)4fRL^6DOdoDUnQ0XsH;d1v+&iwh=L8*YtjeV5tQ=_MWH2!=4##Sx zLd^`iXUt*;ESeM#bXO#f9M7Jq-kjaz7cPvA*Q{$_*dvFhU;4b{UCwf<3 zYZ#daqe&)ND^i6jfSNP;nX%k98dLR*yr1;dZU!=Qzx=JyePx-kz(iCiQNeB25xRqT zq_eRZkKBUNh!j6P*g_B9p`r*g=6*Kb|I17uT0O6f-1EzVd*O~7dmW|?(4ba+(cOKQ zwbH)NWl+ne8s|p>Q(rJEug|8}wuG1ci2QYY!RS^T;{KUOLy=$bL-cB@PXddiuz&Q8 z@C(@4K{-9tTF_yyS3Z*yor8c2DZyU2pfrAwXKAzr#g*OqCB<9aZA_43mua4uAZm{{ z;*q4f%=@d)@l(&kVbL}JerPGE!Qf_k?At}5`M?i7Tk4If7i6`|JffyU;XEzkAAl4| z{P87*u&?BR&W+3I>Hw^&7Jr2qCthBT4YTS%$n=(M$^5rx-)5hlzYn>v=0fB=Q8~@I zM%~aLzn~8vr@VY0R23(UnHm6NFcNNnmdsjCloiv@v+hQCOTCoUMpdZ6X!s9YYo`bD zdiNh5v;2fHIup{SnPasie$Nx=eg8Bsd(LV{kNp02a<;t!6&ca2LI0Z4((JOyaCaRi z_04AJOnCIkQrv++ZE}Lo-YwfCYPDoyr{u?sM*2vo_N-uz;>ikTTYS=O-^1~v*2z8E z^W43-c~d3ZXj+!M-j(y%MjTQ;?HO~4?#wEBMIo*2(K_J1^iq(mY3`}`r}43G;LHln9eTf0^X;JeEURWk%D;*IEF#5j z+(r_+(91sZ|~O&_Lpi`j#W}{i&kgg zt7f@W(MyJjR0=YuxHU_T-q@p%C$;ybOIalCbiJy!S#mvMDRCBjJ^ zbe;S3k@@tWkU-sUZw@@qTkc)A=iKJaYqKQZdQ~5p_@1q`sA#~}PLal@mz12~==R4u zo?r;^FCGco8Ew_9BkcvxX#}tV2gsxH=6I?455@F!KXJ>4!X`=>K2LCTy71T8&x(kEe^Z?M3;jWTg?Xc9_6@Gj8Z9>31@&(5(usWU zMt4L(v(R`6V+_$e6evlJiMSZBry4n3bNQyhqI7{AF;Mc5Ox+-tLP} za@W^fa~R5!8+~RkHf-C!&-87)b#ZukhDP)HBVIPQp5GPd9ZAePXP{_D%`pGFnDn*K zd+npEaO!w;k>HGHrn$L+k`fBEH^U=H+eI(a&A>=*&PvvzG{%82-flFRnfjnYsw0=S zh0v1!-56Qf^bn1N&fqqC_S(~3GlU)eO`z-&HKq@E0fycrSYHFHoBenq>U= zm8X#eNBgq-yWVML?&sB`ybVx=?Fgii6uA5o%SlmP&C=y_5pN)3 z6}{hBIKOKQqPH)?AGDUS>fFpep`F6w9;3S0c5H5~?>NCr#sitdvS(01_XybbbfNG< ziII;;a69ou@&6<@dYll+aO)a}bE~zzeLieNZ^54}$@``{X|e8;Pwyw%rKY$qMjXGE z^Wv%iI8F0f)mfUmo{0qnKF6;psmoIT-s(*`7`;vmdLQc;SUoY3c|2xwyp}|Xe^;Ld zLM<)yq`IxzO$;z5Qh;zki-h z&yh#o?c8RI?jibpEoWhL#p-z^Yy85yB4F^hdpJt%P1irD)>|ol*->nlJHsc|B z4Pz?Lyj==@TkT%^x5YmyyAO@=Rjeo08;p7$O@>QiX@$L()7WHIOFNik{F9C^v84PQ zGID4;5nPal*XY?5_4ZhpNITIw-Ay0g!o0(D*HZxQePlCxcjyccxX^uK_$79Y#}LJE zOaF5R(}mVYL%n^wjd1&PIKb>nwhJK-{QcNI{-bFM&^=~YJUvri5m3iHU1TMa%WE9^ z+cAXo1(jx=aJQ}=3-eg8@Ury%nEXF)QGCDZ>HOV+!qPh|JugjBs@ zZd)Jg?J*F6J8v%}N`E;vei%bcN{YTbE#?<$5N3t=NT&_px{j6TWwc!}mk|Hso}6P{ zRMznc3TAJMfVivf0Z$NBZ(GM1&@l^35ovO>V_QQh5JOM}7x;8@Vp5VQ9Se-ecAGcr ziV4by?sM+m#}yhLiQ~A%P|v-^;cFGno!g6-$d)fj5)TxRUT+sMq}ugzCz*S~0RnKa z**W)0L1`L^R&cFtvbmp_uNpsuCsX9yvB-~0S1+AC-d(Kj4IB?x)URH1@sk9jBk`?N z0=q8KJ^a=V8E*GCE1#nlm<~iOqdo+ekyyNe(vsC0WB{lk&&$f7n>ETT@-BfE?A}Ep z!a)F}(Yiv{ks!6>UH!|gEx>Ez7nZBMqJ*%)Ox`$eRKB}t({C~8sa17+)Zm}YOAdtp zz2H(I+w7tq2Oty)(Q4UhYL@(nSr80tDO$S35<;1gLy6{8abL(!VdwIg8@Aay=R%5m zLcJv+=XcA@U&nAfse}@`_815!Hak8Ws`%I6M@!E`e*{d$?Rw!$4}F{)FguyLm>3-Y zFay`(8zC;T(L{az9#GJQbRMb<-1SIgkkL22H_-g*up{r5L??CtBRXqA&d$>b;8Vc$ z_vuSxA!~*S^~_i^9US&ctvWK(hWl2!?3>#|t6C+T%PRWYj?L!z9jH$2q~asS{?!fB zHoib+tk^-yTT>Pdz~v<>&Zw=Gu3XD&DeKzM1+;VTW9?JEbXt6tY7}*4Oj{|vTBFo$Y94R z$?Lk^Jfb)*VSE8b1@Bxsf7z#gUBegTf5UqgZQ@lo&y|Eo=*XlI?ooUVr3cCBDfUL4 z`DYV^%Fb78hxav7s9@(W^_FSlpnuK4e}faOnW6&HbgbEb;k_nPyzDJ}+ej)~&l~E@1ok~v>NSqM1lRt_4Vp8&xkOe{)JLN=@t)OG zF%UGSFu6QLAIMk7tr$D@MlU%3dzVd#5Q>m`#K;b=!le==zQ3cb%QJ2zG$Wq_he>-y z!<0~_AD0JA*wB?1Z_k3-&H00ZF6HYi6`npgagGpUewt-KRn$Xz_hXJiV(l0VYCo72 z=)!Sq8q?(Hu=bpf@gin>DPrd9-A3Z0Gb(Boh_0@XrUk3*CgSLx-uJ4*!#??Mq~D>_ z_}=frg}VbsZOX6yL)qDFKf(*b-H6x#OL=^^6Xoaa)x2?8Z8hUqeQV%yS~+3JB79LVTT=Iomy^><3| zD>LEdPXS!@9R?_!p&e!%?FuX{)oE1?k@!~E!i+IeZHlsuYdw%V@u3&me-qo--nLFIH=w@ zpo@OJERqr>de3C)^EiY+(|RpU+cLYb+gO=jrgP8F=6;T&E5?!$fx7+&S6ACu)4{>8 z_mRqh!`iNRhJ&}H4%;+7SiEq04@r!Mk@v~P$gx#DWk&A|kxU6Ym4y5GGkcfF;|d~Z z*gXB}$uAO}RvFu$+0*UAsD!6XAqHO%OaoRw2F4UT%kE+8GLWu&O^UA3@8!(>xVMV-!UM&tX%p3x&7Zggrrwqx zcr-5`NPna$Ajh47j8zgDj48n$JsB)d$k6LaGV$*GXC>sY>)srWK76D)9?PKcX$Es9 zt=QXBpQ%B^rZ)=As;kms^{~1$H@7@?eJ#|t1sitBJFO?{16FjN#i%hIxz5AzO_uED zUeU*KqE}ASv|b!hvLu2~;cTZv%Krr1vx{(!BN* zT7TLEzCyd(T)nvY`*`x1r~#z4{@F(=0rzS0ovk*dngKhkCTyh$%J+TbN!;eg3v{9rMEn z(baPx?kR9YKBk#Q-f2Hs?{)p0fL4m!Eyz(-fH~r_WM=k*i2d6r29+m7UYTRo7y1X3 z&n~oeyB;o@y^k+M4zV>tzY3xB4x_DIVi>eosLcpa;L}E$&exQG)g_bN_+l2tjO4IF z>2%K)1wK%irFrHCvvb|F z`6qTRY9<}+o{(Um9_MKP-1+wtNk`n0 zbPccTYh57>7ERVaFGI4bvhx?7$`^yUrPpUvGyt`&=7z-$%RP3uDGEH(E&8G^EpF(a zi>lK4io+C7s+*)Y9>sHKZ+^7h6P^y{_H2sw+8pkkW9=?|BNH_Z@aui_yZt=+XIzwxC{m#X*SANs~+4N+rR&4D@JPfuy4YTn_Y6e~FsSoELV=FSGRHcv;@ zrjgl^ex6$vP=4>;+S98ba{dFV)$-3HV!o|4i}O!oZvbnlo7Y%rx4%-^RX0x}E*ePb zTCZ7+|H#B*;G_?|%L%gLb=5?CfwDUo$J0)1B}}vqUr8$07^=IeJ#`bakyN*Yt;u65 zan~bOpZ~e{Wpn_IbzHf^P>JMLYtnBW_Yx|MAFXEGDe(Rd@Ua@pn$`*yPh(+xNp{@1 zG&B>uthKxS*kwsTCpDZ{z$WH?fxg0L)`O6|WO{W)+h-v-z;WA!46oFy8M)90ne^@y zednbA_&}!F)MOYC$SJ>K%x8=p+~c_3F)jT~KcT9yq)35VH1I(9V96tLYv1RKb&N?R zOBjRXVt*nD#_P*&Jar{W*3cYC+4|MZ2LlZ^qjRhw9vP_bO)y9&ck3*9|8_HT*1B+k zI3GC&o=%R0gdE8TO@aLlzJiq6_oEMg+oKv>v*QQ$6CAc1PCWZ!Nnd!!JP$SPx134J zamLlKiq)D@k+CSvR4!4^^Qj&rF-3lvZ;mgTfs5)m@=vyW5c5i>t>0SY^$!u3IwLks z9VgtdMZkB`pwm2kPuM@z-0B*m>| z_sf?@E&D0odB?!7yw4Nnx4)!h`EF`aqJ*`{tyC@z2h?xtzex4nQ=~X)Qa}%#_@yrWy}TVpCH0BKROs4Y4x~q|noGYNV-Tg|HIK+D2!K+6MJJ@U@r24kBZC zSJMZAf}edOTQy2~rb(XSHoq{E=Dz~u)_EhB^2+34W97K+`UEt}ipC`SLR~!3MG0Hh zADV1umfcgAypN1v9(1F$o|KdpmWYLLw2>I2LxJDEb7CMJOi@3Y>lHz|w_h=^AD;l# z?ZTNXPO!ciuL>LnRz7NHTcj37h#!wz}AO`wgM@RRM?=`dSr`oIwYa_WNX+{h{UXqPxU2j82N7r2(yo?9l4t9Ea zxFJ1i^Fw|Rs@`&Gv7(;J=lb<+T@3AYjs!j8rxMW9G`8k{Z@qjneiGd=5_o%nqxBR< z1_9vtQ!(n&-Hw!3m^PIzoq1;_-ygMq4w7<{E*-kkCUx)_b^G97d3<%t;pU(AdX4kx zX|sBFuE=WUEHDT%zmnclF4)1u)oAlY+5&^B+nTkzyD^9V-#W6!c$QD86B>^i-X&ymM==!7^7i|ikpG&_N+L~tHgod zNiG#Bl*BJjT0Qn=3%I!6Rocz9es*36ga9*f)CTS7_!JU>QILFX<84;S#qmv}>dn8u zD3VMm}WVU^!*xfl&P{4e^|!j%Afh;Vd|Ul z%kT8%b1v)k)v2dF#lI;%1-#2~vT4+@rWRTgfjErlbk(_UQd7qlsh0hFY>WekJ~z0| z7i01in6E*R26QqZ()I?zd3c}?Jeq*DFI#c&>+{ZniSx+`*Y!HJh_FZ+T7s519p-TN zUtfZqf7fQRjLg^FM2sN|6uK-0%!R46n#n%fpWunF1x6B~R`bcE!($fhS0AKIemp?C z@aQJlET@)KkV(t!--hSu;z1mx1i0i$-};~(eK-v2OqqRQ#Mmb{^{)Kh(*!OpHh711 zM%kAJ^^3iEPi<5)9ik+z<5tM1qCum#;0xH{mWs>sI-mtIe@k^aSocv0>wOqPLAZtU z@iP%BBl;601in8&p^eP9?`BWuFoxc}Tx~V=JX+vgw>-`0R9J(L=zhMcp$P!`ExcKfg-@es42XtjC&aCh_&5o9T*P zZ1gHSV6l0Jp@op9;Kz(&`iPp04*v@;r1M$_#4ySfyjv~f)JFP6Wj7DcqKOv9lxRc* zvzHq4?V1QKM#UqL3U$yWn@rf7jXIw0)lilnZrbIoJ976rfMDb%`%S(my5w@vJt6Ro zt*D^(f%zDHH~856%RDnaPhsP~$|)>$0#k#-DjOlPkH|Rp*H_u39tdA$1P&;p;betouBafNiWj>jd0aZuZ zcP0`|)n_KiEa+wI?4gE|d>n>8$=8(Rdgm<(+p+t2WR5*?y9WBV#<6!EUAv8ln-z_` zK3K6_r2fN}E$@q^2qz`{60>7EVUf7;xpd+AsgI@jNbO?K?ud3y!EH5?Q-IufWQ_RK zx56k{Cg|Tfx`#|tE0%^sY+i^XmOUsx?U|3$FGL*78M+Be9Gu=Q)E_gb=~wN*i}uAl z^CL-Hnk}`}nA=$)5)GJLS}n7Yv?NF+Y;l>=)}VTy^$jgR!1IH&|K+Z#t^fNiZRVZ{9*Jp?m}Tm;aP3tTD4Kz&r@|MGmd+a|*;HO=-wUK=0o6t)?k` z)By1BGpj(udThHb1UD}441e0bz)f-K&71#KrN=3r4WBBN7uoT2R(t3_F_Uo zYVsHm{rhAcUE^G2iCf$p=U`pfm~*c!KT62D0NcnM*lvw5^Ym74lvPrsKG8ZN**YSQ zGy=cncL*}6?IMUq9G$PdeX)9e&%x{U=KXg5kA^IPREm;;i2UzqasIKGNYf)e)*9|$ zJ+D%Y!$2T0CQJO+x*5t4(xH!mOy02bdevzmRScP)Ha`IaC`&}9cp5ZV7rXc!8SBvf zsFt=vy(Wwz^g>pb$Qb%-=vy-mS<1wsJW!}^o*K#uv0XQDtil}@j<_p!p>9wr@Du9i=U)3xk()#uFuxEQ^BYSMlAQ+qY&6n7|Ca?6Lp3x@x&vSeP;Jbx9c0zx8LiR0DQ7Z7k@450A z=|*$?kbQLDQJj~|q5fElM7MO+t2;Rq;bfd;9LC75vwBLX`HpC}HXh5TEXB!b-lp zZ#a$!3X0J{2PYFS;?6UKf7k2Hu3VtT2Xpm(Tzh-&Gqp~oRldvG$9)z+kPMF5AnU5d zVVk|Z!t?;fgXL4e#H)Ry`*}lvoseQRDxcG_l05U;lSn#;Q+3OsMQM9u&?gLSs<**} z4L${o{k}8w+J8PzzlORo0wAw~7nfd55rq=)by{@_;y+^GvjA{TP)H#9`OC=qO0T^W z&Q9yq)UJ$Z|k@UpBqMiF)6b}N|7`0;ZJsQ3jgH&fg zr?$BpH`kN_jtuJDXN8v-VsgpWV$?zkz$gDz$5rnTkI5o~GsXBdjn9Wo`jDqi=FV|J z7nr-B1}RVI0h63fDPSZxs*^}F;W&YV&oP@)?U}3Hyp7oJcA%`(Nd-wMvZDvm16d|CiJkjXIJ9Q?#uH3UkM X=F-RY=E(4Ws%}AwnhI5N<{|$FUZqMR literal 0 HcmV?d00001 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))