28 changed files with 190 additions and 605 deletions
-
66abonapp/templates/abonapp/charts.html
-
5abonapp/templates/abonapp/ext.htm
-
3abonapp/urls.py
-
71abonapp/views.py
-
27chatbot/email_bot.py
-
5chatbot/send_func.py
-
28devapp/views.py
-
6djing/__init__.py
-
9djing/celery.py
-
10djing/settings.py
-
56djing/tasks.py
-
2djing/urls.py
-
7ip_pool/models.py
-
115msg_app/models.py
-
4requirements.txt
-
0statistics/__init__.py
-
0statistics/admin.py
-
5statistics/apps.py
-
53statistics/fields.py
-
29statistics/migrations/0001_initial.py
-
19statistics/migrations/0002_auto_20180808_1236.py
-
79statistics/migrations/0003_auto_20180814_1921.py
-
0statistics/migrations/__init__.py
-
132statistics/models.py
-
37statistics/templates/statistics/index.html
-
9statistics/urls.py
-
9statistics/views.py
-
9taskapp/handle.py
@ -1,66 +0,0 @@ |
|||
{% extends request.is_ajax|yesno:'nullcont.htm,abonapp/ext.htm' %} |
|||
{% load i18n %} |
|||
{% block content %} |
|||
|
|||
<div class="row"> |
|||
<div class="col-sm-12"> |
|||
<div class="panel panel-default"> |
|||
<div class="panel-heading"> |
|||
<h3 class="panel-title">{% blocktrans with wantdate_d=wantdate|date:'j E Y' %}Graph of use by {{ wantdate_d }}{% endblocktrans %}</h3> |
|||
</div> |
|||
<div class="panel-body"> |
|||
{% if charts_data %} |
|||
<div id="chrt"></div> |
|||
<script type="text/javascript"> |
|||
$(document).ready(function ($) { |
|||
new Chartist.Line('#chrt', { |
|||
series: [ |
|||
{ |
|||
data: [ |
|||
{{ charts_data }} |
|||
] |
|||
} |
|||
] |
|||
}, { |
|||
height: '600px', |
|||
showArea: true, |
|||
showLine: false, |
|||
showPoint: false, |
|||
high: {{ high }}, |
|||
axisX: { |
|||
type: Chartist.FixedScaleAxis, |
|||
divisor: 12, |
|||
labelInterpolationFnc: function (value) { |
|||
return moment(value).format('HH:mm:ss'); |
|||
} |
|||
}, |
|||
lineSmooth: Chartist.Interpolation.cardinal({ |
|||
tension: 0 |
|||
}) |
|||
}); |
|||
}); |
|||
</script> |
|||
{% else %} |
|||
<h2>{% trans 'Static info was Not found' %}</h2> |
|||
{% endif %} |
|||
<form action="{% url 'abonapp:charts' group.pk abon.username %}" method="get" class="input-group"> |
|||
<span class="input-group-btn"> |
|||
<button class="btn btn-default" type="submit"> |
|||
<span class="glyphicon glyphicon-calendar"></span> {% trans 'Show graph by date' %} |
|||
</button> |
|||
</span> |
|||
<input type="text" class="form-control" placeholder="{% trans 'Choose a date' %}" id="date_choose" name="wantdate" value="{{ wantdate|date:'dmY' }}"> |
|||
</form> |
|||
<script type="text/javascript"> |
|||
$(document).ready(function ($) { |
|||
$('#date_choose').datetimepicker({ |
|||
format: 'DDMMYYYY' |
|||
}); |
|||
}); |
|||
</script> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
{% endblock %} |
|||
@ -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) |
|||
@ -1,5 +0,0 @@ |
|||
# send via email |
|||
from .email_bot import send_notify |
|||
|
|||
# for Telegram |
|||
# from chatbot.telebot import send_notify |
|||
@ -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() |
|||
@ -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) |
|||
@ -1,5 +0,0 @@ |
|||
from django.apps import AppConfig |
|||
|
|||
|
|||
class StatisticsConfig(AppConfig): |
|||
name = 'statistics' |
|||
@ -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) |
|||
@ -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', |
|||
}, |
|||
), |
|||
] |
|||
@ -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',)}, |
|||
), |
|||
] |
|||
@ -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' |
|||
}, |
|||
) |
|||
) |
|||
) |
|||
@ -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' |
|||
@ -1,37 +0,0 @@ |
|||
{% extends 'base.html' %} |
|||
{% block main %} |
|||
<link href="/static/css/chartist.min.css" rel="stylesheet" type="text/css"/> |
|||
<script src="/static/js/chartist.min.js"></script> |
|||
<script> |
|||
|
|||
var chart = new Chartist.Line('#maincontent', { |
|||
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], |
|||
series: [ |
|||
[1, 5, 2, 5, 4, 3], |
|||
[2, 3, 4, 8, 1, 2], |
|||
[5, 4, 3, 2, 1, 0.5] |
|||
] |
|||
}, { |
|||
low: 0, |
|||
showArea: true, |
|||
showPoint: false, |
|||
fullWidth: true, |
|||
height: 500 |
|||
}); |
|||
|
|||
chart.on('draw', function (data) { |
|||
if (data.type === 'line' || data.type === 'area') { |
|||
data.element.animate({ |
|||
d: { |
|||
begin: 2000 * data.index, |
|||
dur: 2000, |
|||
from: data.path.clone().scale(1, 0).translate(0, data.chartRect.height()).stringify(), |
|||
to: data.path.clone().stringify(), |
|||
easing: Chartist.Svg.Easing.easeOutQuint |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
</script> |
|||
{% endblock %} |
|||
@ -1,9 +0,0 @@ |
|||
from django.urls import path |
|||
|
|||
from . import views |
|||
|
|||
app_name = 'statistics' |
|||
|
|||
urlpatterns = [ |
|||
path('', views.home, name='home'), |
|||
] |
|||
@ -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') |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue