diff --git a/main/extra_func.py b/main/extra_func.py index 9fb141d..c47e6a1 100644 --- a/main/extra_func.py +++ b/main/extra_func.py @@ -1,5 +1,5 @@ import os -from datetime import timedelta, datetime +from datetime import timedelta, datetime, date from django.contrib.auth.models import User from zenpy import Zenpy @@ -203,58 +203,155 @@ def last_day_of_month(day): return next_month - timedelta(days=next_month.day) -def get_statistic_from_data(data, start_date, end_date): - """ - Функция возвращает словарь, в котором ключ - дата, значение - кол-во проработанных в этот день секунд - data - массив объектов RoleChangeLogs, является списком логов пользователя - """ - if not data: - return None - # Обнуление всех дней - stat = {} - for day in daterange(start_date, end_date + timedelta(days=1)): - stat[day] = 0 - first_log, last_log = data[0], data[len(data) - 1] +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() - # Если инженер работал ещё до начала диапазона - 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() + 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 - # Если инженер закончил работать после диапазона - 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() - # Цикл по логам - for log_index in range(len(data) - 1): - if int(data[log_index].new_role) == ROLES['engineer']: - current_log, next_log = data[log_index], data[log_index + 1] - # Если сессия закончилась НЕ в тот же день, что и началась - 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() - stat[next_log.change_time.date()] += get_timedelta(next_log).total_seconds() - # Если проработал несколько дней подряд, то заполнить эти дни по 24 часа - for day in daterange(current_log.change_time.date() + timedelta(days=1), next_log.change_time.date()): - 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 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_logs(context, start_date, end_date, email): - """ - Функция возвращает список из лог-ов в диапазоне дат start_date-end_date для пользователя с почтой email - """ - data = [] - 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 + def get_data(self): + """ + Вернуть данные + data - массив объектов RoleChangeLogs, является списком логов пользователя + """ + if self.is_valid_data(): + return self.data + else: + return None + + def get_statistic(self): + """ + Вернуть словарь 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) diff --git a/main/templates/pages/stat.html b/main/templates/pages/stat.html index 2409e0f..69afbde 100644 --- a/main/templates/pages/stat.html +++ b/main/templates/pages/stat.html @@ -31,6 +31,15 @@
  • {{error}}
  • {% endfor %} + {%if form.errors%} + + {%endif%} {% endblock %} diff --git a/main/views.py b/main/views.py index b2d4fc0..bb9434f 100644 --- a/main/views.py +++ b/main/views.py @@ -1,6 +1,6 @@ import logging import os -from datetime import date, datetime +from datetime import datetime from django.contrib.auth.decorators import login_required 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 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 .models import UserProfile @@ -247,42 +247,23 @@ def statistic_page(request): } if request.method == "POST": form = StatisticForm(request.POST) - if not form.is_valid(): - context['errors'] += form.errors - else: - start_date, end_date, data = form.cleaned_data['range_start'], form.cleaned_data['range_end'], list() + if form.is_valid(): + start_date, end_date, stats = form.cleaned_data['range_start'], form.cleaned_data['range_end'], dict() + interval, show = form.cleaned_data['interval'], form.cleaned_data['display_format'] if end_date < start_date or end_date > datetime.now().date(): context['errors'] += ['Конец диапазона должен быть позже начала диапазона и раньше текущего времени'] else: - data = get_data_logs(context, start_date, end_date, form.cleaned_data['email']) - stats = get_statistic_from_data(data, start_date, end_date) - if stats is None: - context['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: - new_stats[index] = value - stats = new_stats - + Data = StatisticData(start_date, end_date, form.cleaned_data['email']) + stats = Data.get_statistic() + if Data.errors: + context['errors'] += Data.pop_errors() + else: + stats = Data.use_display(show) + 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 if request.method == 'GET': form = StatisticForm()