From: Emilie Zawadzki Date: Wed, 18 Jan 2017 13:26:03 +0000 (+0100) Subject: [Timesheet] : dashboard + style X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=89520d38e5c1ca1ea8e42b090e4a8cde62ed3fa3;p=mezzo.git [Timesheet] : dashboard + style --- diff --git a/app/organization/core/templatetags/organization_tags.py b/app/organization/core/templatetags/organization_tags.py index 0cda8e9d..155f743f 100644 --- a/app/organization/core/templatetags/organization_tags.py +++ b/app/organization/core/templatetags/organization_tags.py @@ -21,6 +21,7 @@ # -*- coding: utf-8 -*- import datetime +import calendar from django.http import QueryDict from mezzanine.pages.models import Page from mezzanine.blog.models import BlogPost @@ -202,3 +203,16 @@ def unspam(email): @register.filter def get_attr(obj, attr): return getattr(obj, attr) + +@register.filter +def month_name(month_number): + return calendar.month_name[month_number] + +@register.filter +def format_wp(work_packages): + work_packages = [str(wk.number) for wk in work_packages] + return ",".join(work_packages) + +@register.filter +def format_percent(percent): + return str(percent * 100) + ' %' diff --git a/app/organization/network/admin.py b/app/organization/network/admin.py index 7d831112..15559c2f 100644 --- a/app/organization/network/admin.py +++ b/app/organization/network/admin.py @@ -33,7 +33,7 @@ from organization.pages.models import * from organization.core.admin import * from organization.pages.admin import PageImageInline, PageBlockInline, PagePlaylistInline, DynamicContentPageInline, PageRelatedTitleAdmin from organization.shop.models import PageProductList -from organization.network.utils import TimesheetXLS +from organization.network.utils import TimesheetXLS, set_timesheets_validation_date class OrganizationAdminInline(StackedDynamicInlineAdmin): @@ -280,7 +280,8 @@ class PersonActivityTimeSheetAdmin(BaseTranslationOrderedModelAdmin): search_fields = ['year','activity__person__last_name', "project__title"] list_display = ['person', 'activity', 'year', 'month', 'project', 'work_package', 'percentage', 'accounting', 'validation'] list_filter = ['activity__person', 'year', 'project'] - actions = ['export_xls',] + actions = ['export_xls', 'validate_timesheets'] + def person(self, instance): return instance.activity.person @@ -292,6 +293,10 @@ class PersonActivityTimeSheetAdmin(BaseTranslationOrderedModelAdmin): def export_xls(self, request, queryset): xls = TimesheetXLS(queryset) return xls.write() + + def validate_timesheets(self, request, queryset): + set_timesheets_validation_date(queryset) + export_xls.short_description = "Export person timesheets" diff --git a/app/organization/network/api.py b/app/organization/network/api.py index af2773c4..0e3428ed 100644 --- a/app/organization/network/api.py +++ b/app/organization/network/api.py @@ -3,7 +3,7 @@ import requests import time from django.conf import settings -from datetime import date, datetime, timedelta +from datetime import date, timedelta import dateutil.parser WEEK_DAYS = { diff --git a/app/organization/network/forms.py b/app/organization/network/forms.py index 6bdd5043..61aafeac 100644 --- a/app/organization/network/forms.py +++ b/app/organization/network/forms.py @@ -19,6 +19,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from django.utils import timezone from dal import autocomplete import dal_queryset_sequence import dal_select2_queryset_sequence @@ -88,7 +89,11 @@ class OrganizationLinkedForm(forms.ModelForm): class PersonActivityTimeSheetForm(forms.ModelForm): + def save(self): + self.instance.accounting = timezone.now() + super(PersonActivityTimeSheetForm, self).save() + class Meta: model = PersonActivityTimeSheet fields = ('__all__') - # PersonActivityTimeSheetviewfields = ['pub_date', 'headline', 'content', 'reporter'] + exclude = ['accounting', 'validation'] diff --git a/app/organization/network/migrations/0084_auto_20170118_1119.py b/app/organization/network/migrations/0084_auto_20170118_1119.py new file mode 100644 index 00000000..faa9037e --- /dev/null +++ b/app/organization/network/migrations/0084_auto_20170118_1119.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-01-18 10:19 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('organization-network', '0083_auto_20170116_1235'), + ] + + operations = [ + migrations.AlterField( + model_name='personactivitytimesheet', + name='accounting', + field=models.DateField(blank=True, null=True), + ), + migrations.AlterField( + model_name='personactivitytimesheet', + name='validation', + field=models.DateField(blank=True, null=True), + ), + ] diff --git a/app/organization/network/migrations/0085_auto_20170118_1239.py b/app/organization/network/migrations/0085_auto_20170118_1239.py new file mode 100644 index 00000000..e64bf171 --- /dev/null +++ b/app/organization/network/migrations/0085_auto_20170118_1239.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-01-18 11:39 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('organization-network', '0084_auto_20170118_1119'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='personactivitytimesheet', + unique_together=set([('activity', 'project', 'month', 'year')]), + ), + ] diff --git a/app/organization/network/migrations/0086_auto_20170118_1247.py b/app/organization/network/migrations/0086_auto_20170118_1247.py new file mode 100644 index 00000000..6b6e3e4f --- /dev/null +++ b/app/organization/network/migrations/0086_auto_20170118_1247.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-01-18 11:47 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('organization-network', '0085_auto_20170118_1239'), + ] + + operations = [ + migrations.AlterField( + model_name='personactivitytimesheet', + name='month', + field=models.IntegerField(choices=[(1, 'January'), (2, 'February'), (3, 'March'), (4, 'April'), (5, 'May'), (6, 'June'), (7, 'July'), (8, 'August'), (9, 'September'), (10, 'October'), (11, 'November'), (12, 'December')], verbose_name='month'), + ), + ] diff --git a/app/organization/network/models.py b/app/organization/network/models.py index 7cbb9ccd..9d30483e 100644 --- a/app/organization/network/models.py +++ b/app/organization/network/models.py @@ -38,7 +38,6 @@ from django.core.urlresolvers import reverse, reverse_lazy from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ValidationError -from django.utils import timezone from mezzanine.pages.models import Page from mezzanine.core.models import RichText, Displayable, Slugged @@ -80,6 +79,21 @@ PATTERN_CHOICES = [ ('pattern-bg--triangles', _('triangles')), ] +MONTH_CHOICES = [ + (1, _('January')), + (2, _('February')), + (3, _('March')), + (4, _('April')), + (5, _('May')), + (6, _('June')), + (7, _('July')), + (8, _('August')), + (9, _('September')), + (10, _('October')), + (11, _('November')), + (12, _('December')), +] + ALIGNMENT_CHOICES = (('left', _('left')), ('left', _('left')), ('right', _('right'))) CSS_COLOR_CHOICES = [ @@ -561,10 +575,10 @@ class PersonActivityTimeSheet(models.Model): project = models.ForeignKey('organization-projects.Project', verbose_name=_('project'), related_name='timesheets') work_packages = models.ManyToManyField('organization-projects.ProjectWorkPackage', verbose_name=_('work package'), related_name='timesheets', blank=True) percentage = models.FloatField(_('% of work time on the project'), validators=[validate_positive]) - month = models.IntegerField(_('month')) + month = models.IntegerField(_('month'), choices=MONTH_CHOICES) year = models.IntegerField(_('year')) - accounting = models.DateField(default=timezone.now(), blank=True, null=True) - validation = models.DateField(default=timezone.now(), blank=True, null=True) + accounting = models.DateField(blank=True, null=True) + validation = models.DateField(blank=True, null=True) @property def date(self): @@ -574,6 +588,7 @@ class PersonActivityTimeSheet(models.Model): verbose_name = _('activity timesheet') verbose_name_plural = _('activity timesheets') ordering = ['month',] + unique_together = (("activity", "project", "month", "year"),) class PersonActivityVacation(Period): diff --git a/app/organization/network/utils.py b/app/organization/network/utils.py index f2e0ef00..16825dfe 100644 --- a/app/organization/network/utils.py +++ b/app/organization/network/utils.py @@ -4,8 +4,9 @@ import csv from django.http import HttpResponse from xlwt import * import calendar -from organization.network.api import * import datetime +from django.utils import timezone +from organization.network.api import * from collections import defaultdict, OrderedDict from pprint import pprint @@ -83,12 +84,6 @@ def get_nb_half_days_by_period_per_month(date_from, date_to): return md_dict -class TimesheetXLS2(object): - - def __init__(self, timesheets): - self.timesheets = timesheets.order_by('activity','project', 'year', 'month') - - class TimesheetXLS(object): t_dict = OrderedDict() @@ -163,11 +158,12 @@ class TimesheetXLS(object): if not person_slug in self.t_dict: self.t_dict[person_slug] = {} # caculate for each person leaved days in year + print("timesheet.year", timesheet.year) date_from = datetime.date(timesheet.year, 1, 1) date_to = datetime.date(timesheet.year, 12, 31) nb_half_days = get_nb_half_days_by_period_per_month(date_from, date_to) leave_days = get_leave_days_per_month(date_from, date_to, timesheet.activity.person.external_id) - worked_hours_by_month = {"test" : "1"} + worked_hours_by_month = {} # for each month for m_key, m_val in nb_half_days.items(): # for each week day @@ -275,3 +271,11 @@ class TimesheetXLS(object): response['Content-Disposition'] = 'attachment; filename=users.xls' self.book.save(response) return response + + + +def set_timesheets_validation_date(timesheets): + """ Admin action to set validation date for selected timesheets """ + for timesheet in timesheets : + timesheet.validation = timezone.now() + timesheet.save() diff --git a/app/organization/network/views.py b/app/organization/network/views.py index 41cb3045..5371a958 100644 --- a/app/organization/network/views.py +++ b/app/organization/network/views.py @@ -31,7 +31,9 @@ from organization.network.models import * from organization.core.views import * from datetime import date from organization.network.forms import * -from organization.network.utils import TimesheetXLS, TimesheetXLS2 +from organization.network.utils import TimesheetXLS +from collections import OrderedDict + class PersonListView(ListView): @@ -140,8 +142,13 @@ class TimeSheetCreateView(TimesheetAbstractView, CreateView): context_object_name = 'timesheet' form_class = PersonActivityTimeSheetForm + def get_success_url(self): + print(" self.kwargs['slug']", self.kwargs['slug']) + return reverse('organization-network-timesheet-list-view', kwargs={'slug': self.kwargs['slug']}) + def get_initial(self): initial = super(TimeSheetCreateView, self).get_initial() + # get the more recent activity initial['activity'] = PersonActivity.objects.filter(person__slug=self.kwargs['slug']).first() initial['month'] = self.kwargs['month'] initial['year'] = self.kwargs['year'] @@ -156,11 +163,22 @@ class TimeSheetCreateView(TimesheetAbstractView, CreateView): class PersonActivityTimeSheetListView(TimesheetAbstractView, ListView): model = PersonActivityTimeSheet template_name='network/person_activity_timesheet/person_activity_timesheet_list.html' - context_object_name = 'timesheet' + context_object_name = 'timesheets_by_year' def get_queryset(self): if 'slug' in self.kwargs: - return PersonActivityTimeSheet.objects.filter(activity__person__slug__exact=self.kwargs['slug']) + timesheets = PersonActivityTimeSheet.objects.filter(activity__person__slug__exact=self.kwargs['slug']) + t_dict = OrderedDict() + for timesheet in timesheets: + year = timesheet.year + if not year in t_dict: + t_dict[year] = {} + project_slug = timesheet.project.title + # if new person + if not project_slug in t_dict[year]: + t_dict[year][project_slug] = [] + t_dict[year][project_slug].append(timesheet) + return t_dict def get_context_data(self, **kwargs): context = super(PersonActivityTimeSheetListView, self).get_context_data(**kwargs) diff --git a/app/organization/projects/migrations/0042_auto_20170118_1239.py b/app/organization/projects/migrations/0042_auto_20170118_1239.py new file mode 100644 index 00000000..a72b079c --- /dev/null +++ b/app/organization/projects/migrations/0042_auto_20170118_1239.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-01-18 11:39 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('organization-projects', '0041_merge'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='manager', + field=models.ManyToManyField(blank=True, related_name='projects_manager', to='organization-network.Person', verbose_name='Manager'), + ), + ] diff --git a/app/organization/projects/models.py b/app/organization/projects/models.py index 28fe7ed7..bc9ab0ad 100644 --- a/app/organization/projects/models.py +++ b/app/organization/projects/models.py @@ -61,7 +61,7 @@ class Project(Displayable, Period, RichText): website = models.URLField(_('website'), max_length=512, blank=True) topic = models.ForeignKey('ProjectTopic', verbose_name=_('topic'), related_name='projects', blank=True, null=True) referring_person = models.ManyToManyField('organization-network.Person', verbose_name=_('Referring Person'), related_name='projects_referring_person', blank=True) - manager = models.ManyToManyField('organization-network.Person', verbose_name=_('Manager'), related_name='projects_manager', blank=True, null=True) + manager = models.ManyToManyField('organization-network.Person', verbose_name=_('Manager'), related_name='projects_manager', blank=True) class Meta: verbose_name = _('project') diff --git a/app/templates/network/person_activity_timesheet/includes/person_activity_timesheet_inline.html b/app/templates/network/person_activity_timesheet/includes/person_activity_timesheet_inline.html index 3cbd38cb..7b7c2cf0 100644 --- a/app/templates/network/person_activity_timesheet/includes/person_activity_timesheet_inline.html +++ b/app/templates/network/person_activity_timesheet/includes/person_activity_timesheet_inline.html @@ -1 +1,22 @@ -{{ object }} +{% load organization_tags %} + + + + + + + + + + +
{{ timesheet.month|month_name }}
+ {{ timesheet.percentage|format_percent }} +
+ {% with timesheet.work_packages.all as work_packages %} + {% if work_packages %} + {{ timesheet.work_packages.all|format_wp }} + {% else %} + - + {% endif %} + {% endwith %} +
diff --git a/app/templates/network/person_activity_timesheet/person_activity_timesheet_list.html b/app/templates/network/person_activity_timesheet/person_activity_timesheet_list.html index 30a2295a..31a3f230 100644 --- a/app/templates/network/person_activity_timesheet/person_activity_timesheet_list.html +++ b/app/templates/network/person_activity_timesheet/person_activity_timesheet_list.html @@ -14,11 +14,22 @@ {% block page_content %} Declare this month - - {% if person_activity_timesheet %} - - {% for pat in person_activity_timesheet %} - {% include "network/person_activity_timesheet/includes/person_activity_timesheet_inline.html" with object=pat %} + {{ timesheets_by_project }} + {% if timesheets_by_year %} + {% for year_k, year_v in timesheets_by_year.items %} +

{{ year_k }}

+ {% for project_k, project_v in year_v.items %} +

{{ project_k }}

+ + + {% for timesheet in project_v %} + + {% endfor %} + +
+ {% include "network/person_activity_timesheet/includes/person_activity_timesheet_inline.html" %} +
+ {% endfor %} {% endfor %} {% else %}