diff --git a/abonapp/templates/abonapp/charts.html b/abonapp/templates/abonapp/charts.html deleted file mode 100644 index 2f55c5b..0000000 --- a/abonapp/templates/abonapp/charts.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %} -{% load i18n %} -{% block content %} - -
-
-
-
-

{% blocktrans with wantdate_d=wantdate|date:'j E Y' %}Graph of use by {{ wantdate_d }}{% endblocktrans %}

-
-
- {% if charts_data %} -
- - {% else %} -

{% trans 'Static info was Not found' %}

- {% endif %} -
- - - - -
- -
-
-
-
- -{% endblock %} diff --git a/abonapp/templates/abonapp/ext.htm b/abonapp/templates/abonapp/ext.htm index 51ac412..6d45196 100644 --- a/abonapp/templates/abonapp/ext.htm +++ b/abonapp/templates/abonapp/ext.htm @@ -39,11 +39,6 @@ {% trans 'History of tasks' %} - {% url 'abonapp:charts' group.pk abon.username as abtasklog %} - - {% trans 'Charts' %} - - {% url 'abonapp:dials' group.pk abon.username as abdials %} {% trans 'Dialing' %} diff --git a/abonapp/urls.py b/abonapp/urls.py index c329a84..16092a6 100644 --- a/abonapp/urls.py +++ b/abonapp/urls.py @@ -1,6 +1,6 @@ from django.urls import path, include, re_path -from . import views +from abonapp import views app_name = 'abonapp' @@ -13,7 +13,6 @@ subscriber_patterns = [ path('addinvoice/', views.add_invoice, name='add_invoice'), path('pick/', views.pick_tariff, name='pick_tariff'), path('passport_view/', views.PassportUpdateView.as_view(), name='passport_view'), - path('chart/', views.charts, name='charts'), path('dials/', views.DialsListView.as_view(), name='dials'), # path('reset_ip/', views.reset_ip, name='reset_ip'), path('unsubscribe_service//', views.unsubscribe_service, name='unsubscribe_service'), diff --git a/abonapp/views.py b/abonapp/views.py index d0b72cf..e731908 100644 --- a/abonapp/views.py +++ b/abonapp/views.py @@ -1,4 +1,4 @@ -from datetime import datetime, date +from datetime import datetime from typing import Dict, Optional from agent.commands.dhcp import dhcp_commit, dhcp_expiry, dhcp_release @@ -11,7 +11,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, \ PermissionRequiredMixin as PermissionRequiredMixin_django, PermissionRequiredMixin from django.core.exceptions import PermissionDenied, ValidationError from django.db import IntegrityError, ProgrammingError, transaction, \ - OperationalError, DatabaseError + DatabaseError from django.db.models import Count, Q from django.http import HttpResponse, HttpResponseBadRequest, \ HttpResponseRedirect @@ -33,7 +33,6 @@ from guardian.shortcuts import get_objects_for_user, assign_perm from gw_app.models import NASModel from gw_app.nas_managers import NasFailedResult, NasNetworkError from ip_pool.models import NetworkModel -from statistics.models import getModel from tariff_app.models import Tariff from taskapp.models import Task from xmlview.decorators import xml_view @@ -440,15 +439,17 @@ def pick_tariff(request, gid: int, uname): trf = Tariff.objects.get(pk=request.POST.get('tariff')) deadline = request.POST.get('deadline') log_comment = _( - "Service '%(service_name)s' has connected via admin") % { - 'service_name': trf.title - } - if deadline == '' or deadline is None: - abon.pick_tariff(trf, request.user, comment=log_comment) - else: + "Service '%(service_name)s' " + "has connected via admin until %(deadline)s") % { + 'service_name': trf.title, + 'deadline': deadline + } + if deadline: deadline = datetime.strptime(deadline, '%Y-%m-%d %H:%M:%S') abon.pick_tariff(trf, request.user, deadline=deadline, comment=log_comment) + else: + abon.pick_tariff(trf, request.user, comment=log_comment) r = abon.nas_sync_self() if r is None: messages.success(request, _('Tariff has been picked')) @@ -689,56 +690,56 @@ def clear_dev(request, gid: int, uname): return redirect('abonapp:abon_home', gid=gid, uname=uname) -@login_required -@only_admins -@permission_required('group_app.view_group', (Group, 'pk', 'gid')) -def charts(request, gid: int, uname): - high = 100 - - wandate = request.GET.get('wantdate') - if wandate: - wandate = datetime.strptime(wandate, '%d%m%Y').date() - else: - wandate = date.today() - - try: - StatElem = getModel(wandate) - abon = models.Abon.objects.get(username=uname) - if abon.group is None: - abon.group = Group.objects.get(pk=gid) - abon.save(update_fields=('group',)) - - charts_data = StatElem.objects.chart( - abon, - count_of_parts=30, - want_date=wandate - ) - - abontariff = abon.active_tariff() - if abontariff is not None: - trf = abontariff.tariff - high = trf.speedIn + trf.speedOut - if high > 100: - high = 100 - - except models.Abon.DoesNotExist: - messages.error(request, _('Abon does not exist')) - return redirect('abonapp:people_list', gid) - except Group.DoesNotExist: - messages.error(request, _("Group what you want doesn't exist")) - return redirect('abonapp:group_list') - except (ProgrammingError, OperationalError) as e: - messages.error(request, e) - return redirect('abonapp:charts', gid=gid, uname=uname) - - return render(request, 'abonapp/charts.html', { - 'group': abon.group, - 'abon': abon, - 'charts_data': ',\n'.join( - charts_data) if charts_data is not None else None, - 'high': high, - 'wantdate': wandate - }) +# @login_required +# @only_admins +# @permission_required('group_app.view_group', (Group, 'pk', 'gid')) +# def charts(request, gid: int, uname): +# high = 100 +# +# wandate = request.GET.get('wantdate') +# if wandate: +# wandate = datetime.strptime(wandate, '%d%m%Y').date() +# else: +# wandate = date.today() +# +# try: +# StatElem = getModel(wandate) +# abon = models.Abon.objects.get(username=uname) +# if abon.group is None: +# abon.group = Group.objects.get(pk=gid) +# abon.save(update_fields=('group',)) +# +# charts_data = StatElem.objects.chart( +# abon, +# count_of_parts=30, +# want_date=wandate +# ) +# +# abontariff = abon.active_tariff() +# if abontariff is not None: +# trf = abontariff.tariff +# high = trf.speedIn + trf.speedOut +# if high > 100: +# high = 100 +# +# except models.Abon.DoesNotExist: +# messages.error(request, _('Abon does not exist')) +# return redirect('abonapp:people_list', gid) +# except Group.DoesNotExist: +# messages.error(request, _("Group what you want doesn't exist")) +# return redirect('abonapp:group_list') +# except (ProgrammingError, OperationalError) as e: +# messages.error(request, e) +# return redirect('abonapp:charts', gid=gid, uname=uname) +# +# return render(request, 'abonapp/charts.html', { +# 'group': abon.group, +# 'abon': abon, +# 'charts_data': ',\n'.join( +# charts_data) if charts_data is not None else None, +# 'high': high, +# 'wantdate': wandate +# }) @login_required diff --git a/chatbot/email_bot.py b/chatbot/email_bot.py deleted file mode 100644 index ccdde95..0000000 --- a/chatbot/email_bot.py +++ /dev/null @@ -1,27 +0,0 @@ -from _socket import gaierror -from smtplib import SMTPException -from django.core.mail import EmailMultiAlternatives -from django.utils.html import strip_tags -from django.conf import settings - -from chatbot.models import ChatException - - -def send_notify(msg_text, account, tag='none'): - try: - # MessageQueue.objects.push(msg=msg_text, user=account, tag=tag) - target_email = account.email - text_content = strip_tags(msg_text) - - msg = EmailMultiAlternatives( - subject=getattr(settings, 'COMPANY_NAME', 'Djing notify'), - body=text_content, - from_email=getattr(settings, 'DEFAULT_FROM_EMAIL'), - to=(target_email,) - ) - msg.attach_alternative(msg_text, 'text/html') - msg.send() - except SMTPException as e: - raise ChatException('SMTPException: %s' % e) - except gaierror as e: - raise ChatException('Socket error: %s' % e) diff --git a/chatbot/send_func.py b/chatbot/send_func.py deleted file mode 100644 index 5571f23..0000000 --- a/chatbot/send_func.py +++ /dev/null @@ -1,5 +0,0 @@ -# send via email -from .email_bot import send_notify - -# for Telegram -# from chatbot.telebot import send_notify diff --git a/devapp/views.py b/devapp/views.py index 688769c..7781c75 100644 --- a/devapp/views.py +++ b/devapp/views.py @@ -4,7 +4,6 @@ from ipaddress import ip_address from abonapp.models import Abon from accounts_app.models import UserProfile from chatbot.models import ChatException -from chatbot.send_func import send_notify from devapp.base_intr import DeviceImplementationError from django.conf import settings from django.contrib import messages @@ -25,6 +24,7 @@ from djing.lib.decorators import only_admins, hash_auth_view from djing.lib.mixins import LoginAdminPermissionMixin, LoginAdminMixin from djing.lib.tln import ZteOltConsoleError, OnuZteRegisterError, \ ZteOltLoginFailed +from djing.tasks import multicast_email_notify from easysnmp import EasySNMPTimeoutError, EasySNMPError from group_app.models import Group from guardian.decorators import \ @@ -700,24 +700,18 @@ class OnDeviceMonitoringEvent(global_base_views.SecureApiView): recipients = UserProfile.objects.get_profiles_by_group( device_down.group.pk) - names = list() - - for recipient in recipients.iterator(): - send_notify( - msg_text=gettext(notify_text) % { - 'device_name': "%s(%s) %s" % ( - device_down.ip_address, - device_down.mac_addr, - device_down.comment - ) - }, - account=recipient, - tag='devmon' + + multicast_email_notify.delay(msg_text=gettext(notify_text) % { + 'device_name': "%s(%s) %s" % ( + device_down.ip_address, + device_down.mac_addr, + device_down.comment ) - names.append(recipient.username) + }, account_ids=( + recipient.pk for recipient in recipients.only('pk').iterator() + )) return { - 'text': 'notification successfully sent', - 'recipients': names + 'text': 'notification successfully sent' } except ChatException as e: return { diff --git a/djing/__init__.py b/djing/__init__.py index 90ca955..e2c9155 100644 --- a/djing/__init__.py +++ b/djing/__init__.py @@ -1,14 +1,14 @@ +import importlib import os import re -import importlib import typing as t from urllib.parse import unquote from django.http import HttpResponseRedirect, HttpResponse -from netaddr import mac_unix, mac_eui48 - from django.shortcuts import _get_queryset from django.utils.http import is_safe_url +from netaddr import mac_unix, mac_eui48 +from djing.celery import app MAC_ADDR_REGEX = '^([0-9A-Fa-f]{1,2}[:-]){5}([0-9A-Fa-f]{1,2})$' diff --git a/djing/celery.py b/djing/celery.py new file mode 100644 index 0000000..aae6b6c --- /dev/null +++ b/djing/celery.py @@ -0,0 +1,9 @@ +import os +from celery import Celery + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings") +app = Celery('djing', broker='redis://localhost:6379/0') +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks() diff --git a/djing/settings.py b/djing/settings.py index bd79b72..12d6a85 100644 --- a/djing/settings.py +++ b/djing/settings.py @@ -228,7 +228,9 @@ EMAIL_USE_TLS = getattr(local_settings, 'EMAIL_USE_TLS', True) SERVER_EMAIL = getattr(local_settings, 'SERVER_EMAIL', EMAIL_HOST_USER) -# Inactive ip lease time in seconds. -# If lease time more than time of create, and lease is inactive -# then delete it. Used in ip_pool app. -LEASE_LIVE_TIME = 86400 +# REDIS related settings +REDIS_HOST = 'localhost' +REDIS_PORT = '6379' +BROKER_URL = 'redis://' + REDIS_HOST + ':' + REDIS_PORT + '/0' +BROKER_TRANSPORT_OPTIONS = {'visibility_timeout': 3600} +CELERY_RESULT_BACKEND = 'redis://' + REDIS_HOST + ':' + REDIS_PORT + '/0' diff --git a/djing/tasks.py b/djing/tasks.py new file mode 100644 index 0000000..6f5a9e3 --- /dev/null +++ b/djing/tasks.py @@ -0,0 +1,56 @@ +import logging +from _socket import gaierror +from smtplib import SMTPException +from typing import Iterable + +from accounts_app.models import UserProfile +from django.conf import settings +from django.core.mail import EmailMultiAlternatives +from django.utils.html import strip_tags +from celery import shared_task + + +@shared_task +def send_email_notify(msg_text: str, account_id: int): + try: + account = UserProfile.objects.get(pk=account_id) + target_email = account.email + text_content = strip_tags(msg_text) + + msg = EmailMultiAlternatives( + subject=getattr(settings, 'COMPANY_NAME', 'Djing notify'), + body=text_content, + from_email=getattr(settings, 'DEFAULT_FROM_EMAIL'), + to=(target_email,) + ) + msg.attach_alternative(msg_text, 'text/html') + msg.send() + except SMTPException as e: + logging.error('SMTPException: %s' % e) + except gaierror as e: + logging.error('Socket error: %s' % e) + except UserProfile.DoesNotExist: + logging.error('UserProfile with pk=%d not found' % account_id) + + +@shared_task +def multicast_email_notify(msg_text: str, account_ids: Iterable): + text_content = strip_tags(msg_text) + for acc_id in account_ids: + try: + account = UserProfile.objects.get(pk=acc_id) + target_email = account.email + msg = EmailMultiAlternatives( + subject=getattr(settings, 'COMPANY_NAME', 'Djing notify'), + body=text_content, + from_email=getattr(settings, 'DEFAULT_FROM_EMAIL'), + to=(target_email,) + ) + msg.attach_alternative(msg_text, 'text/html') + msg.send() + except SMTPException as e: + logging.error('SMTPException: %s' % e) + except gaierror as e: + logging.error('Socket error: %s' % e) + except UserProfile.DoesNotExist: + logging.error('UserProfile with pk=%d not found' % acc_id) diff --git a/djing/urls.py b/djing/urls.py index d24bd1a..ad7e9da 100644 --- a/djing/urls.py +++ b/djing/urls.py @@ -11,7 +11,7 @@ urlpatterns = [ path('search/', include('searchapp.urls', namespace='searchapp')), path('dev/', include('devapp.urls', namespace='devapp')), path('map/', include('mapapp.urls', namespace='mapapp')), - path('statistic/', include('statistics.urls', namespace='statistics')), + # path('statistic/', include('statistics.urls', namespace='statistics')), path('tasks/', include('taskapp.urls', namespace='taskapp')), path('client/', include('clientsideapp.urls', namespace='client_side')), path('msg/', include('msg_app.urls', namespace='msg_app')), diff --git a/ip_pool/models.py b/ip_pool/models.py index daf15bd..a8e267d 100644 --- a/ip_pool/models.py +++ b/ip_pool/models.py @@ -176,13 +176,6 @@ class IpLeaseManager(models.Manager): except IntegrityError as e: raise DuplicateEntry(e) - def expired(self): - lease_live_time = getattr(settings, 'LEASE_LIVE_TIME') - if lease_live_time is None: - raise ImproperlyConfigured('You must specify LEASE_LIVE_TIME in settings') - senility = now() - timedelta(seconds=lease_live_time) - return self.filter(lease_time__lt=senility) - # Deprecated. Remove after migrations squashed class IpLeaseModel(models.Model): diff --git a/msg_app/models.py b/msg_app/models.py index 7c06d46..c8f12e9 100644 --- a/msg_app/models.py +++ b/msg_app/models.py @@ -1,7 +1,7 @@ from django.db import models from django.utils.translation import gettext_lazy as _ from accounts_app.models import UserProfile -from chatbot.send_func import send_notify +from djing.tasks import send_email_notify from chatbot.models import ChatException @@ -10,17 +10,29 @@ class MessageError(Exception): class MessageStatus(models.Model): - msg = models.ForeignKey('Message', on_delete=models.CASCADE, related_name='msg_statuses') - user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='usr_msg_status') + msg = models.ForeignKey( + 'Message', on_delete=models.CASCADE, + related_name='msg_statuses' + ) + user = models.ForeignKey( + UserProfile, on_delete=models.CASCADE, + related_name='usr_msg_status' + ) MESSAGE_STATES = ( ('new', _('New')), ('old', _('Seen')), ('del', _('Deleted')) ) - state = models.CharField(max_length=3, choices=MESSAGE_STATES, default='new') + state = models.CharField( + max_length=3, choices=MESSAGE_STATES, + default='new' + ) def __str__(self): - return "%s for %s (%s)" % (self.get_state_display(), self.user, self.msg) + return "%s for %s (%s)" % ( + self.get_state_display(), + self.user, self.msg + ) class Meta: db_table = 'message_status' @@ -34,10 +46,22 @@ class MessageStatus(models.Model): class Message(models.Model): text = models.TextField(_("Body")) sent_at = models.DateTimeField(_("sent at"), auto_now_add=True) - author = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='messages') - conversation = models.ForeignKey('Conversation', on_delete=models.CASCADE, verbose_name=_('Conversation')) - attachment = models.FileField(upload_to='messages_attachments/%Y_%m_%d', blank=True, null=True) - account_status = models.ManyToManyField(UserProfile, through=MessageStatus, through_fields=('msg', 'user')) + author = models.ForeignKey( + UserProfile, on_delete=models.CASCADE, + related_name='messages' + ) + conversation = models.ForeignKey( + 'Conversation', on_delete=models.CASCADE, + verbose_name=_('Conversation') + ) + attachment = models.FileField( + upload_to='messages_attachments/%Y_%m_%d', + blank=True, null=True + ) + account_status = models.ManyToManyField( + UserProfile, through=MessageStatus, + through_fields=('msg', 'user') + ) def __str__(self): return self.text[:9] @@ -70,7 +94,10 @@ class Message(models.Model): class ConversationMembership(models.Model): - account = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name='memberships') + account = models.ForeignKey( + UserProfile, on_delete=models.CASCADE, + related_name='memberships' + ) conversation = models.ForeignKey('Conversation', on_delete=models.CASCADE) PARTICIPANT_STATUS = ( ('adm', _('Admin')), @@ -78,9 +105,14 @@ class ConversationMembership(models.Model): ('ban', _('Banned user')), ('inv', _('Inviter')) ) - status = models.CharField(max_length=3, choices=PARTICIPANT_STATUS, default='gst') - who_invite_that_user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, null=True, blank=True, - related_name='self_conversations') + status = models.CharField( + max_length=3, choices=PARTICIPANT_STATUS, default='gst' + ) + who_invite_that_user = models.ForeignKey( + UserProfile, on_delete=models.CASCADE, + null=True, blank=True, + related_name='self_conversations' + ) def __str__(self): return "%s < %s" % (self.conversation, self.account) @@ -102,7 +134,9 @@ def id_to_userprofile(acc): class ConversationManager(models.Manager): def create_conversation(self, author, other_participants, title=None): - other_participants = tuple(id_to_userprofile(acc) for acc in other_participants) + other_participants = tuple( + id_to_userprofile(acc) for acc in other_participants + ) if not title: usernames = tuple(acc.username for acc in other_participants) if not usernames: @@ -112,7 +146,8 @@ class ConversationManager(models.Manager): conversation = self.create(title=title, author=author) for acc in other_participants: ConversationMembership.objects.create( - account=acc, conversation=conversation, status='adm', who_invite_that_user=author + account=acc, conversation=conversation, + status='adm', who_invite_that_user=author ) ConversationMembership.objects.create( @@ -123,12 +158,16 @@ class ConversationManager(models.Manager): @staticmethod def get_new_messages_count(account): if isinstance(account, UserProfile): - return MessageStatus.objects.filter(user=account, state='new').count() + return MessageStatus.objects.filter( + user=account, state='new' + ).count() else: return 0 def fetch(self, account): - conversations = self.filter(models.Q(author=account) | models.Q(participants__in=(account,))).annotate( + conversations = self.filter( + models.Q(author=account) | models.Q(participants__in=(account,)) + ).annotate( msg_count=models.Count('message', distinct=True) ) return conversations @@ -136,9 +175,11 @@ class ConversationManager(models.Manager): class Conversation(models.Model): title = models.CharField(max_length=32) - participants = models.ManyToManyField(UserProfile, related_name='conversations', - through='ConversationMembership', - through_fields=('conversation', 'account')) + participants = models.ManyToManyField( + UserProfile, related_name='conversations', + through='ConversationMembership', + through_fields=('conversation', 'account') + ) author = models.ForeignKey(UserProfile, on_delete=models.CASCADE) date_create = models.DateTimeField(auto_now_add=True) @@ -152,7 +193,9 @@ class Conversation(models.Model): def get_messages_new_count(self, account): msgs = Message.objects.filter(conversation=self) - return MessageStatus.objects.filter(user=account, msg__in=msgs, state='new').count() + return MessageStatus.objects.filter( + user=account, msg__in=msgs, state='new' + ).count() def last_message(self): messages = Message.objects.filter(conversation=self) @@ -162,14 +205,18 @@ class Conversation(models.Model): def new_message(self, text, attachment, author, with_status=True): try: msg = Message.objects.create( - text=text, conversation=self, attachment=attachment, author=author + text=text, conversation=self, + attachment=attachment, author=author ) if with_status: for participant in self.participants.all(): if participant == author: continue MessageStatus.objects.create(msg=msg, user=participant) - send_notify(msg_text=text, account=participant, tag='msgapp') + send_email_notify.delay( + msg_text=text, + account_id=participant.pk + ) return msg except ChatException as e: raise MessageError(e) @@ -188,10 +235,13 @@ class Conversation(models.Model): def _make_participant_status(self, user, status, cm=None): if cm is None: - cm = ConversationMembership.objects.get(account=user, conversation=self) + cm = ConversationMembership.objects.get( + account=user, conversation=self + ) else: if not isinstance(cm, ConversationMembership): - raise TypeError('cm must be instance of msg_app.ConversationMembership') + raise TypeError('cm must be instance of ' + 'msg_app.ConversationMembership') cm.status = status cm.save(update_fields=('status',)) return cm @@ -210,21 +260,28 @@ class Conversation(models.Model): def remove_participant(self, user): try: - cm = ConversationMembership.objects.get(account=user, conversation=self) + cm = ConversationMembership.objects.get( + account=user, conversation=self + ) cm.delete() except ConversationMembership.DoesNotExist: pass def add_participant(self, author, user): return ConversationMembership.objects.create( - account=user, conversation=self, status='gst', who_invite_that_user=author + account=user, conversation=self, + status='gst', who_invite_that_user=author ) def find_messages_by_text(self, text): - return Message.objects.filter(text__icontains=text, conversation=self) + return Message.objects.filter( + text__icontains=text, conversation=self + ) def _make_messages_status(self, account, status): - qs = MessageStatus.objects.filter(msg__conversation=self, user=account).exclude(state='del') + qs = MessageStatus.objects.filter( + msg__conversation=self, user=account + ).exclude(state='del') if status != 'del': qs = qs.exclude(state=status) return qs.update(state=status) diff --git a/requirements.txt b/requirements.txt index 8c24d4a..c0fa958 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,7 @@ asterisk # django-xmlview for pay system allpay -e git://github.com/nerosketch/django-xmlview.git#egg=django-xmlview + +Celery +redis==2.10.6 +celery[redis] diff --git a/statistics/__init__.py b/statistics/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/statistics/admin.py b/statistics/admin.py deleted file mode 100644 index e69de29..0000000 diff --git a/statistics/apps.py b/statistics/apps.py deleted file mode 100644 index 6770560..0000000 --- a/statistics/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class StatisticsConfig(AppConfig): - name = 'statistics' diff --git a/statistics/fields.py b/statistics/fields.py deleted file mode 100644 index 62f61bf..0000000 --- a/statistics/fields.py +++ /dev/null @@ -1,53 +0,0 @@ -# -# Get from https://github.com/Niklas9/django-unixdatetimefield -# -import datetime -import time - -import django.db.models as models - - -class UnixDateTimeField(models.DateTimeField): - # TODO(niklas9): - # * should we take care of transforming between time zones in any way here ? - # * get default datetime format from settings ? - DEFAULT_DATETIME_FMT = '%Y-%m-%d %H:%M:%S' - TZ_CONST = '+' - # TODO(niklas9): - # * metaclass below just for Django < 1.9, fix a if stmt for it? - # __metaclass__ = models.SubfieldBase - description = "Unix timestamp integer to datetime object" - - def get_internal_type(self): - return 'PositiveIntegerField' - - def to_python(self, val): - if val is None or isinstance(val, datetime.datetime): - return val - if isinstance(val, datetime.date): - return datetime.datetime(val.year, val.month, val.day) - elif self._is_string(val): - # TODO(niklas9): - # * not addressing time zone support as todo above for now - if self.TZ_CONST in val: - val = val.split(self.TZ_CONST)[0] - return datetime.datetime.strptime(val, self.DEFAULT_DATETIME_FMT) - else: - return datetime.datetime.fromtimestamp(float(val)) - - @staticmethod - def _is_string(val): - return isinstance(val, str) - - def get_db_prep_value(self, val, *args, **kwargs): - if val is None: - if self.default == models.fields.NOT_PROVIDED: return None - return self.default - return int(time.mktime(val.timetuple())) - - def value_to_string(self, obj): - val = self._get_val_from_obj(obj) - return self.to_python(val).strftime(self.DEFAULT_DATETIME_FMT) - - def from_db_value(self, val, expression, connection): - return self.to_python(val) diff --git a/statistics/migrations/0001_initial.py b/statistics/migrations/0001_initial.py deleted file mode 100644 index 7131ef2..0000000 --- a/statistics/migrations/0001_initial.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-02-26 00:20 -from __future__ import unicode_literals - -from django.db import migrations, models -from djing.fields import MyGenericIPAddressField -import statistics.fields - - -class Migration(migrations.Migration): - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='StatCache', - fields=[ - ('last_time', statistics.fields.UnixDateTimeField()), - ('ip', MyGenericIPAddressField(max_length=8, primary_key=True, protocol='ipv4', serialize=False)), - ('octets', models.PositiveIntegerField(default=0)), - ('packets', models.PositiveIntegerField(default=0)), - ], - options={ - 'db_table': 'flowcache', - }, - ), - ] diff --git a/statistics/migrations/0002_auto_20180808_1236.py b/statistics/migrations/0002_auto_20180808_1236.py deleted file mode 100644 index be359df..0000000 --- a/statistics/migrations/0002_auto_20180808_1236.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11 on 2018-08-08 12:36 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('statistics', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='statcache', - options={'ordering': ('-last_time',)}, - ), - ] diff --git a/statistics/migrations/0003_auto_20180814_1921.py b/statistics/migrations/0003_auto_20180814_1921.py deleted file mode 100644 index 9e6c5be..0000000 --- a/statistics/migrations/0003_auto_20180814_1921.py +++ /dev/null @@ -1,79 +0,0 @@ -# Generated by Django 2.1 on 2018-09-22 14:30 -from django.core.exceptions import ImproperlyConfigured -from django.db import migrations, connection, models -from statistics.fields import UnixDateTimeField - - -# def psql_migr(apps, _): -# pass - - -class Migration(migrations.Migration): - - dependencies = [ - ('abonapp', '0005_current_tariff'), - ('statistics', '0002_auto_20180808_1236'), - ] - - operations = [ - migrations.AlterModelOptions( - name='statcache', - options={'ordering': ('-last_time',)}, - ), - ] - - -db_e = connection.settings_dict.get('ENGINE') -if db_e is None: - raise ImproperlyConfigured('Database ENGINE is not set') -# if 'postgresql' in db_e: -# # Postgres - Migration.operations.insert(0, migrations.RunPython(psql_migr)) -if 'mysql' in db_e: - Migration.operations.insert(0, migrations.RunSQL( - ( - "DROP TABLE `flowcache`;", - "CREATE TABLE `flowcache` ( " - " `last_time` INT(10) UNSIGNED NOT NULL, " - " `abon_id` INT(11) DEFAULT NULL UNIQUE, " - " `octets` INT(10) UNSIGNED NOT NULL, " - " `packets` INT(10) UNSIGNED NOT NULL, " - " KEY `flowcache_abon_id_91e1085d` (`abon_id`) " - ") ENGINE = MEMORY DEFAULT CHARSET = utf8;" - ), - state_operations=[ - migrations.DeleteModel(name='statcache'), - migrations.CreateModel( - name='statcache', - fields=[ - ('last_time', UnixDateTimeField()), - ('abon', models.OneToOneField('abonapp.Abon', on_delete=models.CASCADE, primary_key=True)), - ('octets', models.PositiveIntegerField(default=0)), - ('packets', models.PositiveIntegerField(default=0)) - ], - options={ - 'db_table': 'flowcache', - }, - ) - ] - )) -else: - Migration.operations.extend( - ( - migrations.DeleteModel(name='statcache'), - migrations.CreateModel( - name='statcache', - fields=[ - ('last_time', UnixDateTimeField()), - ('abon', models.OneToOneField('abonapp.Abon', on_delete=models.CASCADE, primary_key=True)), - ('octets', models.PositiveIntegerField(default=0)), - ('packets', models.PositiveIntegerField(default=0)) - ], - options={ - 'db_table': 'flowcache', - 'ordering': ('-last_time',), - #'db_tablespace': 'ram' - }, - ) - ) - ) diff --git a/statistics/migrations/__init__.py b/statistics/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/statistics/models.py b/statistics/models.py deleted file mode 100644 index 6812a0c..0000000 --- a/statistics/models.py +++ /dev/null @@ -1,132 +0,0 @@ -import math -from datetime import datetime, timedelta, date, time -from django.db import models, connection, ProgrammingError -from django.utils.timezone import now - -from djing.fields import MyGenericIPAddressField -from .fields import UnixDateTimeField - - -def get_dates(): - tables = connection.introspection.table_names() - tables = (t.replace('flowstat_', '') for t in tables if t.startswith('flowstat_')) - return tuple(datetime.strptime(t, '%d%m%Y').date() for t in tables) - - -class StatManager(models.Manager): - def chart(self, user, count_of_parts=12, want_date=date.today()): - def byte_to_mbit(x): - return ((x / 60) * 8) / 2 ** 20 - - def split_list(lst, chunk_count): - chunk_size = len(lst) // chunk_count - if chunk_size == 0: - chunk_size = 1 - return tuple(lst[i:i + chunk_size] for i in range(0, len(lst), chunk_size)) - - def avarage(elements): - return sum(elements) / len(elements) - - try: - charts_data = self.filter(abon=user) - charts_times = tuple(cd.cur_time.timestamp() * 1000 for cd in charts_data) - charts_octets = tuple(cd.octets for cd in charts_data) - if len(charts_octets) > 0 and len(charts_octets) == len(charts_times): - charts_octets = split_list(charts_octets, count_of_parts) - charts_octets = (byte_to_mbit(avarage(c)) for c in charts_octets) - - charts_times = split_list(charts_times, count_of_parts) - charts_times = tuple(avarage(t) for t in charts_times) - - charts_data = zip(charts_times, charts_octets) - charts_data = ["{x: new Date(%d), y: %.2f}" % (cd[0], cd[1]) for cd in charts_data] - midnight = datetime.combine(want_date, time.min) - charts_data.append("{x:new Date(%d),y:0}" % (int(charts_times[-1:][0]) + 1)) - charts_data.append("{x:new Date(%d),y:0}" % (int((midnight + timedelta(days=1)).timestamp()) * 1000)) - return charts_data - else: - return - except ProgrammingError as e: - if "Table 'djing_db_n.flowstat" in str(e): - return - - -class StatElem(models.Model): - cur_time = UnixDateTimeField(primary_key=True) - abon = models.ForeignKey('abonapp.Abon', on_delete=models.CASCADE, null=True, default=None, blank=True) - ip = MyGenericIPAddressField() - octets = models.PositiveIntegerField(default=0) - packets = models.PositiveIntegerField(default=0) - - objects = StatManager() - - # ReadOnly - def save(self, *args, **kwargs): - pass - - # ReadOnly - def delete(self, *args, **kwargs): - pass - - @property - def table_name(self): - return self._meta.db_table - - def delete_month(self): - cursor = connection.cursor() - table_name = self._meta.db_table - sql = "DROP TABLE %s;" % table_name - cursor.execute(sql) - - @staticmethod - def percentile(N, percent, key=lambda x: x): - """ - Find the percentile of a list of values. - - @parameter N - is a list of values. Note N MUST BE already sorted. - @parameter percent - a float value from 0.0 to 1.0. - @parameter key - optional key function to compute value from each element of N. - - @return - the percentile of the values - """ - if not N: - return None - k = (len(N) - 1) * percent - f = math.floor(k) - c = math.ceil(k) - if f == c: - return key(N[int(k)]) - d0 = key(N[int(f)]) * (c - k) - d1 = key(N[int(c)]) * (k - f) - return d0 + d1 - - class Meta: - abstract = True - - -def getModel(want_date=now()): - class DynamicStatElem(StatElem): - class Meta: - db_table = 'flowstat_%s' % want_date.strftime("%d%m%Y") - abstract = False - - return DynamicStatElem - - -class StatCache(models.Model): - last_time = UnixDateTimeField() - # ip = MyGenericIPAddressField(primary_key=True) - abon = models.OneToOneField('abonapp.Abon', on_delete=models.CASCADE, primary_key=True) - octets = models.PositiveIntegerField(default=0) - packets = models.PositiveIntegerField(default=0) - - def is_online(self): - return self.last_time > now() - timedelta(minutes=55) - - def is_today(self): - return date.today() == self.last_time.date() - - class Meta: - db_table = 'flowcache' - ordering = ('-last_time',) - # db_tablespace = 'ram' diff --git a/statistics/templates/statistics/index.html b/statistics/templates/statistics/index.html deleted file mode 100644 index 0911ad6..0000000 --- a/statistics/templates/statistics/index.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends 'base.html' %} -{% block main %} - - - -{% endblock %} \ No newline at end of file diff --git a/statistics/urls.py b/statistics/urls.py deleted file mode 100644 index ab0f9d3..0000000 --- a/statistics/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.urls import path - -from . import views - -app_name = 'statistics' - -urlpatterns = [ - path('', views.home, name='home'), -] diff --git a/statistics/views.py b/statistics/views.py deleted file mode 100644 index d5eb1b8..0000000 --- a/statistics/views.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.shortcuts import render -from django.contrib.auth.decorators import login_required -from djing.lib.decorators import only_admins - - -@login_required -@only_admins -def home(request): - return render(request, 'statistics/index.html') diff --git a/taskapp/handle.py b/taskapp/handle.py index c8bf45f..a492bc0 100644 --- a/taskapp/handle.py +++ b/taskapp/handle.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from django.template.loader import render_to_string from django.utils.translation import gettext as _ -from chatbot.send_func import send_notify +from djing.tasks import send_email_notify from chatbot.models import ChatException from djing.lib import MultipleException @@ -30,12 +30,9 @@ def handle(task, author, recipients): if task.state == 'F' or task.state == 'C': # If task completed or failed than send one message to author - try: - send_notify(fulltext, author, tag='taskap') - except ChatException as e: - raise TaskException(e) + send_email_notify.delay(fulltext, author.pk) else: - send_notify(fulltext, recipient, tag='taskap') + send_email_notify.delay(fulltext, recipient.pk) except ChatException as e: errors.append(e) if len(errors) > 0: