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