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 @@ + + + + + Новая запись + + + +
{% csrf_token %} + {{ form.as_p }} + +
+ + + 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 %} + + + + + + + + {% endfor %} +
+ Активность + + Когда начал + + Когда закончил + + +
+ {{ activity_log.activity }} + + {{ activity_log.start_time }} + + {{ activity_log.end_time }} + +
{% csrf_token %} + +
+
+
{% csrf_token %} + +
+
+{% 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 @@ + + + + + Новый пользователь + + + +
    {% csrf_token %} + {{ form.as_p }} + +
    + + + 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, + })