stats
This commit is contained in:
parent
cdfe18f6b7
commit
9a7581cfdf
@ -47,7 +47,8 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
'web.apps.WebConfig',
|
'web.apps.WebConfig',
|
||||||
'configs.apps.ConfigsConfig',
|
'configs.apps.ConfigsConfig',
|
||||||
'experiments.apps.ExperimentsConfig'
|
'experiments.apps.ExperimentsConfig',
|
||||||
|
'stats.apps.StatsConfig'
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -20,5 +20,6 @@ urlpatterns = [
|
|||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path('configs/', include('configs.urls')),
|
path('configs/', include('configs.urls')),
|
||||||
path('experiments/', include('experiments.urls')),
|
path('experiments/', include('experiments.urls')),
|
||||||
|
path('stats/', include('stats.urls')),
|
||||||
path('', include('web.urls'))
|
path('', include('web.urls'))
|
||||||
]
|
]
|
||||||
|
0
stats/__init__.py
Normal file
0
stats/__init__.py
Normal file
7
stats/admin.py
Normal file
7
stats/admin.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from stats.models import Snapshot
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
|
||||||
|
admin.site.register(Snapshot)
|
6
stats/apps.py
Normal file
6
stats/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class StatsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'stats'
|
0
stats/management/__init__.py
Normal file
0
stats/management/__init__.py
Normal file
0
stats/management/commands/__init__.py
Normal file
0
stats/management/commands/__init__.py
Normal file
31
stats/management/commands/fetch_stats.py
Normal file
31
stats/management/commands/fetch_stats.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import datetime
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import croniter
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
from requests import get
|
||||||
|
|
||||||
|
from stats.models import Snapshot
|
||||||
|
from web.models import Project
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
while True:
|
||||||
|
for project in Project.objects.filter(next_stats_fetch_time__lte=timezone.now()):
|
||||||
|
if project.stats_enabled:
|
||||||
|
if not project.stats_link:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
response = get(project.stats_link)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
if response.status_code != 200:
|
||||||
|
continue
|
||||||
|
Snapshot.objects.create(project=project, data=response.json())
|
||||||
|
cron = croniter.croniter(project.stats_cron, timezone.now())
|
||||||
|
next_date = cron.get_next(datetime.datetime)
|
||||||
|
project.next_stats_fetch_time = next_date
|
||||||
|
project.save()
|
||||||
|
sleep(5 * 60)
|
30
stats/migrations/0001_initial.py
Normal file
30
stats/migrations/0001_initial.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-09-26 16:20
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('web', '0004_alter_customuser_vk_id_alter_customuser_yandex_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Snapshot',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('data', models.JSONField()),
|
||||||
|
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.project')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='snapshot',
|
||||||
|
index=models.Index(fields=['project', '-created_at'], name='stats_snaps_project_4ed188_idx'),
|
||||||
|
),
|
||||||
|
]
|
0
stats/migrations/__init__.py
Normal file
0
stats/migrations/__init__.py
Normal file
13
stats/models.py
Normal file
13
stats/models.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Snapshot(models.Model):
|
||||||
|
data = models.JSONField()
|
||||||
|
project = models.ForeignKey('web.Project', on_delete=models.CASCADE)
|
||||||
|
created_at = models.DateTimeField(default=timezone.now)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['project', '-created_at'])
|
||||||
|
]
|
3
stats/tests.py
Normal file
3
stats/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
7
stats/urls.py
Normal file
7
stats/urls.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from .views import *
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(*StatsView.as_path()),
|
||||||
|
]
|
44
stats/views.py
Normal file
44
stats/views.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import croniter
|
||||||
|
import validators
|
||||||
|
|
||||||
|
from BaseLib.BaseView import BaseView
|
||||||
|
from stats.models import Snapshot
|
||||||
|
|
||||||
|
|
||||||
|
class StatsView(BaseView):
|
||||||
|
endpoint = ''
|
||||||
|
view_file = 'stats.html'
|
||||||
|
required_login = True
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
count = 20
|
||||||
|
snapshots = list(Snapshot.objects.filter(project=self.request.user.selected_project).order_by('-created_at')[:count])
|
||||||
|
keys = set()
|
||||||
|
for snapshot in snapshots:
|
||||||
|
for key in snapshot.data:
|
||||||
|
keys.add(key)
|
||||||
|
keys = list(keys)
|
||||||
|
rows = [[] for _ in keys]
|
||||||
|
for snapshot in snapshots:
|
||||||
|
for index, key in enumerate(keys):
|
||||||
|
rows[index].append(snapshot.data.get(key))
|
||||||
|
self.context['keys'] = keys
|
||||||
|
self.context['data'] = rows
|
||||||
|
self.context['err'] = 'err' in self.request.GET
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
self.request.user.selected_project.stats_enabled = 'enabled' in self.request.POST
|
||||||
|
if self.request.POST['link'] == "":
|
||||||
|
self.request.user.selected_project.stats_link = None
|
||||||
|
elif validators.url(self.request.POST['link']):
|
||||||
|
self.request.user.selected_project.stats_link = self.request.POST['link']
|
||||||
|
else:
|
||||||
|
return '/stats?err=true'
|
||||||
|
if self.request.POST['cron'] == "":
|
||||||
|
self.request.user.selected_project.stats_cron = None
|
||||||
|
elif croniter.croniter.is_valid(self.request.POST['cron']):
|
||||||
|
self.request.user.selected_project.stats_cron = self.request.POST['cron']
|
||||||
|
else:
|
||||||
|
return '/stats?err=true'
|
||||||
|
self.request.user.selected_project.save()
|
||||||
|
return '/stats'
|
@ -91,7 +91,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="/profile" class="nav-link">
|
<a href="/stats" class="nav-link">
|
||||||
<span class="sidebar-icon">
|
<span class="sidebar-icon">
|
||||||
<i class="fa fa-arrow-up"></i>
|
<i class="fa fa-arrow-up"></i>
|
||||||
</span>
|
</span>
|
||||||
|
27
templates/stats.html
Normal file
27
templates/stats.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends 'layouts/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Статистика</h1>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<h4><input {% if user.selected_project.stats_link %}value="{{ user.selected_project.stats_link }}"{% endif %} name="link" placeholder="Ссылка на статистику" /> <input {% if user.selected_project.stats_cron %} value="{{ user.selected_project.stats_cron }}"{% endif %} name="cron" placeholder="CRON"/> Включено <input name="enabled" type="checkbox" {% if user.selected_project.stats_enabled %}checked{% endif %} /> <button type="submit" class="btn btn-primary">Сохранить</button> </h4>
|
||||||
|
</form>
|
||||||
|
{% if err %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
Ошибка валидации!
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
{% for row in data %}
|
||||||
|
<tr>
|
||||||
|
{% for item in row %}
|
||||||
|
<td>
|
||||||
|
{{ item }}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
@ -3,3 +3,12 @@ from django.db import models
|
|||||||
|
|
||||||
class Project(models.Model):
|
class Project(models.Model):
|
||||||
name = models.TextField()
|
name = models.TextField()
|
||||||
|
stats_link = models.TextField(null=True)
|
||||||
|
stats_cron = models.TextField(null=True)
|
||||||
|
stats_enabled = models.BooleanField(default=False)
|
||||||
|
next_stats_fetch_time = models.DateTimeField(null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['next_stats_fetch_time', 'stats_enabled'])
|
||||||
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user