Refactor statistic page with class StatisticData

This commit is contained in:
Sokurov Idar 2021-03-09 00:53:11 +03:00
parent ce55ec61e6
commit 5efec2641a
3 changed files with 174 additions and 87 deletions

View File

@ -1,5 +1,5 @@
import os import os
from datetime import timedelta, datetime from datetime import timedelta, datetime, date
from django.contrib.auth.models import User from django.contrib.auth.models import User
from zenpy import Zenpy from zenpy import Zenpy
@ -203,58 +203,155 @@ def last_day_of_month(day):
return next_month - timedelta(days=next_month.day) return next_month - timedelta(days=next_month.day)
def get_statistic_from_data(data, start_date, end_date): class StatisticData:
def __init__(self, start_date, end_date, user_email):
self.errors = list()
self.data = None
self.statistic = dict()
self.start_date = start_date
self.end_date = end_date
self.email = user_email
self._set_data()
self._set_statistic()
def use_interval(self, interval):
""" """
Функция возвращает словарь, в котором ключ - дата, значение - кол-во проработанных в этот день секунд Объединяет ключи и значения в соответствии с интервалом работы
"""
if not self.is_valid_statistic():
return None
if not (interval in ['days', 'months']):
self.errors += ['Интервал работы должен быть в днях или месяцах']
return None
stat = {}
if interval == 'months':
# Переделываем ключи под формат, как в прототипе('началоесяца - конец_месяца')
for key, value in self.get_statistic().items():
current_month_start = max(self.start_date, date(year=key.year, month=key.month, day=1))
current_month_end = min(self.end_date, last_day_of_month(date(year=key.year, month=key.month, day=1)))
index = ' - '.join([str(current_month_start), str(current_month_end)])
if stat.get(index):
stat[index] += value
else:
stat[index] = value
elif interval == 'days':
stat = self.get_statistic()
return stat
def use_display(self, display_format):
"""
Приводит данные к формату отображения, возвращает их
"""
if not self.get_statistic():
return None
if not (display_format in ['hours', 'days']):
self.errors += ['Формат отображения должен быть в часах или днях']
return None
for key, item in self.statistic.items():
if display_format == 'hours':
self.statistic[key] = item / 3600
elif display_format == 'days':
self.statistic[key] = item / 86400
return self.statistic.copy()
def pop_errors(self):
"""
Возвращает все текущие ошибки
"""
errors = self.errors.copy()
self.errors.clear()
return errors
def get_data(self):
"""
Вернуть данные
data - массив объектов RoleChangeLogs, является списком логов пользователя data - массив объектов RoleChangeLogs, является списком логов пользователя
""" """
if not data: if self.is_valid_data():
return self.data
else:
return None return None
# Обнуление всех дней
stat = {} def get_statistic(self):
for day in daterange(start_date, end_date + timedelta(days=1)): """
stat[day] = 0 Вернуть словарь statistic или None, если были ошибки при создании
first_log, last_log = data[0], data[len(data) - 1] """
if self.is_valid_statistic():
return self.statistic.copy()
else:
return None
def is_valid_statistic(self):
"""
Были ли ошибки при создании статистики
"""
return not self.errors and self.statistic
def is_valid_data(self):
"""
Были ли ошибки при создании объекта
"""
return not self.errors
def _set_data(self):
"""
Получение списка из лог-ов в диапазоне дат start_date-end_date для пользователя с почтой email
"""
try:
self.data = RoleChangeLogs.objects.filter(
change_time__range=[self.start_date, self.end_date + timedelta(days=1)],
user=User.objects.get(email=self.email),
).order_by('change_time')
self.errors.clear()
except User.DoesNotExist:
self.errors += ['Пользователь не найден']
def _set_statistic(self):
"""
Функция заполняет словарь, в котором ключ - дата, значение - кол-во проработанных в этот день секунд
"""
if not self.get_data():
self.errors += ['Не обнаружены изменения роли в данном промежутке']
return None
self.clear_statistic()
first_log, last_log = self.data[0], self.data[len(self.data) - 1]
# Если инженер работал ещё до начала диапазона # Если инженер работал ещё до начала диапазона
if int(first_log.old_role) == ROLES['engineer']: if int(first_log.old_role) == ROLES['engineer']:
for day in daterange(start_date, first_log.change_time.date()): self.fill_daterange(self.start_date, first_log.change_time.date())
stat[day] = 24 * 3600 self.statistic[first_log.change_time.date()] += get_timedelta(first_log).total_seconds()
stat[first_log.change_time.date()] += get_timedelta(first_log).total_seconds()
# Если инженер закончил работать после диапазона # Если инженер закончил работать после диапазона
if int(last_log.new_role) == ROLES['engineer']: if int(last_log.new_role) == ROLES['engineer']:
for day in daterange(last_log.change_time.date() + timedelta(days=1), end_date + timedelta(days=1)): self.fill_daterange(last_log.change_time.date() + timedelta(days=1), self.end_date + timedelta(days=1))
stat[day] = 24 * 3600 self.statistic[last_log.change_time.date()] += (timedelta(days=1) - get_timedelta(last_log)).total_seconds()
stat[last_log.change_time.date()] += (timedelta(days=1) - get_timedelta(last_log)).total_seconds()
# Цикл по логам # Цикл по логам
for log_index in range(len(data) - 1): for log_index in range(len(self.data) - 1):
if int(data[log_index].new_role) == ROLES['engineer']: if int(self.data[log_index].new_role) == ROLES['engineer']:
current_log, next_log = data[log_index], data[log_index + 1] current_log, next_log = self.data[log_index], self.data[log_index + 1]
# Если сессия закончилась НЕ в тот же день, что и началась # Если сессия закончилась НЕ в тот же день, что и началась
if current_log.change_time.date() != next_log.change_time.date(): if current_log.change_time.date() != next_log.change_time.date():
stat[current_log.change_time.date()] += (timedelta(days=1) - get_timedelta(current_log)).total_seconds() self.statistic[current_log.change_time.date()] += (
stat[next_log.change_time.date()] += get_timedelta(next_log).total_seconds() timedelta(days=1) - get_timedelta(current_log)).total_seconds()
self.statistic[next_log.change_time.date()] += get_timedelta(next_log).total_seconds()
# Если проработал несколько дней подряд, то заполнить эти дни по 24 часа # Если проработал несколько дней подряд, то заполнить эти дни по 24 часа
for day in daterange(current_log.change_time.date() + timedelta(days=1), next_log.change_time.date()): self.fill_daterange(current_log.change_time.date() + timedelta(days=1), next_log.change_time.date())
stat[day] = 24 * 3600
# Если сессия закончилась в тот же день, что и началась # Если сессия закончилась в тот же день, что и началась
else: else:
elapsed_time = next_log.change_time - current_log.change_time elapsed_time = next_log.change_time - current_log.change_time
stat[current_log.change_time.date()] += elapsed_time.total_seconds() self.statistic[current_log.change_time.date()] += elapsed_time.total_seconds()
return stat
def fill_daterange(self, first, last, val=24 * 3600):
"""
Заполение диапазона дат значением val, по умолчанию val = кол-во секунд в 1 дне
"""
for day in daterange(first, last):
self.statistic[day] = val
def get_data_logs(context, start_date, end_date, email): def clear_statistic(self):
""" """
Функция возвращает список из лог-ов в диапазоне дат start_date-end_date для пользователя с почтой email Обнуление всех дней
""" """
data = [] self.fill_daterange(self.start_date, self.end_date + timedelta(days=1), 0)
try:
data = RoleChangeLogs.objects.filter(
change_time__range=[start_date, end_date + timedelta(days=1)],
user=User.objects.get(email=email),
).order_by('change_time')
except User.DoesNotExist:
context['errors'] = ['Пользователь не найден']
return data

