diff --git a/Pipfile b/Pipfile
index b1bcff9..5574909 100644
--- a/Pipfile
+++ b/Pipfile
@@ -9,6 +9,7 @@ sqlalchemy = "*"
numpy = "*"
matplotlib = "*"
scipy = "*"
+django-bootstrap-datepicker-plus = "*"
[dev-packages]
diff --git a/Pipfile.lock b/Pipfile.lock
index afa2d00..d7309b6 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "6116399a31049347a1b4684b7d45038cd1bbc4c40c7f945196fd85072e90c483"
+ "sha256": "c6a6b8d3ebd478446b8e8800b29555ed874b559625670a9961406e358b897e06"
},
"pipfile-spec": 6,
"requires": {
@@ -31,6 +31,14 @@
"index": "pypi",
"version": "==2.1.4"
},
+ "django-bootstrap-datepicker-plus": {
+ "hashes": [
+ "sha256:490058eba99d47f48a7d24fa78581c0e36375bdc7aa9605783eeb170d51fd0df",
+ "sha256:a8bc19cc6846f97ff1e6c447f4e0387881d16e8afa1e8bd7a652c19e545c566b"
+ ],
+ "index": "pypi",
+ "version": "==3.0.5"
+ },
"kiwisolver": {
"hashes": [
"sha256:0ee4ed8b3ae8f5f712b0aa9ebd2858b5b232f1b9a96b0943dceb34df2a223bc3",
diff --git a/templates/web/activitylog_form.html b/templates/web/activitylog_form.html
index f7089bd..972af0d 100644
--- a/templates/web/activitylog_form.html
+++ b/templates/web/activitylog_form.html
@@ -1,10 +1,13 @@
{% extends 'web/base.html' %}
{% block main %}
+ {{ form.media }}
-
+
{% endblock %}
diff --git a/templates/web/user_detail.html b/templates/web/user_detail.html
index 7595880..3735d6a 100644
--- a/templates/web/user_detail.html
+++ b/templates/web/user_detail.html
@@ -81,10 +81,10 @@
Активности
- {% for activity in activities %}
+ {% for activities_and_pct in activities_and_pcts %}
-
-
- {{ activity }}
+
+ {{ activities_and_pct.0 }} — {{ activities_and_pct.1|stringformat:".2f%%" }}
{% endfor %}
diff --git a/timelogger/settings.py b/timelogger/settings.py
index e9b5832..f4cafad 100644
--- a/timelogger/settings.py
+++ b/timelogger/settings.py
@@ -29,6 +29,7 @@ ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
+ 'bootstrap_datepicker_plus',
'web.apps.WebConfig',
'django.contrib.admin',
'django.contrib.auth',
diff --git a/web/chart_views.py b/web/chart_views.py
index c6b8e9e..8fb266b 100644
--- a/web/chart_views.py
+++ b/web/chart_views.py
@@ -28,7 +28,7 @@ class UserChartsPie(DetailView):
sectors = sorted(list(total_time.items()), key=lambda x: x[1])
- ax.pie(x=[i[1] for i in sectors], labels=[i[0] for i in sectors])
+ ax.pie(x=[i[1] for i in sectors], labels=[i[0] for i in sectors], autopct='%1.0f%%')
buf = io.BytesIO()
fig.savefig(buf, format='png')
@@ -59,6 +59,7 @@ class UserChartsActivityAll(DetailView):
day_hours = day_seconds / timedelta(hours=1).total_seconds()
ax.bar(days, day_hours)
+ ax.hlines([np.mean(day_hours)], day_l, day_r, color='orange', lw=3)
plt.xticks(days, [f'{i:%m-%d}' for i in days], rotation=60)
plt.ylabel('кол-во часов')
diff --git a/web/forms.py b/web/forms.py
new file mode 100644
index 0000000..e84ecba
--- /dev/null
+++ b/web/forms.py
@@ -0,0 +1,14 @@
+import bootstrap_datepicker_plus
+from django import forms
+
+from web.models import ActivityLog
+
+
+class ActivityLogCreateForm(forms.ModelForm):
+ class Meta:
+ model = ActivityLog
+ fields = ['user', 'activity', 'start_time', 'end_time']
+ widgets = {
+ 'start_time': bootstrap_datepicker_plus.DateTimePickerInput(format='%Y-%m-%d %H:%M'),
+ 'end_time': bootstrap_datepicker_plus.DateTimePickerInput(format='%Y-%m-%d %H:%M'),
+ }
diff --git a/web/models.py b/web/models.py
index 4001b34..caf63cc 100644
--- a/web/models.py
+++ b/web/models.py
@@ -26,14 +26,17 @@ class ActivityLog(models.Model):
logged_at = models.DateTimeField(auto_now_add=True)
def clean(self):
+ if self.start_time is None or self.end_time is None:
+ raise ValidationError(f'Не указано время: {self}')
+
if self.start_time >= self.end_time:
- raise ValidationError(f'Illegal times: {self}')
+ raise ValidationError(f'Некорректное время: {self}')
for other_activity in ActivityLog.objects.filter(user=self.user).exclude(id=self.id):
l = max(self.start_time, other_activity.start_time)
r = min(self.end_time, other_activity.end_time)
if r > l:
- raise ValidationError(f'Intersects with another activity: \n{self}\n{other_activity}\n{l}, {r}')
+ raise ValidationError(f'Пересекается с другой активностью: \n{self}\n{other_activity}\n{l}, {r}')
def __str__(self):
return f'ActivityLog[{self.user}, {self.activity}] {self.start_time}-{self.end_time} ({self.end_time - self.start_time})'
diff --git a/web/views.py b/web/views.py
index 0ddd541..1c1c8c1 100644
--- a/web/views.py
+++ b/web/views.py
@@ -1,6 +1,8 @@
+import numpy as np
from django.urls import reverse, reverse_lazy
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
+from web.forms import ActivityLogCreateForm
from web.models import ActivityLog, User, Activity
@@ -23,12 +25,26 @@ class UserDetailView(DetailView):
context = super().get_context_data(**kwargs)
context['activity_logs'] = ActivityLog.objects.filter(user=self.object).order_by('start_time')
context['activities'] = set(i.activity for i in ActivityLog.objects.filter(user=self.object))
+
+ total_times = {i: 0 for i in context['activities']}
+ total = 0
+ for i in context['activity_logs']:
+ total_times[i.activity] += (i.end_time - i.start_time).total_seconds()
+ total += (i.end_time - i.start_time).total_seconds()
+
+ keys = list(total_times.keys())
+ context['activities_and_pcts'] = [
+ (keys[i], total_times[keys[i]] / total * 100)
+ for i in reversed(np.argsort(list(total_times.values())))
+ ]
+
return context
class ActivityLogCreateView(CreateView):
model = ActivityLog
- fields = ['activity', 'user', 'start_time', 'end_time']
+ # fields = ['activity', 'user', 'start_time', 'end_time']
+ form_class = ActivityLogCreateForm
def get_success_url(self):
return reverse('user', kwargs={
@@ -66,5 +82,3 @@ class UserChartsActivity(DetailView):
context = super().get_context_data(**kwargs)
context['activity'] = Activity.objects.filter(id=self.kwargs['activity_id']).first()
return context
-
-