17 changed files with 319 additions and 24 deletions
-
37abonapp/templates/abonapp/peoples.html
-
6abonapp/views.py
-
2djing/settings.py
-
2djing/urls.py
-
3locale/ru/LC_MESSAGES/django.po
-
9templates/base.html
-
0traf_stat/__init__.py
-
3traf_stat/admin.py
-
5traf_stat/apps.py
-
50traf_stat/fields.py
-
31traf_stat/migrations/0001_initial.py
-
0traf_stat/migrations/__init__.py
-
129traf_stat/models.py
-
45traf_stat/templates/statistics/index.html
-
3traf_stat/tests.py
-
9traf_stat/urls.py
-
9traf_stat/views.py
@ -0,0 +1,3 @@ |
|||||
|
from django.contrib import admin |
||||
|
|
||||
|
# Register your models here. |
||||
@ -0,0 +1,5 @@ |
|||||
|
from django.apps import AppConfig |
||||
|
|
||||
|
|
||||
|
class TrafStatConfig(AppConfig): |
||||
|
name = 'traf_stat' |
||||
@ -0,0 +1,50 @@ |
|||||
|
# |
||||
|
# 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 isinstance(val, str): |
||||
|
# 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)) |
||||
|
|
||||
|
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, *args, **kwargs): |
||||
|
return self.to_python(val) |
||||
@ -0,0 +1,31 @@ |
|||||
|
# Generated by Django 2.1.1 on 2019-03-06 18:07 |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
import traf_stat.fields |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
initial = True |
||||
|
|
||||
|
dependencies = [ |
||||
|
('abonapp', '0001_squashed_0008_auto_20181115_1206'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.CreateModel( |
||||
|
name='StatCache', |
||||
|
fields=[ |
||||
|
('last_time', traf_stat.fields.UnixDateTimeField()), |
||||
|
('abon', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='abonapp.Abon')), |
||||
|
('octets', models.PositiveIntegerField(default=0)), |
||||
|
('packets', models.PositiveIntegerField(default=0)), |
||||
|
], |
||||
|
options={ |
||||
|
'db_table': 'flowcache', |
||||
|
'ordering': ('-last_time',), |
||||
|
}, |
||||
|
), |
||||
|
migrations.RunSQL(sql=(r'ALTER TABLE flowcache ENGINE=MEMORY;',)) |
||||
|
] |
||||
@ -0,0 +1,129 @@ |
|||||
|
from datetime import datetime, timedelta, date, time |
||||
|
import math |
||||
|
|
||||
|
from django.db import models, connection, ProgrammingError |
||||
|
from django.utils.timezone import now |
||||
|
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 "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 = models.PositiveIntegerField() |
||||
|
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() |
||||
|
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',) |
||||
@ -0,0 +1,45 @@ |
|||||
|
{% extends 'base.html' %} |
||||
|
{% load i18n %} |
||||
|
|
||||
|
{% block additional_link %} |
||||
|
<link href="/static/css/chartist.min.css" rel="stylesheet" type="text/css"/> |
||||
|
<script src="/static/js/chartist.min.js"></script> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block page-header %}{% trans 'Traffic' %}{% endblock %} |
||||
|
|
||||
|
{% block main %} |
||||
|
<script> |
||||
|
$(document).ready(function () { |
||||
|
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> |
||||
|
<div id="maincontent"></div> |
||||
|
{% endblock %} |
||||
@ -0,0 +1,3 @@ |
|||||
|
from django.test import TestCase |
||||
|
|
||||
|
# Create your tests here. |
||||
@ -0,0 +1,9 @@ |
|||||
|
from django.urls import path |
||||
|
|
||||
|
from traf_stat.views import home |
||||
|
|
||||
|
app_name = 'traf_stat' |
||||
|
|
||||
|
urlpatterns = [ |
||||
|
path('', home, name='home'), |
||||
|
] |
||||
@ -0,0 +1,9 @@ |
|||||
|
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