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()
data - массив объектов RoleChangeLogs, является списком логов пользователя self.data = None
""" self.statistic = dict()
if not data: self.start_date = start_date
return None self.end_date = end_date
# Обнуление всех дней self.email = user_email
stat = {} self._set_data()
for day in daterange(start_date, end_date + timedelta(days=1)): self._set_statistic()
stat[day] = 0
first_log, last_log = data[0], data[len(data) - 1]
# Если инженер работал ещё до начала диапазона def use_interval(self, interval):
if int(first_log.old_role) == ROLES['engineer']: """
for day in daterange(start_date, first_log.change_time.date()): Объединяет ключи и значения в соответствии с интервалом работы
stat[day] = 24 * 3600 """
stat[first_log.change_time.date()] += get_timedelta(first_log).total_seconds() 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 int(last_log.new_role) == ROLES['engineer']: """
for day in daterange(last_log.change_time.date() + timedelta(days=1), end_date + timedelta(days=1)): Приводит данные к формату отображения, возвращает их
stat[day] = 24 * 3600 """
stat[last_log.change_time.date()] += (timedelta(days=1) - get_timedelta(last_log)).total_seconds() if not self.get_statistic():
# Цикл по логам return None
for log_index in range(len(data) - 1): if not (display_format in ['hours', 'days']):
if int(data[log_index].new_role) == ROLES['engineer']: self.errors += ['Формат отображения должен быть в часах или днях']
current_log, next_log = data[log_index], data[log_index + 1] return None
# Если сессия закончилась НЕ в тот же день, что и началась for key, item in self.statistic.items():
if current_log.change_time.date() != next_log.change_time.date(): if display_format == 'hours':
stat[current_log.change_time.date()] += (timedelta(days=1) - get_timedelta(current_log)).total_seconds() self.statistic[key] = item / 3600
stat[next_log.change_time.date()] += get_timedelta(next_log).total_seconds() elif display_format == 'days':
# Если проработал несколько дней подряд, то заполнить эти дни по 24 часа self.statistic[key] = item / 86400
for day in daterange(current_log.change_time.date() + timedelta(days=1), next_log.change_time.date()): return self.statistic.copy()
stat[day] = 24 * 3600
# Если сессия закончилась в тот же день, что и началась
else:
elapsed_time = next_log.change_time - current_log.change_time
stat[current_log.change_time.date()] += elapsed_time.total_seconds()
return stat
def pop_errors(self):
"""
Возвращает все текущие ошибки
"""
errors = self.errors.copy()
self.errors.clear()
return errors
def get_data_logs(context, start_date, end_date, email): def get_data(self):
""" """
Функция возвращает список из лог-ов в диапазоне дат start_date-end_date для пользователя с почтой email Вернуть данные
""" data - массив объектов RoleChangeLogs, является списком логов пользователя
data = [] """
try: if self.is_valid_data():
data = RoleChangeLogs.objects.filter( return self.data
change_time__range=[start_date, end_date + timedelta(days=1)], else:
user=User.objects.get(email=email), return None
).order_by('change_time')
except User.DoesNotExist: def get_statistic(self):
context['errors'] = ['Пользователь не найден'] """
return data Вернуть словарь statistic или None, если были ошибки при создании
"""
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']:
self.fill_daterange(self.start_date, first_log.change_time.date())
self.statistic[first_log.change_time.date()] += get_timedelta(first_log).total_seconds()
# Если инженер закончил работать после диапазона
if int(last_log.new_role) == ROLES['engineer']:
self.fill_daterange(last_log.change_time.date() + timedelta(days=1), self.end_date + timedelta(days=1))
self.statistic[last_log.change_time.date()] += (timedelta(days=1) - get_timedelta(last_log)).total_seconds()
# Цикл по логам
for log_index in range(len(self.data) - 1):
if int(self.data[log_index].new_role) == ROLES['engineer']:
current_log, next_log = self.data[log_index], self.data[log_index + 1]
# Если сессия закончилась НЕ в тот же день, что и началась
if current_log.change_time.date() != next_log.change_time.date():
self.statistic[current_log.change_time.date()] += (
timedelta(days=1) - get_timedelta(current_log)).total_seconds()
self.statistic[next_log.change_time.date()] += get_timedelta(next_log).total_seconds()
# Если проработал несколько дней подряд, то заполнить эти дни по 24 часа
self.fill_daterange(current_log.change_time.date() + timedelta(days=1), next_log.change_time.date())
# Если сессия закончилась в тот же день, что и началась
else:
elapsed_time = next_log.change_time - current_log.change_time
self.statistic[current_log.change_time.date()] += elapsed_time.total_seconds()
def fill_daterange(self, first, last, val=24 * 3600):
"""
Заполение диапазона дат значением val, по умолчанию val = кол-во секунд в 1 дне
"""
for day in daterange(first, last):
self.statistic[day] = val
def clear_statistic(self):
"""
Обнуление всех дней
"""
self.fill_daterange(self.start_date, self.end_date + timedelta(days=1), 0)

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'] else:
stats = Data.use_display(show)
if not (show in ['hours', 'days']): # Работа с форматом отображения if stats is None:
context['errors'] += ['Формат отображения должен быть в часах или днях'] context['errors'] += Data.pop_errors()
elif stats: stats = Data.use_interval(interval)
for key, item in stats.items(): if stats is None:
if show == 'hours': context['errors'] += Data.pop_errors()
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:
new_stats[index] = value
stats = new_stats
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()