View File

@ -31,6 +31,15 @@
<li>{{error}}</li> <li>{{error}}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{%if form.errors%}
<ul>
{% for field, errors in form.errors.items %}
{% for error in errors %}
<li>{{error}}</li>
{% endfor %}
{% endfor %}
</ul>
{%endif%}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,6 +1,6 @@
import logging import logging
import os import os
from datetime import date, datetime from datetime import datetime
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordResetForm from django.contrib.auth.forms import PasswordResetForm
@ -21,7 +21,7 @@ from zenpy.lib.api_objects import User as ZenpyUser
from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES from access_controller.settings import EMAIL_HOST_USER, ZENDESK_ROLES
from main.extra_func import check_user_exist, update_profile, get_user_organization, make_engineer, make_light_agent, \ from main.extra_func import check_user_exist, update_profile, get_user_organization, make_engineer, make_light_agent, \
get_users_list, last_day_of_month, get_statistic_from_data, get_data_logs get_users_list, StatisticData
from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, StatisticForm
from .models import UserProfile from .models import UserProfile
@ -247,42 +247,23 @@ def statistic_page(request):
} }
if request.method == "POST": if request.method == "POST":
form = StatisticForm(request.POST) form = StatisticForm(request.POST)
if not form.is_valid(): if form.is_valid():
context['errors'] += form.errors start_date, end_date, stats = form.cleaned_data['range_start'], form.cleaned_data['range_end'], dict()
else: interval, show = form.cleaned_data['interval'], form.cleaned_data['display_format']
start_date, end_date, data = form.cleaned_data['range_start'], form.cleaned_data['range_end'], list()
if end_date < start_date or end_date > datetime.now().date(): if end_date < start_date or end_date > datetime.now().date():
context['errors'] += ['Конец диапазона должен быть позже начала диапазона и раньше текущего времени'] context['errors'] += ['Конец диапазона должен быть позже начала диапазона и раньше текущего времени']
else: else:
data = get_data_logs(context, start_date, end_date, form.cleaned_data['email']) Data = StatisticData(start_date, end_date, form.cleaned_data['email'])
stats = get_statistic_from_data(data, start_date, end_date) stats = Data.get_statistic()
if stats is None: if Data.errors:
context['errors'] += ['Не найдено изменений роли в указаном диапазоне'] context['errors'] += Data.pop_errors()
interval, show = form.cleaned_data['interval'], form.cleaned_data['display_format']
if not (show in ['hours', 'days']): # Работа с форматом отображения
context['errors'] += ['Формат отображения должен быть в часах или днях']
elif stats:
for key, item in stats.items():
if show == 'hours':
stats[key] = item / 3600
elif show == 'days':
stats[key] = item / 86400
if not (interval in ['days', 'months']): # Работа с интервалом работы
context['errors'] += ['Интервал работы должен быть в днях или месяцах']
elif interval == 'months' and stats: # Переделываем ключи под формат в прототипе(началоесяцаонец_месяца)
new_stats = {}
for key, value in stats.items():
current_month_start = max(start_date, date(year=key.year, month=key.month, day=1))
current_month_end = min(end_date, last_day_of_month(date(year=key.year, month=key.month, day=1)))
index = ' - '.join([str(current_month_start), str(current_month_end)])
if new_stats.get(index):
new_stats[index] += value
else: else:
new_stats[index] = value stats = Data.use_display(show)
stats = new_stats if stats is None:
context['errors'] += Data.pop_errors()
stats = Data.use_interval(interval)
if stats is None:
context['errors'] += Data.pop_errors()
context['log_stats'] = stats if not context['errors'] else None context['log_stats'] = stats if not context['errors'] else None
if request.method == 'GET': if request.method == 'GET':
form = StatisticForm() form = StatisticForm()