django-admin-charts 1.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- admin_tools_stats/__init__.py +20 -0
- admin_tools_stats/__pycache__/__init__.cpython-311.pyc +0 -0
- admin_tools_stats/admin.py +186 -0
- admin_tools_stats/app_label_renamer.py +93 -0
- admin_tools_stats/apps.py +6 -0
- admin_tools_stats/charts.py +0 -0
- admin_tools_stats/fixtures/10_dashboardstatscriteria_status.json +17 -0
- admin_tools_stats/fixtures/20_dashboardstatscriteria_call_type.json +26 -0
- admin_tools_stats/fixtures/initial_data.json +37 -0
- admin_tools_stats/forms.py +97 -0
- admin_tools_stats/management/commands/recalculate_charts.py +130 -0
- admin_tools_stats/migrations/0001_initial.py +188 -0
- admin_tools_stats/migrations/0002_auto_20190920_1058.py +95 -0
- admin_tools_stats/migrations/0003_auto_20191007_0950.py +90 -0
- admin_tools_stats/migrations/0004_dashboardstats_y_tick_format.py +24 -0
- admin_tools_stats/migrations/0005_auto_20200203_1537.py +71 -0
- admin_tools_stats/migrations/0006_auto_20200205_0944.py +109 -0
- admin_tools_stats/migrations/0007_auto_20200205_1054.py +62 -0
- admin_tools_stats/migrations/0008_auto_20200911_1400.py +35 -0
- admin_tools_stats/migrations/0009_auto_20200928_1003.py +26 -0
- admin_tools_stats/migrations/0010_dashboardstats_show_to_users.py +21 -0
- admin_tools_stats/migrations/0011_auto_20210204_1206.py +63 -0
- admin_tools_stats/migrations/0012_auto_20210207_0859.py +80 -0
- admin_tools_stats/migrations/0013_auto_20210221_1247.py +47 -0
- admin_tools_stats/migrations/0014_auto_20211122_1511.py +254 -0
- admin_tools_stats/migrations/0015_auto_20211209_0822.py +36 -0
- admin_tools_stats/migrations/0016_dashboardstats_cache_values.py +17 -0
- admin_tools_stats/migrations/0017_alter_dashboardstats_options_and_more.py +46 -0
- admin_tools_stats/migrations/0018_alter_dashboardstats_options.py +16 -0
- admin_tools_stats/migrations/0019_criteriatostatsm2m_recalculate.py +20 -0
- admin_tools_stats/migrations/0020_alter_dashboardstats_graph_key.py +28 -0
- admin_tools_stats/migrations/0021_auto_20230210_1102.py +40 -0
- admin_tools_stats/migrations/__init__.py +0 -0
- admin_tools_stats/models.py +1077 -0
- admin_tools_stats/modules.py +104 -0
- admin_tools_stats/south_migrations/0001_initial_migration.py +211 -0
- admin_tools_stats/south_migrations/0002_add_sum_field_name.py +121 -0
- admin_tools_stats/south_migrations/0003_add_operation_field_name.py +148 -0
- admin_tools_stats/south_migrations/__init__.py +0 -0
- admin_tools_stats/templates/admin/index.html +22 -0
- admin_tools_stats/templates/admin_tools_stats/admin_charts.js +83 -0
- admin_tools_stats/templates/admin_tools_stats/analytics.html +87 -0
- admin_tools_stats/templates/admin_tools_stats/analytics_chart.html +3 -0
- admin_tools_stats/templates/admin_tools_stats/analytics_user.html +5 -0
- admin_tools_stats/templates/admin_tools_stats/chart_container.html +10 -0
- admin_tools_stats/templates/admin_tools_stats/chart_containers.html +8 -0
- admin_tools_stats/templates/admin_tools_stats/chart_data.html +17 -0
- admin_tools_stats/templates/admin_tools_stats/chart_form.html +35 -0
- admin_tools_stats/templates/admin_tools_stats/modules/chart.html +18 -0
- admin_tools_stats/templates/include_nvd3.html +5 -0
- admin_tools_stats/templatetags/__init__.py +0 -0
- admin_tools_stats/templatetags/admin_chart_tags.py +56 -0
- admin_tools_stats/tests/__init__.py +0 -0
- admin_tools_stats/tests/test_admin.py +288 -0
- admin_tools_stats/tests/test_forms.py +18 -0
- admin_tools_stats/tests/test_models.py +1693 -0
- admin_tools_stats/tests/test_views.py +563 -0
- admin_tools_stats/tests/utils.py +41 -0
- admin_tools_stats/urls.py +27 -0
- admin_tools_stats/utils.py +37 -0
- admin_tools_stats/views.py +205 -0
- demoproject/.coveralls.yml +1 -0
- demoproject/.gitignore +1 -0
- demoproject/__init__.py +0 -0
- demoproject/dashboard.py +147 -0
- demoproject/demoproject/__init__.py +0 -0
- demoproject/demoproject/fixtures/auth_user.json +42 -0
- demoproject/demoproject/fixtures/test_data.json +118 -0
- demoproject/demoproject/migrations/0001_initial.py +33 -0
- demoproject/demoproject/migrations/0002_auto_20210221_0541.py +22 -0
- demoproject/demoproject/migrations/0003_testkid_author.py +26 -0
- demoproject/demoproject/migrations/0004_testkid_height.py +17 -0
- demoproject/demoproject/migrations/__init__.py +0 -0
- demoproject/demoproject/models.py +19 -0
- demoproject/demoproject/settings.py +260 -0
- demoproject/demoproject/templates/admin/base_site.html +22 -0
- demoproject/demoproject/templates/home.html +7 -0
- demoproject/demoproject/urls.py +17 -0
- demoproject/demoproject/views.py +12 -0
- demoproject/demoproject/wsgi.py +35 -0
- demoproject/manage.py +11 -0
- demoproject/menu.py +32 -0
- demoproject/requirements.txt +7 -0
- demoproject/test_settings.py +13 -0
- django_admin_charts-1.3.1.dist-info/METADATA +328 -0
- django_admin_charts-1.3.1.dist-info/RECORD +88 -0
- django_admin_charts-1.3.1.dist-info/WHEEL +5 -0
- django_admin_charts-1.3.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
6
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
7
|
+
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
8
|
+
#
|
|
9
|
+
# Copyright (C) 2011-2014 Star2Billing S.L.
|
|
10
|
+
#
|
|
11
|
+
# The Initial Developer of the Original Code is
|
|
12
|
+
# Arezqui Belaid <info@star2billing.com>
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
# edit also docs/source/conf.py and update requirements.txt
|
|
16
|
+
__version__ = "1.3.1"
|
|
17
|
+
__author__ = "Petr Dlouhy"
|
|
18
|
+
__contact__ = "petr.dlouhy@emai.cz"
|
|
19
|
+
__homepage__ = "https://github.com/PetrDlouhy"
|
|
20
|
+
__docformat__ = "restructuredtext"
|
|
Binary file
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
4
|
+
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
#
|
|
6
|
+
# Copyright (C) 2011-2014 Star2Billing S.L.
|
|
7
|
+
#
|
|
8
|
+
# The Initial Developer of the Original Code is
|
|
9
|
+
# Arezqui Belaid <info@star2billing.com>
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
from django import forms
|
|
13
|
+
from django.contrib import admin
|
|
14
|
+
from django.urls import reverse
|
|
15
|
+
from django.utils.html import format_html
|
|
16
|
+
from django.utils.translation import gettext_lazy as _
|
|
17
|
+
|
|
18
|
+
from admin_tools_stats.app_label_renamer import AppLabelRenamer
|
|
19
|
+
from admin_tools_stats.models import (
|
|
20
|
+
CachedValue,
|
|
21
|
+
CriteriaToStatsM2M,
|
|
22
|
+
DashboardStats,
|
|
23
|
+
DashboardStatsCriteria,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
AppLabelRenamer(native_app_label="admin_tools_stats", app_label=_("Admin Tools Stats")).main()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@admin.register(DashboardStatsCriteria)
|
|
31
|
+
class DashboardStatsCriteriaAdmin(admin.ModelAdmin):
|
|
32
|
+
"""
|
|
33
|
+
Allows the administrator to view and modify certain attributes
|
|
34
|
+
of a DashboardStats.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
list_display = (
|
|
38
|
+
"id",
|
|
39
|
+
"criteria_name",
|
|
40
|
+
"criteria_name",
|
|
41
|
+
"dynamic_criteria_field_name",
|
|
42
|
+
"criteria_dynamic_mapping_preview",
|
|
43
|
+
)
|
|
44
|
+
list_filter = ["created_date"]
|
|
45
|
+
readonly_fields = (
|
|
46
|
+
"created_date",
|
|
47
|
+
"updated_date",
|
|
48
|
+
)
|
|
49
|
+
search_fields = ("criteria_name",)
|
|
50
|
+
ordering = ("id",)
|
|
51
|
+
save_as = True
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DashboardStatsCriteriaInline(admin.TabularInline):
|
|
55
|
+
model = CriteriaToStatsM2M
|
|
56
|
+
readonly_fields = (
|
|
57
|
+
"criteria__dynamic_criteria_field_name",
|
|
58
|
+
"criteria__criteria_dynamic_mapping_preview",
|
|
59
|
+
)
|
|
60
|
+
fields = (
|
|
61
|
+
"criteria",
|
|
62
|
+
"order",
|
|
63
|
+
"use_as",
|
|
64
|
+
"default_option",
|
|
65
|
+
"choices_based_on_time_range",
|
|
66
|
+
"recalculate",
|
|
67
|
+
"count_limit",
|
|
68
|
+
"prefix",
|
|
69
|
+
"criteria__dynamic_criteria_field_name",
|
|
70
|
+
"criteria__criteria_dynamic_mapping_preview",
|
|
71
|
+
)
|
|
72
|
+
autocomplete_fields = ("criteria",)
|
|
73
|
+
extra = 0
|
|
74
|
+
|
|
75
|
+
def criteria__dynamic_criteria_field_name(self, obj):
|
|
76
|
+
return format_html(
|
|
77
|
+
"<strong>{}</strong>{}",
|
|
78
|
+
str(obj.prefix or ""),
|
|
79
|
+
str(obj.criteria.dynamic_criteria_field_name or ""),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def criteria__criteria_dynamic_mapping_preview(self, obj):
|
|
83
|
+
return obj.criteria.criteria_dynamic_mapping_preview()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class DashboardStatsForm(forms.ModelForm):
|
|
87
|
+
def __init__(self, *args, **kwargs):
|
|
88
|
+
super().__init__(*args, **kwargs)
|
|
89
|
+
if self.instance.pk:
|
|
90
|
+
self.fields["default_multiseries_criteria"].queryset = (
|
|
91
|
+
CriteriaToStatsM2M.objects.filter(
|
|
92
|
+
stats=self.instance,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@admin.register(DashboardStats)
|
|
98
|
+
class DashboardStatsAdmin(admin.ModelAdmin):
|
|
99
|
+
"""
|
|
100
|
+
Allows the administrator to view and modify certain attributes
|
|
101
|
+
of a DashboardStats.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
fieldsets = (
|
|
105
|
+
(
|
|
106
|
+
None,
|
|
107
|
+
{
|
|
108
|
+
"fields": (
|
|
109
|
+
"graph_key",
|
|
110
|
+
"graph_title",
|
|
111
|
+
("model_app_name", "model_name", "date_field_name"),
|
|
112
|
+
("operation_field_name", "distinct"),
|
|
113
|
+
("user_field_name", "show_to_users"),
|
|
114
|
+
("allowed_type_operation_field_name", "type_operation_field_name"),
|
|
115
|
+
("allowed_chart_types", "default_chart_type"),
|
|
116
|
+
(
|
|
117
|
+
"allowed_time_scales",
|
|
118
|
+
"default_time_scale",
|
|
119
|
+
"default_time_period",
|
|
120
|
+
),
|
|
121
|
+
"y_axis_format",
|
|
122
|
+
"default_multiseries_criteria",
|
|
123
|
+
"is_visible",
|
|
124
|
+
"cache_values",
|
|
125
|
+
),
|
|
126
|
+
},
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
list_display = (
|
|
130
|
+
"id",
|
|
131
|
+
"graph_key",
|
|
132
|
+
"analytics_link",
|
|
133
|
+
"graph_title",
|
|
134
|
+
"model_name",
|
|
135
|
+
"distinct",
|
|
136
|
+
"type_operation_field_name",
|
|
137
|
+
"is_visible",
|
|
138
|
+
"show_to_users",
|
|
139
|
+
"cache_values",
|
|
140
|
+
"created_date",
|
|
141
|
+
"date_field_name",
|
|
142
|
+
"operation_field_name",
|
|
143
|
+
"default_chart_type",
|
|
144
|
+
)
|
|
145
|
+
list_filter = [
|
|
146
|
+
"created_date",
|
|
147
|
+
"is_visible",
|
|
148
|
+
"show_to_users",
|
|
149
|
+
"cache_values",
|
|
150
|
+
]
|
|
151
|
+
exclude = ("criteria",)
|
|
152
|
+
inlines = [DashboardStatsCriteriaInline]
|
|
153
|
+
save_as = True
|
|
154
|
+
form = DashboardStatsForm
|
|
155
|
+
|
|
156
|
+
def analytics_link(self, obj):
|
|
157
|
+
return format_html(
|
|
158
|
+
"<a href='{url}?show={key}' target='_blank'>A</a>",
|
|
159
|
+
url=reverse("chart-analytics"),
|
|
160
|
+
key=obj.graph_key,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@admin.register(CachedValue)
|
|
165
|
+
class CachedValueAdmin(admin.ModelAdmin):
|
|
166
|
+
list_display = (
|
|
167
|
+
"stats",
|
|
168
|
+
"date",
|
|
169
|
+
"value",
|
|
170
|
+
"operation",
|
|
171
|
+
"operation_field_name",
|
|
172
|
+
"filtered_value",
|
|
173
|
+
"time_scale",
|
|
174
|
+
"multiple_series_choice",
|
|
175
|
+
"is_final",
|
|
176
|
+
)
|
|
177
|
+
list_filter = (
|
|
178
|
+
"stats",
|
|
179
|
+
"operation",
|
|
180
|
+
"operation_field_name",
|
|
181
|
+
"filtered_value",
|
|
182
|
+
"time_scale",
|
|
183
|
+
"is_final",
|
|
184
|
+
"multiple_series_choice",
|
|
185
|
+
"stats__cache_values",
|
|
186
|
+
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
4
|
+
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
#
|
|
6
|
+
# Copyright (C) 2011-2014 Star2Billing S.L.
|
|
7
|
+
#
|
|
8
|
+
# The Initial Developer of the Original Code is
|
|
9
|
+
# Arezqui Belaid <info@star2billing.com>
|
|
10
|
+
#
|
|
11
|
+
from django.contrib import admin
|
|
12
|
+
from django.db.models.base import ModelBase
|
|
13
|
+
from django.urls import resolve
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# TODO: Follow evolution of https://code.djangoproject.com/ticket/3591
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Source link : http://django-notes.blogspot.in/2011/07/django-app-name-breadcrumbs-l10n.html
|
|
20
|
+
class AppLabelRenamer(object):
|
|
21
|
+
"""
|
|
22
|
+
Rename app label and app breadcrumbs in admin
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, native_app_label, app_label):
|
|
26
|
+
self.native_app_label = native_app_label
|
|
27
|
+
self.app_label = app_label
|
|
28
|
+
self.module = ".".join([native_app_label, "models"])
|
|
29
|
+
|
|
30
|
+
class StringWithRealoadedTitle(str):
|
|
31
|
+
"""
|
|
32
|
+
thanks to Ionel Maries Cristian for
|
|
33
|
+
http://ionelmc.wordpress.com/2011/06/24/custom-app-names-in-the-django-admin/
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __new__(cls, value, title):
|
|
37
|
+
instance = str.__new__(cls, value)
|
|
38
|
+
instance._title = title
|
|
39
|
+
return instance
|
|
40
|
+
|
|
41
|
+
def title(self):
|
|
42
|
+
return self._title
|
|
43
|
+
|
|
44
|
+
def __copy__(self):
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def __deepcopy__(self, memodict):
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def rename_app_label(self, f):
|
|
51
|
+
app_label = self.app_label
|
|
52
|
+
|
|
53
|
+
def rename_breadcrumbs(f):
|
|
54
|
+
def wrap(self, *args, **kwargs):
|
|
55
|
+
extra_context = kwargs.get("extra_context", {})
|
|
56
|
+
extra_context["app_label"] = app_label
|
|
57
|
+
kwargs["extra_context"] = extra_context
|
|
58
|
+
return f(self, *args, **kwargs)
|
|
59
|
+
|
|
60
|
+
return wrap
|
|
61
|
+
|
|
62
|
+
def wrap(model_or_iterable, admin_class=None, **option):
|
|
63
|
+
if isinstance(model_or_iterable, ModelBase):
|
|
64
|
+
model_or_iterable = [model_or_iterable]
|
|
65
|
+
for model in model_or_iterable:
|
|
66
|
+
if model.__module__ != self.module:
|
|
67
|
+
continue
|
|
68
|
+
if admin_class is None:
|
|
69
|
+
admin_class = type(model.__name__ + "Admin", (admin.ModelAdmin,), {})
|
|
70
|
+
admin_class.add_view = rename_breadcrumbs(admin_class.add_view)
|
|
71
|
+
admin_class.change_view = rename_breadcrumbs(admin_class.change_view)
|
|
72
|
+
admin_class.changelist_view = rename_breadcrumbs(admin_class.changelist_view)
|
|
73
|
+
model._meta.app_label = self.StringWithRealoadedTitle(
|
|
74
|
+
self.native_app_label, self.app_label
|
|
75
|
+
)
|
|
76
|
+
return f(model, admin_class, **option)
|
|
77
|
+
|
|
78
|
+
return wrap
|
|
79
|
+
|
|
80
|
+
def rename_app_index(self, f):
|
|
81
|
+
def wrap(request, app_label, extra_context=None):
|
|
82
|
+
requested_app_label = resolve(request.path).kwargs.get("app_label", "")
|
|
83
|
+
if requested_app_label and requested_app_label == self.native_app_label:
|
|
84
|
+
app_label = self.StringWithRealoadedTitle(self.native_app_label, self.app_label)
|
|
85
|
+
else:
|
|
86
|
+
app_label = requested_app_label
|
|
87
|
+
return f(request, app_label, extra_context=None)
|
|
88
|
+
|
|
89
|
+
return wrap
|
|
90
|
+
|
|
91
|
+
def main(self):
|
|
92
|
+
admin.site.register = self.rename_app_label(admin.site.register)
|
|
93
|
+
admin.site.app_index = self.rename_app_index(admin.site.app_index)
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"pk":1,
|
|
4
|
+
"model":"admin_tools_stats.dashboardstatscriteria",
|
|
5
|
+
"fields":{
|
|
6
|
+
"criteria_fix_mapping":{
|
|
7
|
+
"is_active":"1"
|
|
8
|
+
},
|
|
9
|
+
"updated_date":"2011-09-01 09:06:12",
|
|
10
|
+
"criteria_name":"status",
|
|
11
|
+
"criteria_dynamic_mapping":{
|
|
12
|
+
|
|
13
|
+
},
|
|
14
|
+
"created_date":"2011-09-01 09:01:40"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"pk":2,
|
|
4
|
+
"model":"admin_tools_stats.dashboardstatscriteria",
|
|
5
|
+
"fields":{
|
|
6
|
+
"criteria_fix_mapping":null,
|
|
7
|
+
"updated_date":"2011-09-02 05:51:41",
|
|
8
|
+
"criteria_name":"call_type",
|
|
9
|
+
"dynamic_criteria_field_name":"disposition",
|
|
10
|
+
"criteria_dynamic_mapping":{
|
|
11
|
+
"INVALIDARGS":"INVALIDARGS",
|
|
12
|
+
"BUSY":"BUSY",
|
|
13
|
+
"TORTURE":"TORTURE",
|
|
14
|
+
"ANSWER":"ANSWER",
|
|
15
|
+
"DONTCALL":"DONTCALL",
|
|
16
|
+
"FORBIDDEN":"FORBIDDEN",
|
|
17
|
+
"NOROUTE":"NOROUTE",
|
|
18
|
+
"CHANUNAVAIL":"CHANUNAVAIL",
|
|
19
|
+
"NOANSWER":"NOANSWER",
|
|
20
|
+
"CONGESTION":"CONGESTION",
|
|
21
|
+
"CANCEL":"CANCEL"
|
|
22
|
+
},
|
|
23
|
+
"created_date":"2011-09-02 05:51:41"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"pk":1,
|
|
4
|
+
"model":"admin_tools_stats.dashboardstatscriteria",
|
|
5
|
+
"fields":{
|
|
6
|
+
"criteria_fix_mapping":{
|
|
7
|
+
"is_active":"1"
|
|
8
|
+
},
|
|
9
|
+
"updated_date":"2011-09-01 09:06:12",
|
|
10
|
+
"criteria_name":"status",
|
|
11
|
+
"dynamic_criteria_field_name":"",
|
|
12
|
+
"criteria_dynamic_mapping":{
|
|
13
|
+
|
|
14
|
+
},
|
|
15
|
+
"created_date":"2011-09-01 09:01:40"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"pk":1,
|
|
20
|
+
"model":"admin_tools_stats.dashboardstats",
|
|
21
|
+
"fields":{
|
|
22
|
+
"updated_date":"2011-09-01 09:11:56",
|
|
23
|
+
"is_visible":true,
|
|
24
|
+
"graph_key":"user_graph",
|
|
25
|
+
"graph_title":"User graph",
|
|
26
|
+
"criteria":[
|
|
27
|
+
1
|
|
28
|
+
],
|
|
29
|
+
"date_field_name":"date_joined",
|
|
30
|
+
"operation_field_name":"",
|
|
31
|
+
"type_operation_field_name":"",
|
|
32
|
+
"model_name":"User",
|
|
33
|
+
"created_date":"2011-09-01 08:56:45",
|
|
34
|
+
"model_app_name":"auth"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
2
|
+
|
|
3
|
+
from django import forms
|
|
4
|
+
from django.utils.timezone import now
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChartSettingsForm(forms.Form):
|
|
8
|
+
def __init__(self, stats, user=None, *args, **kwargs):
|
|
9
|
+
super().__init__(*args, **kwargs)
|
|
10
|
+
for ch_filter in stats.criteriatostatsm2m_set.filter(use_as="chart_filter").order_by(
|
|
11
|
+
"order"
|
|
12
|
+
):
|
|
13
|
+
dy_map = ch_filter.get_dynamic_choices(user=user)
|
|
14
|
+
if dy_map:
|
|
15
|
+
self.fields[f"select_box_dynamic_{ch_filter.id}"] = forms.ChoiceField(
|
|
16
|
+
choices=[("", "-------")] + list(dy_map.values()),
|
|
17
|
+
label=ch_filter.criteria.criteria_name,
|
|
18
|
+
initial=ch_filter.default_option,
|
|
19
|
+
)
|
|
20
|
+
self.fields[f"select_box_dynamic_{ch_filter.id}"].widget.attrs[
|
|
21
|
+
"class"
|
|
22
|
+
] = "chart-input"
|
|
23
|
+
|
|
24
|
+
self.fields["graph_key"] = forms.CharField(
|
|
25
|
+
initial=stats.graph_key,
|
|
26
|
+
widget=forms.HiddenInput(attrs={"class": "hidden_graph_key"}),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
multiple_series = stats.criteriatostatsm2m_set.filter(use_as="multiple_series")
|
|
30
|
+
if multiple_series.exists():
|
|
31
|
+
choices = (
|
|
32
|
+
multiple_series.select_related("stats__default_multiseries_criteria", "criteria")
|
|
33
|
+
.order_by("order")
|
|
34
|
+
.values_list("id", "criteria__criteria_name")
|
|
35
|
+
)
|
|
36
|
+
self.fields["select_box_multiple_series"] = forms.ChoiceField(
|
|
37
|
+
label="Divide",
|
|
38
|
+
choices=[("", "-------")] + list(choices),
|
|
39
|
+
initial=(
|
|
40
|
+
stats.default_multiseries_criteria.id
|
|
41
|
+
if stats.default_multiseries_criteria
|
|
42
|
+
else None
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
self.fields["select_box_multiple_series"].widget.attrs[
|
|
46
|
+
"class"
|
|
47
|
+
] = "chart-input select_box_multiple_series"
|
|
48
|
+
|
|
49
|
+
if len(stats.allowed_type_operation_field_name) > 1:
|
|
50
|
+
self.fields["select_box_operation"] = forms.ChoiceField(
|
|
51
|
+
choices=stats.allowed_type_operation_field_name_choices(),
|
|
52
|
+
label="Operation",
|
|
53
|
+
initial=stats.type_operation_field_name,
|
|
54
|
+
)
|
|
55
|
+
self.fields["select_box_operation"].widget.attrs["class"] = "chart-input"
|
|
56
|
+
|
|
57
|
+
operations_list = stats.get_operations_list()
|
|
58
|
+
if operations_list and len(operations_list) > 1:
|
|
59
|
+
self.fields["select_box_operation_field"] = forms.ChoiceField(
|
|
60
|
+
choices=[("", "(divide all)")] + [(o, o) for o in operations_list],
|
|
61
|
+
label="Field",
|
|
62
|
+
initial=operations_list[0],
|
|
63
|
+
)
|
|
64
|
+
self.fields["select_box_operation_field"].widget.attrs["class"] = "chart-input"
|
|
65
|
+
|
|
66
|
+
if len(stats.allowed_time_scales) > 1:
|
|
67
|
+
self.fields["select_box_interval"] = forms.ChoiceField(
|
|
68
|
+
choices=stats.allowed_time_scales_choices(),
|
|
69
|
+
label="Scale",
|
|
70
|
+
initial=stats.default_time_scale,
|
|
71
|
+
)
|
|
72
|
+
self.fields["select_box_interval"].widget.attrs["class"] = "chart-input"
|
|
73
|
+
|
|
74
|
+
if len(stats.allowed_chart_types) > 1:
|
|
75
|
+
self.fields["select_box_chart_type"] = forms.ChoiceField(
|
|
76
|
+
choices=stats.allowed_chart_types_choices(),
|
|
77
|
+
label="Chart",
|
|
78
|
+
initial=stats.default_chart_type,
|
|
79
|
+
)
|
|
80
|
+
self.fields["select_box_chart_type"].widget.attrs[
|
|
81
|
+
"class"
|
|
82
|
+
] = "chart-input select_box_chart_type"
|
|
83
|
+
|
|
84
|
+
self.fields["time_since"] = forms.DateField(
|
|
85
|
+
label="Since",
|
|
86
|
+
initial=(now() - timedelta(days=stats.default_time_period)).strftime("%Y-%m-%d"),
|
|
87
|
+
widget=forms.TextInput(
|
|
88
|
+
attrs={"type": "date", "class": "chart-input select_box_date_since"}
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
self.fields["time_until"] = forms.DateField(
|
|
92
|
+
label="Until",
|
|
93
|
+
initial=now().strftime("%Y-%m-%d"),
|
|
94
|
+
widget=forms.TextInput(
|
|
95
|
+
attrs={"type": "date", "class": "chart-input select_box_date_until"}
|
|
96
|
+
),
|
|
97
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
|
|
3
|
+
from blenderhub.apps.accounts.models import UserProfile
|
|
4
|
+
from datetime_truncate import truncate
|
|
5
|
+
from django.core.management import BaseCommand
|
|
6
|
+
|
|
7
|
+
from admin_tools_stats.models import (
|
|
8
|
+
DashboardStats,
|
|
9
|
+
Interval,
|
|
10
|
+
get_charts_timezone,
|
|
11
|
+
truncate_ceiling,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Command(BaseCommand):
|
|
16
|
+
help = "Recalculate all charts with cached values"
|
|
17
|
+
|
|
18
|
+
def add_arguments(self, parser):
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"graph_key",
|
|
21
|
+
nargs="*",
|
|
22
|
+
type=str,
|
|
23
|
+
help="Recalculate only specified charts",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--exclude",
|
|
27
|
+
help="Exclude graph keys from recalculation separated by comma",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--all-criteria",
|
|
31
|
+
help="Run for all multiseries criteria",
|
|
32
|
+
action="store_true",
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--reload-all",
|
|
36
|
+
help="Recalculate all data in range",
|
|
37
|
+
action="store_true",
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--dry-run",
|
|
41
|
+
help="Only print what would be recalculated",
|
|
42
|
+
action="store_true",
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--time-ranges",
|
|
46
|
+
help="Override time ranges (hours, days, months, quarters, years); Separated by comma",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--periods-count",
|
|
50
|
+
help="Incerease recalculation of charts by more of default periods",
|
|
51
|
+
default=1,
|
|
52
|
+
type=int,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def get_all_multiseries_criteria(self, stats, options):
|
|
56
|
+
all_multiseries_criteria = list(
|
|
57
|
+
stats.criteriatostatsm2m_set.filter(use_as="multiple_series", recalculate=True)
|
|
58
|
+
)
|
|
59
|
+
all_multiseries_criteria.append("")
|
|
60
|
+
return all_multiseries_criteria
|
|
61
|
+
|
|
62
|
+
def handle(self, *args, **options):
|
|
63
|
+
stats_query = DashboardStats.objects.filter(cache_values=True, show_to_users=False)
|
|
64
|
+
if options.get("graph_key", None):
|
|
65
|
+
stats_query = stats_query.filter(graph_key__in=options["graph_key"])
|
|
66
|
+
if options.get("exclude", None):
|
|
67
|
+
stats_query = stats_query.exclude(graph_key__in=options["exclude"].split(","))
|
|
68
|
+
|
|
69
|
+
chart_string = "\n".join(
|
|
70
|
+
f"\t{k.ljust(30)}\t{v}"
|
|
71
|
+
for (k, v) in stats_query.values_list("graph_key", "graph_title")
|
|
72
|
+
)
|
|
73
|
+
print(f"recalculating charts: \n{chart_string}")
|
|
74
|
+
for stats in stats_query:
|
|
75
|
+
operation = stats.type_operation_field_name
|
|
76
|
+
user = UserProfile.objects.get(email="petr.dlouhy@email.cz")
|
|
77
|
+
configuration = {
|
|
78
|
+
"select_box_chart_type": stats.default_chart_type,
|
|
79
|
+
"reload": "True",
|
|
80
|
+
"reload_all": str(options["reload_all"]),
|
|
81
|
+
}
|
|
82
|
+
for criteria in stats.criteriatostatsm2m_set.filter(
|
|
83
|
+
use_as="chart_filter",
|
|
84
|
+
criteria__dynamic_criteria_field_name__isnull=False,
|
|
85
|
+
):
|
|
86
|
+
configuration[f"select_box_dynamic_{criteria.id}"] = criteria.default_option
|
|
87
|
+
|
|
88
|
+
all_multiseries_criteria = self.get_all_multiseries_criteria(stats, options)
|
|
89
|
+
|
|
90
|
+
print(f"recalculating {stats} controls")
|
|
91
|
+
if not options["dry_run"]:
|
|
92
|
+
stats.get_control_form_raw(user=user)
|
|
93
|
+
|
|
94
|
+
for multiseries_criteria in all_multiseries_criteria:
|
|
95
|
+
if multiseries_criteria:
|
|
96
|
+
configuration["select_box_multiple_series"] = multiseries_criteria.id
|
|
97
|
+
|
|
98
|
+
if options.get("time_ranges", None):
|
|
99
|
+
time_scales = options["time_ranges"].split(",")
|
|
100
|
+
else:
|
|
101
|
+
time_scales = stats.allowed_time_scales
|
|
102
|
+
|
|
103
|
+
chart_tz = get_charts_timezone()
|
|
104
|
+
for operation_field in stats.operation_field_name.split(","):
|
|
105
|
+
for selected_interval in time_scales:
|
|
106
|
+
print(
|
|
107
|
+
f"recalculating chart {stats} with {multiseries_criteria} on "
|
|
108
|
+
f"{operation_field} criteria in {selected_interval}"
|
|
109
|
+
)
|
|
110
|
+
time_since = datetime.now() - timedelta(
|
|
111
|
+
days=stats.default_time_period
|
|
112
|
+
) * options.get("periods_count", 1)
|
|
113
|
+
time_since = truncate(time_since, Interval(selected_interval).val())
|
|
114
|
+
time_since = time_since.astimezone(chart_tz)
|
|
115
|
+
|
|
116
|
+
time_until = datetime.now()
|
|
117
|
+
time_until = truncate_ceiling(time_until, Interval(selected_interval).val())
|
|
118
|
+
time_until = time_until.astimezone(chart_tz)
|
|
119
|
+
|
|
120
|
+
configuration["select_box_interval"] = selected_interval
|
|
121
|
+
if not options["dry_run"]:
|
|
122
|
+
stats.get_multi_time_series_cached(
|
|
123
|
+
configuration=configuration,
|
|
124
|
+
time_since=time_since,
|
|
125
|
+
time_until=time_until,
|
|
126
|
+
interval=Interval(selected_interval),
|
|
127
|
+
operation_choice=operation,
|
|
128
|
+
operation_field_choice=operation_field,
|
|
129
|
+
user=user,
|
|
130
|
+
)
|