def __init__(self, board, post_data=None, initial=None): super(WeekSummaryFilterForm, self).__init__(data=post_data, initial=initial) now = timezone.now() year = now.year working_start_date = board.get_working_start_date() working_end_date = board.get_working_end_date() if working_start_date and working_end_date: self.fields["year"].choices = [(year_i, year_i) for year_i in range(working_start_date.year, working_end_date.year+1)] if working_start_date.year == working_end_date.year: start_week = get_iso_week_of_year(working_start_date) end_week = get_iso_week_of_year(working_end_date) self.fields["week"].choices = [(week_i, week_i) for week_i in range(start_week, end_week+1)] else: self.fields["week"].choices = [(week_i, week_i) for week_i in range(1, 53 + 1)] else: self.fields["year"].choices = [(year_i, year_i) for year_i in range(year-100, year+101)] self.fields["week"].choices = [(week_i, week_i) for week_i in range(1, 53+1)] self.fields["member"].choices = [("all", "All")] +\ [(member.id, member.external_username) for member in board.members.filter(is_developer=True)] if initial is None: self.initial["year"] = year self.initial["week"] = get_iso_week_of_year(now) self.initial["member"] = "all"
def spent_time_by_day_of_the_week(request, member_id=None, week_of_year=None, board_id=None): user_boards = get_user_boards(request.user) if member_id is None: if user_is_member(request.user): member = request.user.member else: member = Member.objects.filter(boards__in=user_boards)[0] else: member = Member.objects.filter(boards__in=user_boards).distinct().get( id=member_id) if week_of_year is None: now = timezone.now() today = now.date() week_of_year_ = get_iso_week_of_year(today) week_of_year = "{0}W{1}".format(today.year, week_of_year_) y, w = week_of_year.split("W") week = Week(int(y), int(w)) start_of_week = week.monday() end_of_week = week.sunday() chart_title = u"{0}'s spent time in week {1} ({2} - {3})".format( member.external_username, week_of_year, start_of_week.strftime("%Y-%m-%d"), end_of_week.strftime("%Y-%m-%d")) board = None if board_id: board = user_boards.get(id=board_id) chart_title += u" for board {0}".format(board.name) spent_time_chart = pygal.HorizontalBar(title=chart_title, legend_at_bottom=True, print_values=True, print_zeroes=False, human_readable=True) try: day = start_of_week while day <= end_of_week: member_spent_time = member.get_spent_time(day, board) spent_time_chart.add(u"{0}".format(day.strftime("%A")), member_spent_time) day += datetime.timedelta(days=1) except AssertionError: spent_time_chart.no_data_text = u"No developers for this board.\nCheck members' attributes." spent_time_chart.style = DefaultStyle(no_data_font_size=20) return spent_time_chart.render_django_response() return spent_time_chart.render_django_response()
def add(board, member, date, card, comment, description, spent_time, estimated_time): # In case a uuid is passed, load the Member object if type(member) is str or type(member) is unicode: try: member = board.members.get(uuid=member) except ObjectDoesNotExist: return False weekday = date.strftime("%w") week_of_year = get_iso_week_of_year(date) day_of_year = date.strftime("%j") adjusted_spent_time = None # Convert spent_time to Decimal if is a number if spent_time is not None: spent_time = Decimal(spent_time) adjusted_spent_time = member.adjust_spent_time(spent_time, date) # Convert estimated_time to Decimal if is a number if estimated_time is not None: estimated_time = Decimal(estimated_time) elif spent_time is not None: estimated_time = Decimal(spent_time) # Compute difference between spent_time and estimated_time diff_time = None if spent_time is not None and estimated_time is not None: diff_time = Decimal(estimated_time) - Decimal(spent_time) # Creation of daily_spent_time daily_spent_time = DailySpentTime(board=board, member=member, card=card, comment=comment, uuid=comment.uuid, description=description, adjusted_spent_time=adjusted_spent_time, spent_time=spent_time, estimated_time=estimated_time, diff_time=diff_time, date=date, day_of_year=day_of_year, week_of_year=week_of_year, weekday=weekday) # Rate amount computation hourly_rate = board.get_date_hourly_rate(date) if hourly_rate: if daily_spent_time.rate_amount is None: daily_spent_time.rate_amount = 0 daily_spent_time.rate_amount += hourly_rate.amount # Saving the daily spent time daily_spent_time.save() return spent_time
def send_report(self, daily_spent_times, report_recipient, subject, txt_template_path, html_template_path, csv_file_name): week = get_iso_week_of_year(self.date) start_week_date = start_of_week_of_year(week, self.date.year) end_week_date = end_of_week_of_year(week, self.date.year) replacements = { "start_week_date": start_week_date, "end_week_date": end_week_date, "date": self.date, "day": self.date.day, "week": week, "month": self.date.month, "year": self.date.year, "report_recipient": report_recipient, "boards": report_recipient.boards.all(), "developer_members": Member.objects.filter( is_developer=True, on_holidays=False).order_by("trello_member_profile__username"), "daily_spent_times": daily_spent_times, } txt_message = get_template(txt_template_path).render(replacements) html_message = get_template(html_template_path).render(replacements) csv_report = get_template('daily_spent_times/csv.txt').render( {"spent_times": daily_spent_times}) message = EmailMultiAlternatives(subject, txt_message, settings.EMAIL_HOST_USER, [report_recipient.email]) message.attach_alternative(html_message, "text/html") message.attach(csv_file_name, csv_report, 'text/csv') message.send()
def factory_from_comment(comment): card = comment.card board = card.board spent_estimated_time = comment.get_spent_estimated_time_from_content() date = spent_estimated_time["date"] weekday = date.strftime("%w") week_of_year = get_iso_week_of_year(date) day_of_year = date.strftime("%j") spent_time = spent_estimated_time["spent_time"] estimated_time = spent_estimated_time["estimated_time"] # Set adjusted spent time adjusted_spent_time = None if spent_time is not None: spent_time = Decimal(spent_time) adjusted_spent_time = comment.author.adjust_spent_time(spent_time, date) # Convert estimated_time to Decimal if is a number if estimated_time is not None: estimated_time = Decimal(estimated_time) elif spent_time is not None: estimated_time = Decimal(spent_time) diff_time = estimated_time - spent_time rate_amount = None hourly_rate = board.get_date_hourly_rate(date) if spent_time is not None and hourly_rate is not None: rate_amount = spent_time * hourly_rate.amount daily_spent_time = DailySpentTime( uuid=comment.uuid, board=board, card=card, comment=comment, date=spent_estimated_time["date"], weekday=weekday, week_of_year=week_of_year, day_of_year=day_of_year, spent_time=spent_time, adjusted_spent_time=adjusted_spent_time, estimated_time=estimated_time, diff_time=diff_time, description=spent_estimated_time["description"], member=comment.author, rate_amount=rate_amount ) return daily_spent_time
def handle(self, *args, **options): self.date = super(Command, self).handle(*args, **options) date_help_text = 'Send the weekly report to the administrators for the week this date belongs to' start = time.time() week = get_iso_week_of_year(self.date) year = self.date.year week_start_date = Week(year, week).monday() week_end_date = Week(year, week).friday() daily_spent_times = DailySpentTime.objects.filter(date__gte=week_start_date, date__lte=week_end_date).\ order_by("date", "member") subject = "[Djanban][Reports] Weekly report of {0}/W{1}".format( year, week) txt_template_path = 'reporter/emails/weekly_report.txt' html_template_path = 'reporter/emails/weekly_report.html' csv_file_name = 'spent_times-for-month-{0}W{1}.csv'.format(year, week) report_recipient = self.send_reports(daily_spent_times, subject, txt_template_path, html_template_path, csv_file_name) self.stdout.write( self.style.SUCCESS( u"Weekly reports sent to {0} administrators".format( report_recipient.count()))) end = time.time() elapsed_time = end - start self.stdout.write( self.style.SUCCESS( u"Weekly reports for week {0}/W{1} sent successfully to {2} in {3} s" .format(year, week, report_recipient.count(), elapsed_time)))
def _daily_spent_times_by_period(current_user, board=None, time_measurement="spent_time", operation="Avg", period="month"): # Caching chart_uuid = "labels._daily_spent_times_by_period-{0}-{1}-{2}-{3}".format( board.id if board else "user-{0}".format(current_user.id), time_measurement, operation, period) chart = CachedChart.get(board=board, uuid=chart_uuid) if chart: return chart daily_spent_time_filter = {"{0}__gt".format(time_measurement): 0} last_activity_datetime = timezone.now() if board: last_activity_datetime = board.last_activity_datetime daily_spent_time_filter["board"] = board if operation == "Avg": chart_title = u"Task average {1} as of {0}".format( last_activity_datetime, time_measurement.replace("_", " ")) if board: chart_title += u" for board {0} (fetched on {1})".format( board.name, board.get_human_fetch_datetime()) elif operation == "Count": chart_title = u"Tasks worked on as of {0}".format( last_activity_datetime) if board: chart_title += u" for board {0} (fetched on {1})".format( board.name, board.get_human_fetch_datetime()) else: raise ValueError( u"Operation not valid only Avg and Count values are valid") period_measurement_chart = pygal.StackedBar(title=chart_title, legend_at_bottom=True, print_values=True, print_zeroes=False, x_label_rotation=45, human_readable=True) labels = [] if board: labels = board.labels.all() end_date = DailySpentTime.objects.filter( **daily_spent_time_filter).aggregate(max_date=Max("date"))["max_date"] date_i = DailySpentTime.objects.filter( **daily_spent_time_filter).aggregate(min_date=Min("date"))["min_date"] if date_i is None or end_date is None: return period_measurement_chart.render_django_response() month_i = date_i.month week_i = get_iso_week_of_year(date_i) year_i = date_i.year if operation == "Avg": aggregation = Avg elif operation == "Count": aggregation = Count else: ValueError(u"Operation not valid only Avg and Count values are valid") measurement_titles = [] measurement_values = [] label_measurement_titles = {label.id: [] for label in labels} label_measurement_values = {label.id: [] for label in labels} end_loop = False while not end_loop: if period == "month": period_filter = {"date__month": month_i, "date__year": year_i} measurement_title = u"{0}-{1}".format(year_i, month_i) label_measurement_title_suffix = u"{0}-{1}".format(year_i, month_i) end_loop = datetime.datetime.strptime( '{0}-{1}-1'.format(year_i, month_i), '%Y-%m-%d').date() > end_date elif period == "week": period_filter = {"week_of_year": week_i, "date__year": year_i} measurement_title = u"{0}W{1}".format(year_i, week_i) label_measurement_title_suffix = u"{0}W{1}".format(year_i, week_i) end_loop = start_of_week_of_year(week=week_i, year=year_i) > end_date else: raise ValueError( u"Period {0} not valid. Only 'month' or 'week' is valid". format(period)) period_times = DailySpentTime.objects.filter(**daily_spent_time_filter).\ filter(**period_filter) period_measurement = period_times.aggregate( measurement=aggregation(time_measurement))["measurement"] # For each month that have some data, add it to the chart if period_measurement is not None and period_measurement > 0: measurement_titles.append(measurement_title) measurement_values.append(period_measurement) # For each label that has a name (i.e. it is being used) and has a value, store its measurement per label for label in labels: if label.name: label_measurement = period_times.filter(card__labels=label).\ aggregate(measurement=aggregation(time_measurement))["measurement"] if label_measurement: label_measurement_titles[label.id].append( measurement_title) label_measurement_values[label.id].append( label_measurement) if period == "month": month_i += 1 if month_i > 12: month_i = 1 year_i += 1 elif period == "week": week_i += 1 if week_i > number_of_weeks_of_year(year_i): week_i = 1 year_i += 1 # Weeks there is any measurement period_measurement_chart.x_labels = measurement_titles period_measurement_chart.add("Tasks", measurement_values) # For each label that has any measurement, add its measurement to the chart for label in labels: if sum(label_measurement_values[label.id]) > 0: period_measurement_chart.add(label.name, label_measurement_values[label.id]) chart = CachedChart.make( board=board, uuid=chart_uuid, svg=period_measurement_chart.render(is_unicode=True)) return chart.render_django_response()
def view_week_summary(request, board_id, member_id="all", week_of_year=None): current_member = None if user_is_member(request.user): current_member = request.user.member board = get_user_board_or_404(request.user, board_id) replacements = {"board": board, "member": current_member} if request.method == "POST": form = WeekSummaryFilterForm(post_data=request.POST, board=board) if form.is_valid(): year = form.cleaned_data.get("year") week = form.cleaned_data.get("week") member_id = form.cleaned_data.get("member") week_of_year = "{0}W{1}".format(year, week) return HttpResponseRedirect(reverse("boards:view_week_summary", args=(board_id, member_id, week_of_year,))) year = None week = None if week_of_year is None: now = timezone.now() year = now.year week = int(get_iso_week_of_year(now)) week_of_year = "{0}W{1}".format(year, week) if week is None or year is None: matches = re.match(r"^(?P<year>\d{4})W(?P<week>\d{2})$", week_of_year) if matches: year = int(matches.group("year")) week = int(matches.group("week")) form = WeekSummaryFilterForm(initial={"year": year, "week": week, "member": member_id}, board=board) replacements["form"] = form replacements["week_of_year"] = week_of_year # Done cards that are of this member member_filter = {} if member_id != "all": member_filter = {"members": member_id} # Date limits of the selected week week_start_date = Week(year, week).monday() week_end_date = Week(year, week).friday() # Getting the cards that were completed in the selected week for the selected user completed_cards = board.cards.\ filter(list__type="done", movements__type="forward", movements__destination_list__type="done", movements__datetime__gte=week_start_date, movements__datetime__lte=week_end_date)\ .filter(**member_filter).\ order_by("last_activity_datetime") replacements["completed_cards"] = completed_cards # Time spent developing on this week if member_id == "all": spent_time = board.get_spent_time([week_start_date, week_end_date]) adjusted_spent_time = board.get_spent_time([week_start_date, week_end_date]) else: member = board.members.get(id=member_id) spent_time = board.get_spent_time([week_start_date, week_end_date], member) adjusted_spent_time = board.get_spent_time([week_start_date, week_end_date], member) replacements["selected_member"] = member replacements["spent_time"] = spent_time replacements["adjusted_spent_time"] = adjusted_spent_time replacements["week_start_date"] = week_start_date replacements["week_end_date"] = week_end_date return render(request, "boards/week_summary.html", replacements)
def spent_time_by_week(current_user, week_of_year=None, board=None): if week_of_year is None: now = timezone.now() today = now.date() week_of_year_ = get_iso_week_of_year(today) week_of_year = "{0}W{1}".format(today.year, week_of_year_) # Caching chart_uuid = "members.spent_time_by_week-{0}-{1}".format( current_user.id, week_of_year, board.id if board else "user-{0}".format(current_user.id)) chart = CachedChart.get(board=board, uuid=chart_uuid) if chart: return chart y, w = week_of_year.split("W") week = Week(int(y), int(w)) start_of_week = week.monday() end_of_week = week.sunday() chart_title = u"Spent time in week {0} ({1} - {2})".format( week_of_year, start_of_week.strftime("%Y-%m-%d"), end_of_week.strftime("%Y-%m-%d")) if board: chart_title += u" for board {0}".format(board.name) spent_time_chart = pygal.HorizontalBar(title=chart_title, legend_at_bottom=True, print_values=True, print_zeroes=False, human_readable=True) report_filter = {"date__year": y, "week_of_year": w} if board: report_filter["board_id"] = board.id team_spent_time = 0 if board is None: boards = get_user_boards(current_user) else: boards = [board] members = Member.objects.filter( boards__in=boards, is_developer=True).distinct().order_by("id") for member in members: member_name = member.external_username daily_spent_times = member.daily_spent_times.filter(**report_filter) spent_time = daily_spent_times.aggregate( Sum("spent_time"))["spent_time__sum"] if spent_time is None: spent_time = 0 team_spent_time += spent_time if spent_time > 0: spent_time_chart.add(u"{0}'s spent time".format(member_name), spent_time) spent_time_chart.add(u"Team spent time", team_spent_time) chart = CachedChart.make(board=board, uuid=chart_uuid, svg=spent_time_chart.render(is_unicode=True)) return chart.render_django_response()
def spent_time_by_week_evolution(board, show_interruptions=False): # Caching chart_uuid = "members.spent_time_by_week_evolution-{0}-{1}".format( board.id, "with_interruptions" if show_interruptions else "without_interruptions") chart = CachedChart.get(board=board, uuid=chart_uuid) if chart: return chart chart_title = u"Evolution of each member's spent time by week" if show_interruptions: chart_title += u", including interruptions suffered by the team, " chart_title += u" for board {0} (fetched on {1})".format( board.name, board.get_human_fetch_datetime()) evolution_chart = pygal.Line(title=chart_title, legend_at_bottom=True, print_values=False, print_zeroes=False, fill=False, human_readable=True, x_label_rotation=45) start_working_date = board.get_working_start_date() end_working_date = board.get_working_end_date() if start_working_date is None or end_working_date is None: return evolution_chart.render_django_response() start_week = get_iso_week_of_year(start_working_date) end_week = get_iso_week_of_year(end_working_date) members = board.members.filter(is_developer=True).order_by("id") member_values = {member.id: [] for member in members} member_values["all"] = [] interruptions = [] x_labels = [] week_i = copy.deepcopy(start_week) year_i = start_working_date.year while year_i < end_working_date.year or (year_i == end_working_date.year and week_i < end_week): week_i_start_date = Week(year_i, week_i).monday() week_i_end_date = Week(year_i, week_i).sunday() there_is_data = board.daily_spent_times.filter( date__year=year_i, week_of_year=week_i).exists() if there_is_data: x_labels.append(u"{0}W{1}".format(year_i, week_i)) team_spent_time = 0 for member in members: daily_spent_times = member.daily_spent_times.filter( board=board, date__year=year_i, week_of_year=week_i) spent_time = daily_spent_times.aggregate( Sum("spent_time"))["spent_time__sum"] if spent_time is None: spent_time = 0 team_spent_time += spent_time if spent_time > 0: member_values[member.id].append(spent_time) member_values["all"].append(team_spent_time) if show_interruptions: num_interruptions = Interruption.objects.filter( datetime__year=year_i, datetime__date__gte=week_i_start_date, datetime__date__lte=week_i_end_date).count() if num_interruptions > 0: interruptions.append(num_interruptions) week_i += 1 if week_i > number_of_weeks_of_year(year_i): week_i = 1 year_i += 1 evolution_chart.x_labels = x_labels for member in members: evolution_chart.add(member.external_username, member_values[member.id]) evolution_chart.add("All members", member_values["all"]) if show_interruptions: evolution_chart.add("Interruptions", interruptions) chart = CachedChart.make(board=board, uuid=chart_uuid, svg=evolution_chart.render(is_unicode=True)) return chart.render_django_response()