commit 48b4c9bbfe2f704c1e560495ae3c1eff3b07cb5f
Author: svxf
Date: Thu Dec 27 02:03:40 2018 +0400
init
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000..b1bcff9
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,16 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+django = "*"
+sqlalchemy = "*"
+numpy = "*"
+matplotlib = "*"
+scipy = "*"
+
+[dev-packages]
+
+[requires]
+python_version = "3.7"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000..afa2d00
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,191 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "6116399a31049347a1b4684b7d45038cd1bbc4c40c7f945196fd85072e90c483"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.7"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "cycler": {
+ "hashes": [
+ "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d",
+ "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"
+ ],
+ "version": "==0.10.0"
+ },
+ "django": {
+ "hashes": [
+ "sha256:068d51054083d06ceb32ce02b7203f1854256047a0d58682677dd4f81bceabd7",
+ "sha256:55409a056b27e6d1246f19ede41c6c610e4cab549c005b62cbeefabc6433356b"
+ ],
+ "index": "pypi",
+ "version": "==2.1.4"
+ },
+ "kiwisolver": {
+ "hashes": [
+ "sha256:0ee4ed8b3ae8f5f712b0aa9ebd2858b5b232f1b9a96b0943dceb34df2a223bc3",
+ "sha256:0f7f532f3c94e99545a29f4c3f05637f4d2713e7fd91b4dd8abfc18340b86cd5",
+ "sha256:1a078f5dd7e99317098f0e0d490257fd0349d79363e8c923d5bb76428f318421",
+ "sha256:1aa0b55a0eb1bd3fa82e704f44fb8f16e26702af1a073cc5030eea399e617b56",
+ "sha256:2874060b91e131ceeff00574b7c2140749c9355817a4ed498e82a4ffa308ecbc",
+ "sha256:379d97783ba8d2934d52221c833407f20ca287b36d949b4bba6c75274bcf6363",
+ "sha256:3b791ddf2aefc56382aadc26ea5b352e86a2921e4e85c31c1f770f527eb06ce4",
+ "sha256:4329008a167fac233e398e8a600d1b91539dc33c5a3eadee84c0d4b04d4494fa",
+ "sha256:45813e0873bbb679334a161b28cb9606d9665e70561fd6caa8863e279b5e464b",
+ "sha256:53a5b27e6b5717bdc0125338a822605084054c80f382051fb945d2c0e6899a20",
+ "sha256:574f24b9805cb1c72d02b9f7749aa0cc0b81aa82571be5201aa1453190390ae5",
+ "sha256:66f82819ff47fa67a11540da96966fb9245504b7f496034f534b81cacf333861",
+ "sha256:79e5fe3ccd5144ae80777e12973027bd2f4f5e3ae8eb286cabe787bed9780138",
+ "sha256:83410258eb886f3456714eea4d4304db3a1fc8624623fc3f38a487ab36c0f653",
+ "sha256:8b6a7b596ce1d2a6d93c3562f1178ebd3b7bb445b3b0dd33b09f9255e312a965",
+ "sha256:9576cb63897fbfa69df60f994082c3f4b8e6adb49cccb60efb2a80a208e6f996",
+ "sha256:95a25d9f3449046ecbe9065be8f8380c03c56081bc5d41fe0fb964aaa30b2195",
+ "sha256:a424f048bebc4476620e77f3e4d1f282920cef9bc376ba16d0b8fe97eec87cde",
+ "sha256:aaec1cfd94f4f3e9a25e144d5b0ed1eb8a9596ec36d7318a504d813412563a85",
+ "sha256:acb673eecbae089ea3be3dcf75bfe45fc8d4dcdc951e27d8691887963cf421c7",
+ "sha256:b15bc8d2c2848a4a7c04f76c9b3dc3561e95d4dabc6b4f24bfabe5fd81a0b14f",
+ "sha256:b1c240d565e977d80c0083404c01e4d59c5772c977fae2c483f100567f50847b",
+ "sha256:c595693de998461bcd49b8d20568c8870b3209b8ea323b2a7b0ea86d85864694",
+ "sha256:ce3be5d520b4d2c3e5eeb4cd2ef62b9b9ab8ac6b6fedbaa0e39cdb6f50644278",
+ "sha256:e0f910f84b35c36a3513b96d816e6442ae138862257ae18a0019d2fc67b041dc",
+ "sha256:ea36e19ac0a483eea239320aef0bd40702404ff8c7e42179a2d9d36c5afcb55c",
+ "sha256:efabbcd4f406b532206b8801058c8bab9e79645b9880329253ae3322b7b02cd5",
+ "sha256:f923406e6b32c86309261b8195e24e18b6a8801df0cfc7814ac44017bfcb3939"
+ ],
+ "version": "==1.0.1"
+ },
+ "matplotlib": {
+ "hashes": [
+ "sha256:16aa61846efddf91df623bbb4598e63be1068a6b6a2e6361cc802b41c7a286eb",
+ "sha256:1975b71a33ac986bb39b6d5cfbc15c7b1f218f1134efb4eb3881839d6ae69984",
+ "sha256:2b222744bd54781e6cc0b717fa35a54e5f176ba2ced337f27c5b435b334ef854",
+ "sha256:317643c0e88fad55414347216362b2e229c130edd5655fea5f8159a803098468",
+ "sha256:4269ce3d1b897d46fc3cc2273a0cc2a730345bb47e4456af662e6fca85c89dd7",
+ "sha256:65214fd668975077cdf8d408ccf2b2d6bdf73b4e6895a79f8e99ce4f0b43fcdb",
+ "sha256:74bc213ab8a92d86a0b304d9359d1e1d14168d4c6121b83862c9d8a88b89a738",
+ "sha256:88949be0db54755995dfb0210d0099a8712a3c696c860441971354c3debfc4af",
+ "sha256:8e1223d868be89423ec95ada5f37aa408ee64fe76ccb8e4d5f533699ba4c0e4a",
+ "sha256:9fa00f2d7a552a95fa6016e498fdeb6d74df537853dda79a9055c53dfc8b6e1a",
+ "sha256:c27fd46cab905097ba4bc28d5ba5289930f313fb1970c9d41092c9975b80e9b4",
+ "sha256:c94b792af431f6adb6859eb218137acd9a35f4f7442cea57e4a59c54751c36af",
+ "sha256:f4c12a01eb2dc16693887a874ba948b18c92f425c4d329639ece6d3bb8e631bb"
+ ],
+ "index": "pypi",
+ "version": "==3.0.2"
+ },
+ "numpy": {
+ "hashes": [
+ "sha256:0df89ca13c25eaa1621a3f09af4c8ba20da849692dcae184cb55e80952c453fb",
+ "sha256:154c35f195fd3e1fad2569930ca51907057ae35e03938f89a8aedae91dd1b7c7",
+ "sha256:18e84323cdb8de3325e741a7a8dd4a82db74fde363dce32b625324c7b32aa6d7",
+ "sha256:1e8956c37fc138d65ded2d96ab3949bd49038cc6e8a4494b1515b0ba88c91565",
+ "sha256:23557bdbca3ccbde3abaa12a6e82299bc92d2b9139011f8c16ca1bb8c75d1e95",
+ "sha256:24fd645a5e5d224aa6e39d93e4a722fafa9160154f296fd5ef9580191c755053",
+ "sha256:36e36b6868e4440760d4b9b44587ea1dc1f06532858d10abba98e851e154ca70",
+ "sha256:3d734559db35aa3697dadcea492a423118c5c55d176da2f3be9c98d4803fc2a7",
+ "sha256:416a2070acf3a2b5d586f9a6507bb97e33574df5bd7508ea970bbf4fc563fa52",
+ "sha256:4a22dc3f5221a644dfe4a63bf990052cc674ef12a157b1056969079985c92816",
+ "sha256:4d8d3e5aa6087490912c14a3c10fbdd380b40b421c13920ff468163bc50e016f",
+ "sha256:4f41fd159fba1245e1958a99d349df49c616b133636e0cf668f169bce2aeac2d",
+ "sha256:561ef098c50f91fbac2cc9305b68c915e9eb915a74d9038ecf8af274d748f76f",
+ "sha256:56994e14b386b5c0a9b875a76d22d707b315fa037affc7819cda08b6d0489756",
+ "sha256:73a1f2a529604c50c262179fcca59c87a05ff4614fe8a15c186934d84d09d9a5",
+ "sha256:7da99445fd890206bfcc7419f79871ba8e73d9d9e6b82fe09980bc5bb4efc35f",
+ "sha256:99d59e0bcadac4aa3280616591fb7bcd560e2218f5e31d5223a2e12a1425d495",
+ "sha256:a4cc09489843c70b22e8373ca3dfa52b3fab778b57cf81462f1203b0852e95e3",
+ "sha256:a61dc29cfca9831a03442a21d4b5fd77e3067beca4b5f81f1a89a04a71cf93fa",
+ "sha256:b1853df739b32fa913cc59ad9137caa9cc3d97ff871e2bbd89c2a2a1d4a69451",
+ "sha256:b1f44c335532c0581b77491b7715a871d0dd72e97487ac0f57337ccf3ab3469b",
+ "sha256:b261e0cb0d6faa8fd6863af26d30351fd2ffdb15b82e51e81e96b9e9e2e7ba16",
+ "sha256:c857ae5dba375ea26a6228f98c195fec0898a0fd91bcf0e8a0cae6d9faf3eca7",
+ "sha256:cf5bb4a7d53a71bb6a0144d31df784a973b36d8687d615ef6a7e9b1809917a9b",
+ "sha256:db9814ff0457b46f2e1d494c1efa4111ca089e08c8b983635ebffb9c1573361f",
+ "sha256:df04f4bad8a359daa2ff74f8108ea051670cafbca533bb2636c58b16e962989e",
+ "sha256:ecf81720934a0e18526177e645cbd6a8a21bb0ddc887ff9738de07a1df5c6b61",
+ "sha256:edfa6fba9157e0e3be0f40168eb142511012683ac3dc82420bee4a3f3981b30e"
+ ],
+ "index": "pypi",
+ "version": "==1.15.4"
+ },
+ "pyparsing": {
+ "hashes": [
+ "sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b",
+ "sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592"
+ ],
+ "version": "==2.3.0"
+ },
+ "python-dateutil": {
+ "hashes": [
+ "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93",
+ "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02"
+ ],
+ "version": "==2.7.5"
+ },
+ "pytz": {
+ "hashes": [
+ "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca",
+ "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6"
+ ],
+ "version": "==2018.7"
+ },
+ "scipy": {
+ "hashes": [
+ "sha256:02cb79ea38114dc480e9b08d6b87095728e8fb39b9a49b449ee443d678001611",
+ "sha256:03c827cdbc584e935264040b958e5fa0570a16095bee23a013482ba3f0e963a2",
+ "sha256:04f2b23258139c109d0524f111597dd095a505d9cb2c71e381d688d653877fa3",
+ "sha256:3132a9fab3f3545c8b0ba15688d11857efdd4a58d388d3785dc474f56fea7138",
+ "sha256:4b1f0883cb9d8ee963cf0a31c87341e9e758abb2cf1e5bcc0d7b066ef6b17573",
+ "sha256:4cce25c6e7ff7399c67dfe1b5423c36c391cf9b4b2be390c1675ab410f1ef503",
+ "sha256:51a2424c8ed80e60bdb9a896806e7adaf24a58253b326fbad10f80a6d06f2214",
+ "sha256:5706b785ca289fdfd91aa05066619e51d140613b613e35932601f2315f5d8470",
+ "sha256:58f0435f052cb60f1472c77f52a8f6642f8877b70559e5e0b9a1744f33f5cbe5",
+ "sha256:63e1d5ca9e5e1984f1a275276991b036e25d39d37dd7edbb3f4f6165c2da7dbb",
+ "sha256:64b2c35824da3ef6bb1e722216e4ef28802af6413c7586136500e343d34ba179",
+ "sha256:6f791987899532305126309578727c0197bddbafab9596bafe3e7bfab6e1ce13",
+ "sha256:72bd766f753fd32f072d30d7bc2ad492d56dbcbf3e13764e27635d5c41079339",
+ "sha256:7413080b381766a22d52814edb65631f0e323a7cea945c70021a672f38acc73f",
+ "sha256:78a67ee4845440e81cfbfabde20537ca12051d0eeac951fe4c6d8751feac3103",
+ "sha256:7994c044bf659b0a24ad7673ec7db85c2fadb87e4980a379a9fd5b086fe3649a",
+ "sha256:7dc4002f0a0a688774ef04878afe769ecd1ac21fe9b4b1d7125e2cf16170afd3",
+ "sha256:854bd87cc23824d5db4983956bc30f3790e1c7448f1a9e6a8fb7bff7601aef87",
+ "sha256:8608316d0cc01f8b25111c8adfe6efbc959cbba037a62c784551568d7ffbf280",
+ "sha256:8f5fcc87b48fc3dd6d901669c89af4feeb856dffb6f671539a238b7e8af1799c",
+ "sha256:937147086e8b4338bf139ca8fa98da650e3a46bf2ca617f8e9dd68c3971ec420",
+ "sha256:bc294841f6c822714af362095b181a853f47ed5ce757354bd2e4776d579ff3a4",
+ "sha256:bc6a88b0009a1b60eab5c22ac3a006f6968d6328de10c6a64ebb0d64a05548d3",
+ "sha256:c5eae911cf26b3c7eda889ec98d3c226f312c587acfaaf02602473f98b4c16d6",
+ "sha256:cca33a01a5987c650b87a1a910aa311ffa44e67cca1ff502ef9efdae5d9a8624",
+ "sha256:d1ae77b79fd5e27a10ba7c4e7c3a62927b9d29a4dccf28f6905c25d983aaf183",
+ "sha256:fb36064047e6bf87b6320a9b6eb7f525ef6863c7a4aef1a84a4bbfb043612617",
+ "sha256:fc1a19d95649439dbd50baca676bceb29bbfcd600aed2c5bd71d9bad82a87cfe"
+ ],
+ "index": "pypi",
+ "version": "==1.2.0"
+ },
+ "six": {
+ "hashes": [
+ "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
+ "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
+ ],
+ "version": "==1.12.0"
+ },
+ "sqlalchemy": {
+ "hashes": [
+ "sha256:809547455d012734b4252081db1e6b4fc731de2299f3755708c39863625e1c77"
+ ],
+ "index": "pypi",
+ "version": "==1.2.15"
+ }
+ },
+ "develop": {}
+}
diff --git a/db.sqlite3 b/db.sqlite3
new file mode 100644
index 0000000..db42473
Binary files /dev/null and b/db.sqlite3 differ
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..57e4c2c
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == '__main__':
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'timelogger.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
diff --git a/templates/web/activitylog_form.html b/templates/web/activitylog_form.html
new file mode 100644
index 0000000..02966b3
--- /dev/null
+++ b/templates/web/activitylog_form.html
@@ -0,0 +1,15 @@
+
+
+
+
+ Новая запись
+
+
+
+
+
+
+
diff --git a/templates/web/user_detail.html b/templates/web/user_detail.html
new file mode 100644
index 0000000..38a3164
--- /dev/null
+++ b/templates/web/user_detail.html
@@ -0,0 +1,71 @@
+
+
+
+
+ Пользователь
+
+
+
+{{ user }}
+
+
+Записи
+
+Создать новую запись
+
+{% if activity_logs %}
+
+
+
+ Активность
+ |
+
+ Когда начал
+ |
+
+ Когда закончил
+ |
+
+ |
+
+ |
+
+
+ {% for activity_log in activity_logs %}
+
+
+ {{ activity_log.activity }}
+ |
+
+ {{ activity_log.start_time }}
+ |
+
+ {{ activity_log.end_time }}
+ |
+
+
+ |
+
+
+ |
+
+ {% endfor %}
+
+{% else %}
+ Ничего не залогано :(
+{% endif %}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/web/user_form.html b/templates/web/user_form.html
new file mode 100644
index 0000000..f378453
--- /dev/null
+++ b/templates/web/user_form.html
@@ -0,0 +1,15 @@
+
+
+
+
+ Новый пользователь
+
+
+
+
+
+
+
diff --git a/templates/web/user_list.html b/templates/web/user_list.html
new file mode 100644
index 0000000..50de522
--- /dev/null
+++ b/templates/web/user_list.html
@@ -0,0 +1,28 @@
+
+
+
+
+ Пользователи
+
+
+
+Пользователи
+
+
+
+
+ Создать нового пользователя
+
+
+
+
diff --git a/timelogger/__init__.py b/timelogger/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/timelogger/__pycache__/__init__.cpython-37.pyc b/timelogger/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..4f7bc51
Binary files /dev/null and b/timelogger/__pycache__/__init__.cpython-37.pyc differ
diff --git a/timelogger/__pycache__/settings.cpython-37.pyc b/timelogger/__pycache__/settings.cpython-37.pyc
new file mode 100644
index 0000000..c991864
Binary files /dev/null and b/timelogger/__pycache__/settings.cpython-37.pyc differ
diff --git a/timelogger/__pycache__/urls.cpython-37.pyc b/timelogger/__pycache__/urls.cpython-37.pyc
new file mode 100644
index 0000000..2183ad4
Binary files /dev/null and b/timelogger/__pycache__/urls.cpython-37.pyc differ
diff --git a/timelogger/__pycache__/wsgi.cpython-37.pyc b/timelogger/__pycache__/wsgi.cpython-37.pyc
new file mode 100644
index 0000000..8a9731c
Binary files /dev/null and b/timelogger/__pycache__/wsgi.cpython-37.pyc differ
diff --git a/timelogger/settings.py b/timelogger/settings.py
new file mode 100644
index 0000000..cb9d80c
--- /dev/null
+++ b/timelogger/settings.py
@@ -0,0 +1,117 @@
+"""
+Django settings for timelogger project.
+
+Generated by 'django-admin startproject' using Django 2.1.4.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.1/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/2.1/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = '@uo#()klkn3kl-c@6el=7jfyhcunmw-542ry5e$f$$^+xo%q#x'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'web.apps.WebConfig',
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'timelogger.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [
+ os.path.join(BASE_DIR, 'templates'),
+ ],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'timelogger.wsgi.application'
+
+# Database
+# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ }
+}
+
+# Password validation
+# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+# Internationalization
+# https://docs.djangoproject.com/en/2.1/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = False
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/2.1/howto/static-files/
+
+STATIC_URL = '/static/'
diff --git a/timelogger/urls.py b/timelogger/urls.py
new file mode 100644
index 0000000..2424277
--- /dev/null
+++ b/timelogger/urls.py
@@ -0,0 +1,23 @@
+"""timelogger URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/2.1/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path, include
+
+urlpatterns = [
+ path('admin/', admin.site.urls),
+
+ path('', include('web.urls')),
+]
diff --git a/timelogger/wsgi.py b/timelogger/wsgi.py
new file mode 100644
index 0000000..48b4212
--- /dev/null
+++ b/timelogger/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for timelogger project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'timelogger.settings')
+
+application = get_wsgi_application()
diff --git a/web/__init__.py b/web/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/web/__pycache__/__init__.cpython-37.pyc b/web/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..a120302
Binary files /dev/null and b/web/__pycache__/__init__.cpython-37.pyc differ
diff --git a/web/__pycache__/apps.cpython-37.pyc b/web/__pycache__/apps.cpython-37.pyc
new file mode 100644
index 0000000..1a18034
Binary files /dev/null and b/web/__pycache__/apps.cpython-37.pyc differ
diff --git a/web/__pycache__/models.cpython-37.pyc b/web/__pycache__/models.cpython-37.pyc
new file mode 100644
index 0000000..8cc4767
Binary files /dev/null and b/web/__pycache__/models.cpython-37.pyc differ
diff --git a/web/__pycache__/urls.cpython-37.pyc b/web/__pycache__/urls.cpython-37.pyc
new file mode 100644
index 0000000..9fca362
Binary files /dev/null and b/web/__pycache__/urls.cpython-37.pyc differ
diff --git a/web/__pycache__/views.cpython-37.pyc b/web/__pycache__/views.cpython-37.pyc
new file mode 100644
index 0000000..afebbf5
Binary files /dev/null and b/web/__pycache__/views.cpython-37.pyc differ
diff --git a/web/apps.py b/web/apps.py
new file mode 100644
index 0000000..529d063
--- /dev/null
+++ b/web/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class WebConfig(AppConfig):
+ name = 'web'
diff --git a/web/management/__init__.py b/web/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/web/management/commands/__init__.py b/web/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/web/management/commands/datafill.py b/web/management/commands/datafill.py
new file mode 100644
index 0000000..fd04396
--- /dev/null
+++ b/web/management/commands/datafill.py
@@ -0,0 +1,72 @@
+from datetime import datetime, timedelta
+
+import numpy as np
+from django.core.exceptions import ValidationError
+from django.core.management import BaseCommand
+
+from web.models import Activity, ActivityLog, User
+
+
+class Command(BaseCommand):
+ def handle(self, *args, **options):
+ User.objects.all().delete()
+ Activity.objects.all().delete()
+ ActivityLog.objects.all().delete()
+
+ activity_sleeping = Activity(type='Сон')
+ activity_sleeping.save()
+ activity_eating = Activity(type='Еда')
+ activity_eating.save()
+ activity_studying = Activity(type='Учёба')
+ activity_studying.save()
+ activity_working = Activity(type='Работа')
+ activity_working.save()
+ activity_logistics = Activity(type='Транспорт')
+ activity_logistics.save()
+ activity_pastime = Activity(type='Досуг')
+ activity_pastime.save()
+
+ user1 = User(name='Вася', email='vasya@abra.me')
+ user1.save()
+ user2 = User(name='Петя', email='petya@abra.me')
+ user2.save()
+
+ start_date = datetime(2018, 12, 1, 0, 0, 0)
+ days = (datetime.utcnow() - start_date) // timedelta(days=1) - 1
+
+ for i in range(days):
+ day_start = start_date + timedelta(days=1) * i
+
+ def add_activity(user, activity, start_offset, end_offset, variance):
+ activity_log = ActivityLog(
+ user=user,
+ activity=activity,
+ start_time=day_start + start_offset + variance * abs(np.random.normal()),
+ end_time=day_start + end_offset - variance * abs(np.random.normal()),
+ )
+ try:
+ activity_log.clean()
+ except ValidationError as e:
+ print(e.message)
+ return
+
+ activity_log.save()
+ print(activity_log)
+
+ add_activity(user1, activity_sleeping, timedelta(hours=-24 + 22), timedelta(hours=8), timedelta(hours=2))
+ add_activity(user1, activity_eating, timedelta(hours=8), timedelta(hours=8.5), timedelta(minutes=5))
+ add_activity(user1, activity_logistics, timedelta(hours=8.5), timedelta(hours=9.5), timedelta(minutes=10))
+ add_activity(user1, activity_working, timedelta(hours=9.5), timedelta(hours=14), timedelta(minutes=30))
+ add_activity(user1, activity_eating, timedelta(hours=14), timedelta(hours=14.5), timedelta(minutes=5))
+ add_activity(user1, activity_working, timedelta(hours=14.5), timedelta(hours=18), timedelta(minutes=30))
+ add_activity(user1, activity_logistics, timedelta(hours=18), timedelta(hours=19), timedelta(minutes=10))
+ add_activity(user1, activity_eating, timedelta(hours=20), timedelta(hours=20.5), timedelta(minutes=5))
+ add_activity(user1, activity_pastime, timedelta(hours=20.5), timedelta(hours=22), timedelta(minutes=10))
+
+ add_activity(user2, activity_sleeping, timedelta(hours=0), timedelta(hours=6), timedelta(hours=1))
+ add_activity(user2, activity_pastime, timedelta(hours=6), timedelta(hours=7), timedelta(minutes=10))
+ add_activity(user2, activity_logistics, timedelta(hours=7), timedelta(hours=8), timedelta(minutes=10))
+ add_activity(user2, activity_studying, timedelta(hours=8), timedelta(hours=18), timedelta(minutes=30))
+ add_activity(user2, activity_logistics, timedelta(hours=18), timedelta(hours=19), timedelta(minutes=10))
+ add_activity(user2, activity_eating, timedelta(hours=19), timedelta(hours=20), timedelta(minutes=10))
+ add_activity(user2, activity_pastime, timedelta(hours=20), timedelta(hours=24), timedelta(minutes=10))
diff --git a/web/migrations/0001_initial.py b/web/migrations/0001_initial.py
new file mode 100644
index 0000000..f32d162
--- /dev/null
+++ b/web/migrations/0001_initial.py
@@ -0,0 +1,45 @@
+# Generated by Django 2.1.4 on 2018-12-26 19:52
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Activity',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('type', models.TextField()),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ActivityLog',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('start_time', models.DateTimeField()),
+ ('end_time', models.DateTimeField()),
+ ('logged_at', models.DateTimeField(auto_now_add=True)),
+ ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.Activity')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='User',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.TextField()),
+ ('email', models.TextField()),
+ ],
+ ),
+ migrations.AddField(
+ model_name='activitylog',
+ name='user',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.User'),
+ ),
+ ]
diff --git a/web/migrations/__init__.py b/web/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/web/migrations/__pycache__/0001_initial.cpython-37.pyc b/web/migrations/__pycache__/0001_initial.cpython-37.pyc
new file mode 100644
index 0000000..6caea0c
Binary files /dev/null and b/web/migrations/__pycache__/0001_initial.cpython-37.pyc differ
diff --git a/web/migrations/__pycache__/__init__.cpython-37.pyc b/web/migrations/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..a84e178
Binary files /dev/null and b/web/migrations/__pycache__/__init__.cpython-37.pyc differ
diff --git a/web/models.py b/web/models.py
new file mode 100644
index 0000000..01bea2d
--- /dev/null
+++ b/web/models.py
@@ -0,0 +1,39 @@
+from django.core.exceptions import ValidationError
+from django.db import models
+
+
+class User(models.Model):
+ name = models.TextField()
+ email = models.TextField()
+
+ def __str__(self):
+ return f'{self.name}'
+
+
+class Activity(models.Model):
+ type = models.TextField()
+
+ def __str__(self):
+ return f'{self.type}'
+
+
+class ActivityLog(models.Model):
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
+
+ start_time = models.DateTimeField()
+ end_time = models.DateTimeField()
+ logged_at = models.DateTimeField(auto_now_add=True)
+
+ def clean(self):
+ if self.start_time >= self.end_time:
+ raise ValidationError(f'Illegal times: {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}')
+
+ 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/urls.py b/web/urls.py
new file mode 100644
index 0000000..5248f10
--- /dev/null
+++ b/web/urls.py
@@ -0,0 +1,19 @@
+from django.urls import path
+
+from web import views
+
+urlpatterns = [
+ path('',
+ views.UserListView.as_view(), name='users'),
+ path('users/new',
+ views.UserCreateView.as_view(), name='users_new'),
+ path('users//',
+ views.UserDetailView.as_view(), name='user'),
+
+ path('users//activity_log/new',
+ views.ActivityLogCreateView.as_view(), name='activity_log_create'),
+ path('users//activity_log//update',
+ views.ActivityLogUpdateView.as_view(), name='activity_log_update'),
+ path('users//activity_log//delete',
+ views.ActivityLogDeleteView.as_view(), name='activity_log_delete'),
+]
diff --git a/web/views.py b/web/views.py
new file mode 100644
index 0000000..f6e5f33
--- /dev/null
+++ b/web/views.py
@@ -0,0 +1,56 @@
+from django.urls import reverse_lazy, reverse
+from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
+
+from web.models import Activity, User, ActivityLog
+
+
+class UserListView(ListView):
+ model = User
+
+
+class UserCreateView(CreateView):
+ model = User
+ fields = ['name', 'email']
+ success_url = reverse_lazy('users')
+
+
+class UserDetailView(DetailView):
+ model = User
+ context_object_name = 'user'
+ pk_url_kwarg = 'user_id'
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['activity_logs'] = ActivityLog.objects.filter(user=self.object).order_by('start_time')
+ return context
+
+
+class ActivityLogCreateView(CreateView):
+ model = ActivityLog
+ fields = ['activity', 'user', 'start_time', 'end_time']
+
+ def get_success_url(self):
+ return reverse('user', kwargs={
+ 'user_id': self.object.user.id,
+ })
+
+
+class ActivityLogUpdateView(UpdateView):
+ model = ActivityLog
+ fields = ['activity', 'user', 'start_time', 'end_time']
+ pk_url_kwarg = 'activity_log_id'
+
+ def get_success_url(self):
+ return reverse('user', kwargs={
+ 'user_id': self.object.user.id,
+ })
+
+
+class ActivityLogDeleteView(DeleteView):
+ model = ActivityLog
+ pk_url_kwarg = 'activity_log_id'
+
+ def get_success_url(self):
+ return reverse('user', kwargs={
+ 'user_id': self.object.user.id,
+ })