├── .gitattributes ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── API_DATA.md ├── LICENSE ├── NAMING.txt ├── Procfile ├── Procfile_celery ├── Procfile_local ├── README.md ├── Vagrantfile ├── analytics ├── __init__.py └── events │ ├── __init__.py │ ├── analytics.py │ ├── tests │ ├── __init__.py │ ├── test_analytics.py │ └── test_dataframe_builders.py │ └── utils │ ├── __init__.py │ ├── aggregate_dataframe_builders.py │ └── dataframe_builders.py ├── apis ├── __init__.py ├── apps.py ├── betterself │ ├── __init__.py │ └── v1 │ │ ├── __init__.py │ │ ├── analytics │ │ ├── __init__.py │ │ ├── tests.py │ │ └── views.py │ │ ├── constants.py │ │ ├── correlations │ │ ├── __init__.py │ │ ├── serializers.py │ │ ├── tests.py │ │ └── views.py │ │ ├── events │ │ ├── __init__.py │ │ ├── filters.py │ │ ├── serializers.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_serializers.py │ │ │ └── views │ │ │ │ ├── __init__.py │ │ │ │ ├── test_productivity.py │ │ │ │ ├── test_supplement_logs.py │ │ │ │ ├── test_supplement_reminders.py │ │ │ │ ├── test_user_activities.py │ │ │ │ └── test_views.py │ │ └── views.py │ │ ├── exports │ │ ├── __init__.py │ │ ├── tests.py │ │ └── views.py │ │ ├── mood │ │ ├── __init__.py │ │ ├── filters.py │ │ ├── serializers.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_serializers.py │ │ │ └── test_views.py │ │ └── views.py │ │ ├── signout │ │ ├── __init__.py │ │ └── views.py │ │ ├── signup │ │ ├── __init__.py │ │ ├── fixtures │ │ │ ├── __init__.py │ │ │ ├── builders.py │ │ │ ├── factories.py │ │ │ └── fixtures.py │ │ ├── tasks.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_builders.py │ │ │ ├── test_serializers.py │ │ │ └── test_views.py │ │ └── views.py │ │ ├── sleep │ │ ├── __init__.py │ │ ├── tests.py │ │ └── views.py │ │ ├── supplements │ │ ├── __init__.py │ │ ├── filters.py │ │ ├── serializers.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_serializers.py │ │ │ └── test_views.py │ │ └── views.py │ │ ├── tests │ │ ├── __init__.py │ │ ├── mixins │ │ │ ├── __init__.py │ │ │ ├── test_delete_requests.py │ │ │ ├── test_get_requests.py │ │ │ ├── test_no_data_requests.py │ │ │ ├── test_post_requests.py │ │ │ └── test_put_requests.py │ │ ├── test_base.py │ │ └── test_generic_resources.py │ │ ├── urls.py │ │ ├── users │ │ ├── __init__.py │ │ ├── serializers.py │ │ ├── tests.py │ │ └── views.py │ │ └── utils │ │ ├── __init__.py │ │ └── views.py ├── fitbit │ ├── __init__.py │ ├── admin.py │ ├── defaults.py │ ├── fixtures │ │ ├── __init__.py │ │ └── initial_data.json │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_data_migrations_for_fitbit_types.py │ │ ├── 0003_auto_20170822_0324.py │ │ ├── 0004_auto_20170822_0325.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tasks.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── github │ ├── __init__.py │ └── views.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20170812_0504.py │ └── __init__.py ├── rescuetime │ ├── __init__.py │ ├── importers │ │ ├── __init__.py │ │ ├── historical_daily_importer.py │ │ ├── test_utils.py │ │ └── utils.py │ ├── tasks.py │ └── v1 │ │ ├── __init__.py │ │ ├── serializers.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py ├── twilio │ ├── __init__.py │ ├── tasks.py │ ├── tests.py │ ├── urls.py │ └── views.py └── urls.py ├── app.json ├── assets ├── css │ ├── coreui-style.css │ ├── font-awesome.css │ ├── font-awesome.min.css │ ├── glyphicons-filetypes.css │ ├── glyphicons-social.css │ ├── glyphicons.css │ ├── react-datetime-picker.css │ ├── react-yearly-calendar.css │ ├── simple-line-icons.css │ └── special.css ├── fonts │ ├── FontAwesome.otf │ ├── Simple-Line-Icons.eot │ ├── Simple-Line-Icons.svg │ ├── Simple-Line-Icons.ttf │ ├── Simple-Line-Icons.woff │ ├── Simple-Line-Icons.woff2 │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ ├── glyphicons-filetypes-regular.eot │ ├── glyphicons-filetypes-regular.svg │ ├── glyphicons-filetypes-regular.ttf │ ├── glyphicons-filetypes-regular.woff │ ├── glyphicons-filetypes-regular.woff2 │ ├── glyphicons-regular.eot │ ├── glyphicons-regular.svg │ ├── glyphicons-regular.ttf │ ├── glyphicons-regular.woff │ ├── glyphicons-regular.woff2 │ ├── glyphicons-social-regular.eot │ ├── glyphicons-social-regular.svg │ ├── glyphicons-social-regular.ttf │ ├── glyphicons-social-regular.woff │ └── glyphicons-social-regular.woff2 ├── img │ ├── Sorting icons.psd │ ├── icons │ │ ├── 001-brain.png │ │ └── small_brain.svg │ ├── logos │ │ └── white_logo_transparent_background_small.png │ ├── navigation │ │ ├── fast-forward.svg │ │ ├── left-arrow.svg │ │ └── restart.svg │ ├── rescuetime │ │ ├── rescuetime_description.jpg │ │ ├── rescuetime_example.jpg │ │ ├── rescuetime_example_breakdown.jpg │ │ └── rescuetime_logo.jpg │ ├── select.png │ ├── select2-spinner.gif │ ├── select2.png │ ├── select2x2.png │ ├── sort_asc.png │ ├── sort_asc_disabled.png │ ├── sort_both.png │ ├── sort_desc.png │ └── sort_desc_disabled.png └── js │ ├── analytics │ ├── base.js │ ├── constants.js │ ├── productivity.js │ └── sleep.js │ ├── authentication │ ├── auth.js │ ├── authentication.js │ ├── login.js │ └── logout.js │ ├── constants │ ├── charts.js │ ├── dates_and_times.js │ ├── designs.js │ ├── image_paths.js │ ├── loading_styles.js │ ├── productivity.js │ ├── requests.js │ └── urls.jsx │ ├── create_demo_user │ └── create_demo_user.js │ ├── daily_overview │ ├── constants.js │ ├── daily_overview_view.js │ ├── supplements_and_events_historical_tables.js │ └── supplements_and_events_widgets.js │ ├── dashboard.js │ ├── export │ └── export.js │ ├── fitbit │ └── fitbit.js │ ├── footer │ └── footer.js │ ├── header │ ├── css │ │ ├── external_header.css │ │ ├── external_header_hack.css │ │ └── internal_header.css │ ├── external_header.js │ └── internal_header.js │ ├── heart_rate_log │ └── legacy_heart_rate_log.js │ ├── home │ ├── components │ │ ├── FontAwesomeIconBox.js │ │ ├── HomePageAnalyticsDescriptionSection.js │ │ ├── HomePageDescription.js │ │ ├── HomePageFeaturesShortDescriptionSection.js │ │ └── OpenSourceAndApplicationSupport.js │ ├── css │ │ ├── FontAwesomeIconBox.css │ │ ├── HomePageAnalyticsDescriptionSection.css │ │ ├── HomePageDescription.css │ │ ├── HomePageFeaturesShortDescriptionSection.css │ │ └── OpenSourceAndApplicationSupport.css │ ├── home.js │ └── img │ │ ├── #dark-logs-bg.jpg │ │ ├── dark-logs-bg-2.jpg │ │ ├── dark-logs-bg-3.jpg │ │ ├── dark-logs-bg-4.jpg │ │ ├── dark-logs-bg-5.jpg │ │ ├── dark-logs-bg-6.jpg │ │ ├── dark-logs-bg-8.jpg │ │ ├── dark-logs-bg-9.jpg │ │ └── dark-logs-bg.jpg │ ├── index.js │ ├── mood_log │ ├── add_mood_event.js │ ├── constants.js │ ├── mood_events_table.js │ └── mood_events_view.js │ ├── productivity_log │ ├── add_productivity_event.js │ ├── constants.js │ ├── productivity_event_view.js │ └── productivity_table.js │ ├── release_notes │ └── release_notes.js │ ├── resources_table │ ├── multi_tab_table.js │ ├── resource_table.js │ └── resource_view.js │ ├── routing │ └── routing_utils.js │ ├── sidebar │ ├── sidebar.css │ └── sidebar.js │ ├── signup │ └── signup.js │ ├── sleep_log │ ├── add_sleep_event.js │ ├── constants.js │ ├── sleep_events_table.js │ └── sleep_events_view.js │ ├── supplement_overview │ ├── constants.js │ ├── supplement_overview.js │ └── supplements_overview_charts.js │ ├── supplement_reminders │ ├── add_supplement_reminder.js │ ├── constants.js │ ├── supplement_reminder_table.js │ └── supplement_reminders_view.js │ ├── supplements │ ├── add_supplement.js │ ├── add_supplement_modal.js │ ├── constants.js │ ├── supplements_table.js │ └── supplements_view.js │ ├── supplements_log │ ├── add_supplement_log.js │ ├── constants.js │ ├── supplement_log_table.js │ └── supplement_log_view.js │ ├── supplements_stacks │ ├── add_supplement_stack.js │ ├── constants.js │ ├── supplement_stack_table.js │ └── supplement_stack_view.js │ ├── user_activities_events_log │ ├── add_user_activities_event.js │ ├── constants.js │ ├── user_activites_events_view.js │ └── user_activities_events_table.js │ ├── user_activities_log │ ├── add_user_activity.js │ ├── constants.js │ ├── user_activities_table.js │ └── user_activities_view.js │ ├── user_settings │ └── user_settings.js │ └── utils │ ├── fetch_utils.js │ └── select_utils.js ├── betterself ├── __init__.py ├── base_models.py ├── celery.py ├── contrib │ ├── __init__.py │ └── sites │ │ ├── __init__.py │ │ └── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_set_site_domain_and_name.py │ │ ├── 0003_auto_20160524_0259.py │ │ └── __init__.py ├── static │ ├── bootstrap │ │ ├── css │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-theme.css.map │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap-theme.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ └── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ ├── css │ │ ├── pinegrow │ │ │ ├── header-css-bundled.css │ │ │ └── legacy.css │ │ └── project.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── images │ │ ├── dashboard │ │ │ ├── dashboard_example.png │ │ │ ├── dashboard_example_2.png │ │ │ ├── dashboard_example_3.png │ │ │ ├── dashboard_example_4.png │ │ │ ├── dashboard_example_5.jpg │ │ │ ├── dashboard_example_6.jpg │ │ │ ├── dashboard_heart_rate.jpg │ │ │ └── dashboard_supplements_events_history.jpg │ │ ├── header │ │ │ └── control_panel │ │ │ │ ├── brain.png │ │ │ │ ├── heart.png │ │ │ │ └── heart_2.svg │ │ ├── login │ │ │ └── login_side_photo.jpeg │ │ └── logos │ │ │ └── logojoy │ │ │ ├── favicon.png │ │ │ ├── favicon_legacy.png │ │ │ ├── favicon_transparent.png │ │ │ ├── png │ │ │ ├── color_logo_transparent_background.png │ │ │ ├── color_logo_transparent_background_small.png │ │ │ ├── dark_logo_transparent_background.png │ │ │ ├── dark_logo_transparent_background_small.png │ │ │ ├── white_logo_color_background.png │ │ │ ├── white_logo_color_background_small.png │ │ │ ├── white_logo_transparent_background.png │ │ │ └── white_logo_transparent_background_small.png │ │ │ └── svg │ │ │ ├── color_logo_transparent_background.svg │ │ │ ├── dark_logo_transparent_background.svg │ │ │ ├── white_logo_color_background.svg │ │ │ └── white_logo_transparent_background.svg │ └── sass │ │ └── project.scss ├── templates │ ├── 404.html │ ├── 500.html │ ├── account │ │ ├── base.html │ │ ├── email.html │ │ ├── email_confirm.html │ │ ├── email_confirmed.html │ │ ├── login.html │ │ ├── logout.html │ │ ├── password_change.html │ │ ├── password_reset.html │ │ ├── password_reset_done.html │ │ ├── password_reset_from_key.html │ │ ├── password_reset_from_key_done.html │ │ ├── password_set.html │ │ ├── signup.html │ │ ├── signup_closed.html │ │ ├── verification_sent.html │ │ └── verified_email_required.html │ ├── base.html │ ├── react │ │ ├── dashboard.html │ │ ├── home.html │ │ └── signup.html │ └── users │ │ ├── user_detail.html │ │ ├── user_form.html │ │ └── user_list.html ├── tests.py ├── users │ ├── __init__.py │ ├── adapters.py │ ├── admin.py │ ├── fixtures │ │ ├── __init__.py │ │ └── factories.py │ ├── migrations │ │ ├── 0001_squashed_0014_rename_phone_number_model.py │ │ └── __init__.py │ ├── models.py │ ├── tests │ │ ├── __init__.py │ │ ├── mixins │ │ │ ├── __init__.py │ │ │ └── test_mixins.py │ │ ├── test_admin.py │ │ ├── test_models.py │ │ └── test_views.py │ ├── urls.py │ └── views.py └── utils │ ├── __init__.py │ ├── api_utils.py │ ├── date_utils.py │ ├── django_utils.py │ ├── pandas_utils.py │ └── tests │ ├── __init__.py │ ├── test_api_utils.py │ ├── test_date_utils.py │ └── test_pandas_utils.py ├── config ├── __init__.py ├── development │ └── vagrant │ │ ├── developer_aliases │ │ └── provision_bootstrap.sh ├── pagination.py ├── settings │ ├── __init__.py │ ├── common.py │ ├── constants.py │ ├── local.py │ ├── production.py │ ├── settings_testing.py │ └── staging.py ├── urls.py └── wsgi.py ├── constants.py ├── events ├── __init__.py ├── admin.py ├── apps.py ├── fixtures │ ├── __init__.py │ ├── factories.py │ └── mixins.py ├── migrations │ ├── 0001_squashed_0028_auto_20171113_0332.py │ ├── 0002_add_notes.py │ ├── 0003_auto_20171221_0336.py │ ├── 0004_auto_20171223_0859.py │ └── __init__.py ├── models.py ├── tests.py └── utils │ ├── __init__.py │ └── default_events_builder.py ├── manage.py ├── package.json ├── requirements.txt ├── requirements ├── base.txt ├── local.txt ├── production.txt └── test.txt ├── runtime.txt ├── scripts ├── __init__.py └── quick_dataframe_analysis.py ├── setup.cfg ├── supplements ├── __init__.py ├── admin.py ├── apps.py ├── fixtures │ ├── __init__.py │ ├── factories.py │ └── mixins.py ├── migrations │ ├── 0001_squashed_0007_auto_20171023_0402.py │ ├── 0002_supplement_notes.py │ └── __init__.py ├── models.py └── tests.py ├── utility ├── install_os_dependencies.sh ├── install_python_dependencies.sh ├── requirements.apt └── requirements.apt.xenial ├── vendors ├── __init__.py ├── admin.py ├── apps.py ├── fixtures │ ├── __init__.py │ ├── factories.py │ └── mixins.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20170521_2251.py │ └── __init__.py ├── models.py └── tests.py ├── webpack.config.js └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | static/* linguist-vendored 3 | css/* linguist-vendored 4 | betterself/static/css/* linguist-vendored 5 | betterself/static/bootstrap/* linguist-vendored 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | 64 | # Personal Data 65 | personal_fixtures/ 66 | activity_log.xlsx 67 | 68 | # PyCharm 69 | .idea/ 70 | 71 | # Mac 72 | .DS_Store 73 | 74 | # collect static should be ignored 75 | staticfiles/ 76 | 77 | # don't include the virutalenv that's being used 78 | config/development/virtualenv/ 79 | 80 | # Ignore Vagrant 81 | .vagrant/ 82 | 83 | # ignore your local development test script 84 | *local_dev_test_script* 85 | 86 | *output.csv* 87 | assets/bundles 88 | node_modules/ 89 | 90 | webpack-stats.json 91 | 92 | # secret api settings 93 | local_secret_settings.py 94 | 95 | # output of rescutimetime script 96 | historical_rescuetime.csv 97 | 98 | celerybeat.pid 99 | 100 | scripts/database_restore.sh 101 | 102 | .env 103 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: git://github.com/pre-commit/pre-commit-hooks 3 | sha: v0.9.3 4 | hooks: 5 | # keep ids alphabetical! see http://pre-commit.com/hooks.html 6 | - id: check-merge-conflict 7 | - id: check-json 8 | - id: check-added-large-files 9 | - id: check-xml 10 | - id: debug-statements 11 | files: \.py$ 12 | - id: double-quote-string-fixer 13 | - id: end-of-file-fixer 14 | files: \.(py|sh)$ 15 | - id: flake8 16 | files: \.py$ 17 | args: ['--ignore=E128,W503'] 18 | exclude: local\.py$ # ignore config settings 19 | - id: name-tests-test 20 | files: tests/.+\.py$ 21 | args: ['--django'] # Use args: ['--django'] to match test*.py instead. 22 | - id: trailing-whitespace 23 | files: \.(py|sh|yaml|txt)$ 24 | - id: requirements-txt-fixer 25 | - repo: https://github.com/awebdeveloper/pre-commit-prettier 26 | sha: v0.0.1 27 | hooks: 28 | - id: prettier 29 | additional_dependencies: ['prettier@1.1.0'] 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | language: python 3 | cache: 4 | pip: true 5 | services: 6 | - redis-server 7 | python: 8 | - "3.6.2" 9 | before_script: 10 | - psql -c 'create database travisci;' -U postgres 11 | install: 12 | - pip install -r requirements/test.txt 13 | script: "python manage.py test --parallel" 14 | notifications: 15 | email: 16 | recipients: 17 | - jeffshek@gmail.com 18 | on_success: never # default: change 19 | on_failure: always # default: always 20 | -------------------------------------------------------------------------------- /API_DATA.md: -------------------------------------------------------------------------------- 1 | # API Response Conventions 2 | 3 | 2017-09-22 - I've messed up quite a bit and a lot of my API responses are all over the place. A lot of them are a bit too specific to views, so this is a doc to write some more consistent API structures. 4 | 5 | 1. Instead of returning dictionaries, return list of dictionaries / objects 6 | 7 | * The order is apparent. 8 | * It gives meta information frequently necessary 9 | 10 | ~~~ 11 | result = [ 12 | { 13 | "key": "key_value", 14 | "label": "BCAA", 15 | "value": 55, 16 | "data_type": "string", 17 | } 18 | ] 19 | ~~~ 20 | 21 | 2. Because of #1 never use simple dictionary responses of {k:v} 22 | 3. Use ISO for datetime, never use epoch. Use UTC for all time stamps. 23 | 4. Use "label" as the string to show the display value 24 | 5. Provide data_type so that the frontend has an idea on how to render 25 | 6. Try to generally order them by ascending order (you break this rule in too many places). Put more sort filters so that you can deal with this. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | Copyright (c) 2016, Jeff Shek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | -------------------------------------------------------------------------------- /NAMING.txt: -------------------------------------------------------------------------------- 1 | //////////// 2 | // Some naming patterns because you are all over the place ... 3 | //////////// 4 | 5 | # Put Resource First and THEN generic Nouns 6 | # MAJOR : Generic Nouns at the end of things 7 | # - ie. log / charts / tabs / history 8 | selectedSupplementsCorrelationsTab 9 | sleepHistory 10 | 11 | # try to have correlation after the resource even though it will sound odd sometimes 12 | supplementCorrelationCharts 13 | 14 | # try to make restful resource as early in the word as possible 15 | supplementCorrelationCharts 16 | 17 | # Always put User in front of any resource where it makes sense 18 | UserActivities 19 | 20 | # something that indicates selected goes before everything else! 21 | selectedSupplementsCorrelations 22 | 23 | # When in doubt of singular versus plural, just pick plural, so lean toward plural 24 | UserActivitiesEvents 25 | 26 | # when comparing things like Sleep and Supplements, pick the resource that's closest to the page first 27 | # so if you're on sleep.js 28 | sleepSupplementsCorrelations 29 | 30 | # or if you can ... remove sleep from the name altogether (if you're in the sleep folder) 31 | supplementsCorrelations (does this sound better with supplementCorrelations) instead? 32 | 33 | # try to put data near the end of variables 34 | historyChartData 35 | 36 | # try to call the javascript files that just hold abstract classes 37 | base.js 38 | 39 | # try to make correlations plural 40 | SleepSupplementsCorrelationsTests 41 | 42 | # try to have aggregates/averages come after the resource 43 | # make sure its plural 44 | ProductivityLogAggregatesView 45 | SleepAggregatesView 46 | SleepAveragesView 47 | 48 | # try to have the full django model in the name, easier to decide on that pattern 49 | # make the resource ... plural 50 | ProductivityLogsSupplementsCorrelationsView 51 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: python manage.py migrate 2 | web: gunicorn config.wsgi:application 3 | worker: env > .env; env PYTHONUNBUFFERED=true honcho start -f Procfile_celery 2>&1 4 | -------------------------------------------------------------------------------- /Procfile_celery: -------------------------------------------------------------------------------- 1 | celery_worker: celery worker --app=betterself 2 | celery_beat: celery beat --app=betterself 3 | -------------------------------------------------------------------------------- /Procfile_local: -------------------------------------------------------------------------------- 1 | celery_worker: celery purge -A betterself -f && celery worker --app=betterself 2 | celery_beat: celery beat --app=betterself 3 | web: python manage.py runserver [::]:9000 4 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure(2) do |config| 5 | config.vm.box = "ubuntu/trusty64" 6 | 7 | # Map Django's default port to 9000 8 | config.vm.network "forwarded_port", guest: 9000, host: 9000 9 | config.vm.network "forwarded_port", guest: 5432, host: 5432 10 | 11 | config.vm.network :private_network, ip: '172.28.128.5' 12 | config.vm.synced_folder ".", "/betterself", type: "nfs" 13 | 14 | # Sync as NFS for speed (NFS doesn't work for PCs, but vagrant should default to something else) 15 | # 1. to do NFS, need a private network 16 | # 2. also use bridge to create a private network this allows you to have a postgres instance 17 | config.vm.network "private_network", type: "dhcp", bridge: "en1: Wi-Fi (AirPort)" 18 | 19 | # don't need this since this is not used to provision boxes 20 | config.ssh.insert_key = false 21 | 22 | config.ssh.forward_agent = true 23 | 24 | # pandas takes a lot of memory to assemble 25 | config.vm.provider "virtualbox" do |v| 26 | # the fact that you can/need this much memory in pandas kinda scares me 27 | v.memory = 4096 28 | v.cpus = 2 29 | end 30 | 31 | # Copy a bash_profile config that can be customized 32 | config.vm.provision "file", source: "config/development/vagrant/developer_aliases", destination: "~/.bash_profile" 33 | 34 | # Provision scripts that install necessary requirements 35 | config.vm.provision "shell", path: "config/development/vagrant/provision_bootstrap.sh" 36 | 37 | # to deal with the unbelievable agony of trying to figure out why UTC was off by 14 minutes when it ended up being vagrant sleeping doesn't count time 38 | config.vm.provider 'virtualbox' do |vb| 39 | vb.customize [ "guestproperty", "set", :id, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold", 1000 ] 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /analytics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/analytics/__init__.py -------------------------------------------------------------------------------- /analytics/events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/analytics/events/__init__.py -------------------------------------------------------------------------------- /analytics/events/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/analytics/events/tests/__init__.py -------------------------------------------------------------------------------- /analytics/events/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/analytics/events/utils/__init__.py -------------------------------------------------------------------------------- /apis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/__init__.py -------------------------------------------------------------------------------- /apis/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApisConfig(AppConfig): 5 | name = 'apis' 6 | -------------------------------------------------------------------------------- /apis/betterself/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/analytics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/analytics/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/constants.py: -------------------------------------------------------------------------------- 1 | from events.models import SupplementLog 2 | from supplements.models import IngredientComposition, Supplement, Ingredient, Measurement 3 | from vendors.models import Vendor 4 | 5 | VALID_REST_RESOURCES = [ 6 | SupplementLog, 7 | Supplement, 8 | IngredientComposition, 9 | Ingredient, 10 | Measurement, 11 | Vendor 12 | ] 13 | 14 | # a lot of frontend (react) depends on a uniqueKey to render rows, in this case, do something here that makes rendering 15 | # all the rows a little bit easier. in most circumstances, for any resources that are directly related to a model 16 | # uuid is fine, but not all resources are django models, so uniqueKey comes in handy 17 | UNIQUE_KEY_CONSTANT = 'uniqueKey' 18 | 19 | DAILY_FREQUENCY = 'daily' 20 | MONTHLY_FREQUENCY = 'monthly' 21 | -------------------------------------------------------------------------------- /apis/betterself/v1/correlations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/correlations/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/correlations/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from constants import SLEEP_MINUTES_COLUMN, VERY_PRODUCTIVE_TIME_LABEL, PRODUCTIVITY_DRIVERS_KEYS 4 | 5 | 6 | class ProductivityRequestParamsSerializer(serializers.Serializer): 7 | correlation_lookback = serializers.IntegerField(default=60, min_value=1, max_value=365) 8 | cumulative_lookback = serializers.IntegerField(default=1, min_value=1, max_value=365) 9 | correlation_driver = serializers.ChoiceField(choices=PRODUCTIVITY_DRIVERS_KEYS, 10 | default=VERY_PRODUCTIVE_TIME_LABEL) 11 | 12 | 13 | class SleepRequestParamsSerializer(serializers.Serializer): 14 | correlation_lookback = serializers.IntegerField(default=60, min_value=1, max_value=365) 15 | cumulative_lookback = serializers.IntegerField(default=1, min_value=1, max_value=365) 16 | # Kind of odd, but the only correlation_driver should be SLEEP_MINUTES_COLUMN unlike productivity 17 | # in the future this might change because FitBit has tiered levels of sleep, but you ain't there yet 18 | correlation_driver = serializers.ChoiceField(choices=[SLEEP_MINUTES_COLUMN], default=SLEEP_MINUTES_COLUMN) 19 | 20 | 21 | # TODO 22 | # desired names are 23 | # rollingWindow 24 | # lookbackHistory 25 | -------------------------------------------------------------------------------- /apis/betterself/v1/events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/events/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/events/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/events/tests/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/events/tests/test_serializers.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from rest_framework import serializers 3 | 4 | from apis.betterself.v1.events.serializers import valid_daily_max_minutes, SupplementLogReadOnlySerializer 5 | from events.fixtures.factories import SupplementEventFactory 6 | from supplements.fixtures.factories import SupplementFactory 7 | 8 | 9 | class TestSerializerUtils(TestCase): 10 | @staticmethod 11 | def test_regular_max_minutes(): 12 | valid_daily_max_minutes(600) 13 | 14 | def test_more_than_daily_max_minutes(self): 15 | with self.assertRaises(serializers.ValidationError): 16 | valid_daily_max_minutes(3601) 17 | 18 | def test_less_than_zero_max_minutes(self): 19 | with self.assertRaises(serializers.ValidationError): 20 | valid_daily_max_minutes(-50) 21 | 22 | 23 | class TestSupplementEventSerializer(TestCase): 24 | def test_supplement_serializer(self): 25 | supplement = SupplementFactory(notes='gibberish') 26 | event = SupplementEventFactory(supplement=supplement) 27 | serializer = SupplementLogReadOnlySerializer(event) 28 | 29 | dict_responses = serializer.data 30 | 31 | self.assertEqual(dict_responses['uuid'], str(event.uuid)) 32 | self.assertEqual(dict_responses['notes'], event.notes) 33 | self.assertEqual(dict_responses['quantity'], event.quantity) 34 | -------------------------------------------------------------------------------- /apis/betterself/v1/events/tests/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/events/tests/views/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/exports/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/exports/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/exports/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.test import TestCase 3 | from django.urls import reverse 4 | from rest_framework.test import APIClient 5 | 6 | from apis.betterself.v1.signup.fixtures.builders import DemoHistoricalDataBuilder 7 | 8 | User = get_user_model() 9 | 10 | 11 | class UserExportAllDataTests(TestCase): 12 | @classmethod 13 | def setUpTestData(cls): 14 | cls.url = reverse('api-user-export-all-data') 15 | cls.user = User.objects.create(username='demo') 16 | 17 | builder = DemoHistoricalDataBuilder(cls.user) 18 | builder.create_historical_fixtures() 19 | 20 | super().setUpTestData() 21 | 22 | def setUp(self): 23 | self.client = APIClient() 24 | self.client.force_authenticate(self.user) 25 | 26 | def test_export_view(self): 27 | # TODO - switch to loading the response in an excel reader 28 | # and measuring the columns are equal to what one would expect 29 | response = self.client.get(self.url) 30 | response_content_disposition = response.get('Content-Disposition') 31 | 32 | self.assertEqual(response.status_code, 200) 33 | self.assertTrue('attachment; filename=' in response_content_disposition) 34 | self.assertTrue('xlsx' in response_content_disposition) 35 | 36 | def test_export_view_with_user_and_no_data(self): 37 | user = User.objects.create(username='demo-with-no-data') 38 | client = APIClient() 39 | client.force_authenticate(user) 40 | 41 | response = client.get(self.url) 42 | self.assertEqual(response.status_code, 200) 43 | 44 | def test_export_view_not_logged_in(self): 45 | client = APIClient() 46 | response = client.get(self.url) 47 | # not authorized 48 | self.assertEqual(response.status_code, 401) 49 | -------------------------------------------------------------------------------- /apis/betterself/v1/mood/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/mood/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/mood/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters.rest_framework import FilterSet 2 | 3 | from events.models import UserMoodLog 4 | 5 | 6 | class UserMoodLogFilter(FilterSet): 7 | class Meta: 8 | model = UserMoodLog 9 | fields = [ 10 | 'uuid', 11 | 'value', 12 | 'time', 13 | ] 14 | -------------------------------------------------------------------------------- /apis/betterself/v1/mood/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from betterself.utils.date_utils import get_current_utc_time_and_tz 4 | from events.models import UserMoodLog, INPUT_SOURCES_TUPLES, WEB_INPUT_SOURCE 5 | 6 | 7 | class MoodReadOnlySerializer(serializers.ModelSerializer): 8 | notes = serializers.CharField(required=False) 9 | 10 | class Meta: 11 | model = UserMoodLog 12 | fields = ('value', 'time', 'notes', 'source', 'uuid') 13 | 14 | 15 | class MoodCreateUpdateSerializer(serializers.ModelSerializer): 16 | uuid = serializers.UUIDField(required=False, read_only=True) 17 | value = serializers.IntegerField(max_value=10, min_value=1) 18 | notes = serializers.CharField(required=False) 19 | source = serializers.ChoiceField(INPUT_SOURCES_TUPLES, default=WEB_INPUT_SOURCE) 20 | time = serializers.DateTimeField(default=get_current_utc_time_and_tz) 21 | 22 | class Meta: 23 | fields = ('value', 'time', 'notes', 'source', 'uuid') 24 | model = UserMoodLog 25 | 26 | def create(self, validated_data): 27 | user = self.context.get('user') or self.context['request'].user 28 | create_model = self.Meta.model 29 | time = validated_data.pop('time') 30 | 31 | obj, _ = create_model.objects.update_or_create( 32 | user=user, 33 | time=time, 34 | defaults=validated_data) 35 | 36 | return obj 37 | 38 | def update(self, instance, validated_data): 39 | instance.value = validated_data.get('value', instance.value) 40 | instance.source = validated_data.get('source', instance.source) 41 | instance.notes = validated_data.get('notes', instance.notes) 42 | instance.time = validated_data.get('time', instance.time) 43 | instance.save() 44 | return instance 45 | -------------------------------------------------------------------------------- /apis/betterself/v1/mood/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/mood/tests/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/mood/tests/test_views.py: -------------------------------------------------------------------------------- 1 | from dateutil.relativedelta import relativedelta 2 | from django.contrib.auth import get_user_model 3 | 4 | from apis.betterself.v1.tests.mixins.test_get_requests import GetRequestsTestsMixin 5 | from apis.betterself.v1.tests.mixins.test_post_requests import PostRequestsTestsMixin 6 | from apis.betterself.v1.tests.mixins.test_put_requests import PUTRequestsTestsMixin 7 | from apis.betterself.v1.tests.test_base import BaseAPIv1Tests 8 | from betterself.utils.date_utils import get_current_utc_time_and_tz 9 | from events.fixtures.factories import UserMoodLogFactory 10 | from events.models import UserMoodLog 11 | 12 | User = get_user_model() 13 | 14 | 15 | class TestUserMoodLogViews(BaseAPIv1Tests, GetRequestsTestsMixin, PostRequestsTestsMixin, PUTRequestsTestsMixin): 16 | TEST_MODEL = UserMoodLog 17 | PAGINATION = True 18 | FIXTURES_SIZE = 50 19 | 20 | DEFAULT_POST_PARAMS = { 21 | 'value': 5 22 | } 23 | 24 | @classmethod 25 | def setUpTestData(cls): 26 | super().setUpTestData() 27 | 28 | now = get_current_utc_time_and_tz() 29 | for subtract_seconds in range(0, cls.FIXTURES_SIZE): 30 | time = now - relativedelta(seconds=subtract_seconds) 31 | UserMoodLogFactory(time=time, user=cls.user_1) 32 | 33 | def test_valid_get_request_with_params_filters_correctly(self): 34 | request_parameters = {'value': 1} 35 | super().test_valid_get_request_with_params_filters_correctly(request_parameters) 36 | 37 | def test_valid_get_request_for_key_in_response(self): 38 | key = 'value' 39 | super().test_valid_get_request_for_key_in_response(key) 40 | -------------------------------------------------------------------------------- /apis/betterself/v1/mood/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListCreateAPIView 2 | 3 | from apis.betterself.v1.mood.filters import UserMoodLogFilter 4 | from apis.betterself.v1.mood.serializers import MoodReadOnlySerializer, MoodCreateUpdateSerializer 5 | from apis.betterself.v1.utils.views import ReadOrWriteSerializerChooser, UUIDDeleteMixin, UUIDUpdateMixin 6 | from config.pagination import ModifiedPageNumberPagination 7 | from events.models import UserMoodLog 8 | 9 | 10 | class UserMoodViewSet(ListCreateAPIView, ReadOrWriteSerializerChooser, UUIDDeleteMixin, UUIDUpdateMixin): 11 | model = UserMoodLog 12 | pagination_class = ModifiedPageNumberPagination 13 | read_serializer_class = MoodReadOnlySerializer 14 | write_serializer_class = MoodCreateUpdateSerializer 15 | update_serializer_class = MoodCreateUpdateSerializer 16 | filter_class = UserMoodLogFilter 17 | 18 | def get_serializer_class(self): 19 | return self._get_read_or_write_serializer_class() 20 | 21 | def get_queryset(self): 22 | return self.model.objects.filter(user=self.request.user) 23 | -------------------------------------------------------------------------------- /apis/betterself/v1/signout/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/signout/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/signout/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import logout 2 | from django.views.generic import RedirectView 3 | 4 | 5 | class SessionLogoutView(RedirectView): 6 | """ 7 | A view that will logout a user out and redirect to homepage. 8 | """ 9 | permanent = False 10 | query_string = True 11 | pattern_name = 'home' 12 | 13 | def get_redirect_url(self, *args, **kwargs): 14 | """ 15 | Logout user and redirect to target url. 16 | """ 17 | if self.request.user.is_authenticated(): 18 | logout(self.request) 19 | return super(SessionLogoutView, self).get_redirect_url(*args, **kwargs) 20 | -------------------------------------------------------------------------------- /apis/betterself/v1/signup/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/signup/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/signup/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/signup/fixtures/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/signup/tasks.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.utils.text import slugify 3 | from faker import Faker 4 | 5 | from apis.betterself.v1.signup.fixtures.builders import DemoHistoricalDataBuilder 6 | from betterself import celery_app 7 | from betterself.users.models import DemoUserLog 8 | 9 | User = get_user_model() 10 | 11 | 12 | @celery_app.task() 13 | def create_demo_fixtures(): 14 | fake = Faker() 15 | name = fake.name() 16 | 17 | # have username be demo-username, so demos-users are easy to tell 18 | username = 'demo-{name}'.format(name=name) 19 | username = slugify(username) 20 | 21 | # since these are demo accounts, just set the username/pass the same 22 | # so this is a really weird bug since you'd wonder why this would be a get_or_create 23 | # but faker doesn't always generate fake names in celery instances ... 24 | user, _ = User.objects.get_or_create(username=username) 25 | 26 | # create a log of this person as a demo user, otherwise we would never be able to tell if someone is a demo or not! 27 | _, created = DemoUserLog.objects.get_or_create(user=user) 28 | if not created: 29 | return 30 | 31 | fixtures_builder = DemoHistoricalDataBuilder(user, periods_back=180) 32 | fixtures_builder.create_historical_fixtures() 33 | -------------------------------------------------------------------------------- /apis/betterself/v1/signup/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/signup/tests/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/signup/tests/test_serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.test import TestCase 3 | 4 | from apis.betterself.v1.users.serializers import UserDetailsSerializer 5 | from betterself.users.models import UserPhoneNumberDetails 6 | 7 | User = get_user_model() 8 | 9 | 10 | class TestUserSerializer(TestCase): 11 | @classmethod 12 | def setUpClass(cls): 13 | super().setUpClass() 14 | 15 | @classmethod 16 | def setUpTestData(cls): 17 | cls.default_user, _ = User.objects.get_or_create(username='default') 18 | super().setUpTestData() 19 | 20 | def test_user_details_serializer(self): 21 | a_cool_user_name = 'a_cool_user_name' 22 | cool_user, _ = User.objects.get_or_create(username=a_cool_user_name) 23 | serializer = UserDetailsSerializer(cool_user) 24 | 25 | data = serializer.data 26 | 27 | self.assertEqual(data['username'], a_cool_user_name) 28 | self.assertEqual(data['phone_number'], None) 29 | self.assertEqual(data['uuid'], str(cool_user.uuid)) 30 | 31 | def test_user_details_serializer_with_phone_number(self): 32 | good_number = '+16171234567' 33 | UserPhoneNumberDetails.objects.create(user=self.default_user, phone_number=good_number) 34 | serializer = UserDetailsSerializer(self.default_user) 35 | 36 | data = serializer.data 37 | 38 | self.assertEqual(data['phone_number'], good_number) 39 | -------------------------------------------------------------------------------- /apis/betterself/v1/sleep/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/sleep/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/supplements/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/supplements/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/supplements/filters.py: -------------------------------------------------------------------------------- 1 | import django_filters 2 | from django_filters.rest_framework import FilterSet 3 | 4 | from supplements.models import IngredientComposition, Supplement, UserSupplementStack, UserSupplementStackComposition 5 | 6 | 7 | class IngredientCompositionFilter(FilterSet): 8 | ingredient_uuid = django_filters.UUIDFilter(name='ingredient__uuid') 9 | measurement_uuid = django_filters.UUIDFilter(name='measurement__uuid') 10 | 11 | class Meta: 12 | model = IngredientComposition 13 | fields = ['ingredient_uuid', 'quantity', 'measurement_uuid', 'uuid'] 14 | 15 | 16 | class SupplementFilter(FilterSet): 17 | # TODO - This doesn't support multiple compositions very well, but we'll worry about that later 18 | # Right now, only filters one 19 | ingredient_compositions_uuids = django_filters.CharFilter(name='ingredient_compositions__uuid') 20 | 21 | class Meta: 22 | model = Supplement 23 | fields = ['ingredient_compositions_uuids', 'name', 'uuid'] 24 | 25 | 26 | class UserSupplementStackFilter(FilterSet): 27 | # TODO - This doesn't support multiple supplements very well, but we'll worry about that later 28 | # Right now, only filters one 29 | supplement_uuids = django_filters.CharFilter(name='supplements__uuid') 30 | 31 | class Meta: 32 | model = UserSupplementStack 33 | fields = ['supplement_uuids', 'name', 'uuid'] 34 | 35 | 36 | class UserSupplementStackCompositionFilter(FilterSet): 37 | class Meta: 38 | model = UserSupplementStackComposition 39 | fields = ['uuid', 'quantity'] 40 | -------------------------------------------------------------------------------- /apis/betterself/v1/supplements/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/supplements/tests/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/tests/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/tests/mixins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/tests/mixins/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/tests/mixins/test_delete_requests.py: -------------------------------------------------------------------------------- 1 | from apis.betterself.v1.tests.test_base import GenericRESTMethodMixin 2 | 3 | 4 | class DeleteRequestsTestsMixinV2(GenericRESTMethodMixin): 5 | """ 6 | Because you're kind of weird/lazy, the way you handle deletes are passing a delete to the endpoint 7 | querying the object and then deleting it 8 | """ 9 | 10 | def test_delete_object(self): 11 | response = self.client_1.get(self.url) 12 | results = self._get_results_from_response(response) 13 | self.assertTrue(len(results) > 0) 14 | 15 | uuids = {item['uuid'] for item in results} 16 | 17 | for uuid in uuids: 18 | data = {'uuid': uuid} 19 | response = self.client_1.delete(self.url, data=data) 20 | self.assertEqual(response.status_code, 204, response) 21 | 22 | # now after we've deleted everything, when we try to get a get, we should get nothing 23 | response = self.client_1.get(self.url) 24 | results = self._get_results_from_response(response) 25 | 26 | self.assertEqual(len(results), 0) 27 | 28 | def test_deleting_object_that_doesnt_belong_to_you(self): 29 | response = self.client_1.get(self.url) 30 | results = self._get_results_from_response(response) 31 | original_count = len(results) 32 | 33 | uuids = {item['uuid'] for item in results} 34 | 35 | for uuid in uuids: 36 | data = {'uuid': uuid} 37 | response = self.client_2.delete(self.url, data=data) 38 | self.assertEqual(response.status_code, 404, response) 39 | 40 | response = self.client_1.get(self.url) 41 | results = self._get_results_from_response(response) 42 | after_delete_count = len(results) 43 | 44 | self.assertEqual(original_count, after_delete_count) 45 | -------------------------------------------------------------------------------- /apis/betterself/v1/tests/mixins/test_no_data_requests.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/tests/mixins/test_no_data_requests.py -------------------------------------------------------------------------------- /apis/betterself/v1/tests/test_generic_resources.py: -------------------------------------------------------------------------------- 1 | from apis.betterself.v1.constants import VALID_REST_RESOURCES 2 | from apis.betterself.v1.tests.test_base import BaseAPIv1Tests 3 | from apis.betterself.v1.urls import API_V1_LIST_CREATE_URL 4 | 5 | 6 | class GeneralAPIv1Tests(BaseAPIv1Tests): 7 | def test_fake_resources_404(self): 8 | url = API_V1_LIST_CREATE_URL.format('fake_made_up_resource') 9 | request = self.client_1.get(url) 10 | self.assertEqual(request.status_code, 404) 11 | 12 | def test_all_resources_have_valid_get(self): 13 | for resource in VALID_REST_RESOURCES: 14 | resource_name = resource.RESOURCE_NAME 15 | 16 | url = API_V1_LIST_CREATE_URL.format(resource_name) 17 | request = self.client_1.get(url) 18 | self.assertEqual(request.status_code, 200) 19 | 20 | def test_all_resources_have_resource_name(self): 21 | for resource in VALID_REST_RESOURCES: 22 | has_resource_name = getattr(resource, 'RESOURCE_NAME') 23 | self.assertTrue(has_resource_name) 24 | -------------------------------------------------------------------------------- /apis/betterself/v1/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/users/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/betterself/v1/utils/__init__.py -------------------------------------------------------------------------------- /apis/betterself/v1/utils/views.py: -------------------------------------------------------------------------------- 1 | from django.http import Http404 2 | from rest_framework.generics import get_object_or_404 3 | from rest_framework.response import Response 4 | 5 | 6 | class ReadOrWriteSerializerChooser(object): 7 | """ 8 | Mixin to decide read or write serializer class 9 | """ 10 | 11 | def _get_read_or_write_serializer_class(self): 12 | request_method = self.request.method 13 | if request_method.lower() in ['list', 'get']: 14 | return self.read_serializer_class 15 | else: 16 | return self.write_serializer_class 17 | 18 | 19 | class UUIDDeleteMixin(object): 20 | """ 21 | Mixin for API Views to allow deleting of objects 22 | """ 23 | 24 | def delete(self, request, *args, **kwargs): 25 | try: 26 | uuid = request.data['uuid'] 27 | except KeyError: 28 | raise Http404 29 | 30 | filter_params = { 31 | 'uuid': uuid, 32 | 'user': request.user 33 | } 34 | 35 | get_object_or_404(self.model, **filter_params).delete() 36 | return Response(status=204) 37 | 38 | 39 | class UUIDUpdateMixin(object): 40 | """ 41 | Mixin for API Views to allow updating of objects 42 | """ 43 | 44 | def put(self, request, *args, **kwargs): 45 | data = request.data 46 | user = request.user 47 | 48 | try: 49 | uuid = data['uuid'] 50 | except KeyError: 51 | raise Http404 52 | 53 | instance = get_object_or_404(self.model, user=user, uuid=uuid) 54 | serializer = self.update_serializer_class(instance, data=request.data, partial=True) 55 | if serializer.is_valid(): 56 | serializer.save() 57 | else: 58 | return Response('Invalid Data Submitted {}'.format(data), status=400) 59 | 60 | return Response(serializer.data) 61 | -------------------------------------------------------------------------------- /apis/fitbit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/fitbit/__init__.py -------------------------------------------------------------------------------- /apis/fitbit/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from apis.fitbit.models import UserFitbit 4 | 5 | admin.site.register(UserFitbit) 6 | -------------------------------------------------------------------------------- /apis/fitbit/defaults.py: -------------------------------------------------------------------------------- 1 | # Where to redirect to after Fitbit authentication credentials have been 2 | # removed. 3 | FITAPP_LOGOUT_REDIRECT = '/' 4 | 5 | # By default, don't subscribe to user data. Set this to true to subscribe. 6 | FITAPP_SUBSCRIBE = False 7 | # Only retrieve data for resources in FITAPP_SUBSCRIPTIONS. The default value 8 | # of none results in all subscriptions being retrieved. Override it to be an 9 | # OrderedDict of just the items you want retrieved, in the order you want them 10 | # retrieved, eg: 11 | # from collections import OrderedDict 12 | # FITAPP_SUBSCRIPTIONS = OrderedDict([ 13 | # ('foods', ['log/caloriesIn', 'log/water']), 14 | # ]) 15 | # The default ordering is ['category', 'resource'] when a subscriptions dict is 16 | # not specified. 17 | FITAPP_SUBSCRIPTIONS = None 18 | 19 | # The initial delay (in seconds) when doing the historical data import 20 | FITAPP_HISTORICAL_INIT_DELAY = 10 21 | # The delay (in seconds) between items when doing requests 22 | FITAPP_BETWEEN_DELAY = 5 23 | 24 | # The template to use when an unavoidable error occurs during Fitbit 25 | # integration. 26 | FITAPP_ERROR_TEMPLATE = 'fitapp/error.html' 27 | 28 | # The default message used by the fitbit_integration_warning decorator to 29 | # inform the user about Fitbit integration. If a callable is given, it is 30 | # called with the request as the only parameter to get the final value for the 31 | # message. 32 | FITAPP_DECORATOR_MESSAGE = 'This page requires Fitbit integration.' 33 | -------------------------------------------------------------------------------- /apis/fitbit/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/fitbit/fixtures/__init__.py -------------------------------------------------------------------------------- /apis/fitbit/migrations/0002_data_migrations_for_fitbit_types.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import os 4 | from sys import path 5 | from django.core import serializers 6 | from django.db import models, migrations 7 | 8 | fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures')) 9 | fixture_filename = 'initial_data.json' 10 | 11 | def load_fixture(apps, schema_editor): 12 | fixture_file = os.path.join(fixture_dir, fixture_filename) 13 | 14 | fixture = open(fixture_file, 'rb') 15 | objects = serializers.deserialize('json', fixture, ignorenonexistent=True) 16 | for obj in objects: 17 | obj.save() 18 | fixture.close() 19 | 20 | def unload_fixture(apps, schema_editor): 21 | 'Brutally deleting all entries for this model...' 22 | 23 | TimeSeriesDataType = apps.get_model('fitbit', 'FitbitTimeSeriesDataType') 24 | TimeSeriesDataType.objects.all().delete() 25 | 26 | 27 | class Migration(migrations.Migration): 28 | 29 | dependencies = [ 30 | ('fitbit', '0001_initial'), 31 | ] 32 | 33 | operations = [ 34 | migrations.RunPython(load_fixture, reverse_code=unload_fixture), 35 | ] 36 | -------------------------------------------------------------------------------- /apis/fitbit/migrations/0003_auto_20170822_0324.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-08-22 03:24 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('fitbit', '0002_data_migrations_for_fitbit_types'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterUniqueTogether( 16 | name='fitbittimeseriesdata', 17 | unique_together=set([]), 18 | ), 19 | migrations.RemoveField( 20 | model_name='fitbittimeseriesdata', 21 | name='resource_type', 22 | ), 23 | migrations.RemoveField( 24 | model_name='fitbittimeseriesdata', 25 | name='user', 26 | ), 27 | migrations.DeleteModel( 28 | name='FitbitTimeSeriesDataType', 29 | ), 30 | migrations.DeleteModel( 31 | name='FitbitTimeSeriesData', 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /apis/fitbit/migrations/0004_auto_20170822_0325.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-08-22 03:25 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('fitbit', '0003_auto_20170822_0324'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='userfitbit', 17 | old_name='fitbit_user', 18 | new_name='fitbit_user_id', 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /apis/fitbit/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/fitbit/migrations/__init__.py -------------------------------------------------------------------------------- /apis/fitbit/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.db import models 3 | 4 | 5 | # Taken from django-fitbit, many thanks to them! Package is good, just wanted CBVs 6 | # https://github.com/orcasgit/django-fitbit/blob/master/fitapp/models.py 7 | 8 | USER_MODEL = get_user_model() 9 | 10 | 11 | class UserFitbit(models.Model): 12 | """ A user's fitbit credentials, allowing API access """ 13 | user = models.OneToOneField(USER_MODEL) 14 | fitbit_user_id = models.CharField(max_length=32, unique=True) 15 | access_token = models.TextField() 16 | refresh_token = models.TextField() 17 | # this is a floatfield since it's returned that way by fitbit 18 | expires_at = models.FloatField() 19 | 20 | def __str__(self): 21 | return self.user.__str__() 22 | 23 | def refresh_cb(self, token): 24 | """ Called when the OAuth token has been refreshed """ 25 | self.access_token = token['access_token'] 26 | self.refresh_token = token['refresh_token'] 27 | self.expires_at = token['expires_at'] 28 | self.save() 29 | 30 | def get_user_data(self): 31 | return { 32 | 'user_id': self.fitbit_user_id, 33 | 'access_token': self.access_token, 34 | 'refresh_token': self.refresh_token, 35 | 'expires_at': self.expires_at, 36 | 'refresh_cb': self.refresh_cb, 37 | } 38 | -------------------------------------------------------------------------------- /apis/fitbit/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import fitbit 3 | import pandas as pd 4 | 5 | from django.conf import settings 6 | from django.contrib.auth import get_user_model 7 | 8 | from apis.fitbit.models import UserFitbit 9 | from apis.fitbit.serializers import FitbitResponseSleepActivitySerializer 10 | from betterself import celery_app 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | User = get_user_model() 15 | 16 | 17 | @celery_app.task() 18 | def import_user_fitbit_history_via_api(user, start_date, end_date): 19 | fitbit_user = UserFitbit.objects.get(user=user) 20 | fitbit_api = fitbit.Fitbit( 21 | client_id=settings.FITBIT_CONSUMER_KEY, 22 | client_secret=settings.FITBIT_CONSUMER_SECRET, 23 | access_token=fitbit_user.access_token, 24 | expires_at=fitbit_user.expires_at, 25 | refresh_token=fitbit_user.refresh_token, 26 | refresh_cb=fitbit_user.refresh_cb, 27 | ) 28 | 29 | query_dates = pd.date_range(start=start_date, end=end_date).date 30 | for query_date in query_dates: 31 | api_response = fitbit_api.get_sleep(query_date) 32 | 33 | valid_data = api_response.get('sleep') 34 | if not valid_data: 35 | # if the response doesn't contain valid data, no sense to continue 36 | continue 37 | 38 | for datum in valid_data: 39 | data = { 40 | 'user': user.id, 41 | 'start_time': datum['startTime'], 42 | 'end_time': datum['endTime'] 43 | } 44 | 45 | serializer = FitbitResponseSleepActivitySerializer(data=data) 46 | serializer.is_valid() 47 | serializer.save() 48 | -------------------------------------------------------------------------------- /apis/fitbit/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from apis.fitbit.views import FitbitLoginView, FitbitCompleteView, FitbitUserAuthCheck, FitbitUserUpdateSleepHistory 4 | 5 | urlpatterns = [ 6 | url(r'^oauth2/login/$', FitbitLoginView.as_view(), name='fitbit-login'), 7 | url(r'^oauth2/callback/$', FitbitCompleteView.as_view(), name='fitbit-complete-backend'), 8 | url(r'^user-auth-check/$', FitbitUserAuthCheck.as_view(), name='fitbit-user-auth-check'), 9 | url(r'^update-sleep-history/$', FitbitUserUpdateSleepHistory.as_view(), name='fitbit-user-update-sleep-history'), 10 | ] 11 | -------------------------------------------------------------------------------- /apis/github/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/github/__init__.py -------------------------------------------------------------------------------- /apis/github/views.py: -------------------------------------------------------------------------------- 1 | from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter 2 | from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter 3 | from rest_auth.registration.views import SocialLoginView 4 | 5 | 6 | class GitHubLoginView(SocialLoginView): 7 | adapter_class = GitHubOAuth2Adapter 8 | 9 | 10 | class FacebookLogin(SocialLoginView): 11 | adapter_class = FacebookOAuth2Adapter 12 | -------------------------------------------------------------------------------- /apis/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-25 12:51 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='BetterSelfCredentials', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('api_key', models.CharField(max_length=500)), 24 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | migrations.CreateModel( 28 | name='FitBitAPICredentials', 29 | fields=[ 30 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 31 | ('api_key', models.CharField(max_length=500)), 32 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 33 | ], 34 | ), 35 | migrations.CreateModel( 36 | name='RescueTimeAPICredentials', 37 | fields=[ 38 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 39 | ('api_key', models.CharField(max_length=500)), 40 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 41 | ], 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /apis/migrations/0002_auto_20170812_0504.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.2 on 2017-08-12 05:04 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('apis', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='betterselfcredentials', 17 | name='user', 18 | ), 19 | migrations.RemoveField( 20 | model_name='fitbitapicredentials', 21 | name='user', 22 | ), 23 | migrations.RemoveField( 24 | model_name='rescuetimeapicredentials', 25 | name='user', 26 | ), 27 | migrations.DeleteModel( 28 | name='BetterSelfCredentials', 29 | ), 30 | migrations.DeleteModel( 31 | name='FitBitAPICredentials', 32 | ), 33 | migrations.DeleteModel( 34 | name='RescueTimeAPICredentials', 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /apis/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/migrations/__init__.py -------------------------------------------------------------------------------- /apis/rescuetime/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/rescuetime/__init__.py -------------------------------------------------------------------------------- /apis/rescuetime/importers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/rescuetime/importers/__init__.py -------------------------------------------------------------------------------- /apis/rescuetime/importers/test_utils.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from apis.rescuetime.importers.utils import calculate_rescue_time_pulse 4 | 5 | 6 | class TestRescueTimeImportersUtils(TestCase): 7 | def test_rescuetime_of_zero(self): 8 | pulse = calculate_rescue_time_pulse(0, 0, 0, 0, 0) 9 | self.assertEqual(pulse, 0) 10 | -------------------------------------------------------------------------------- /apis/rescuetime/tasks.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | 3 | from apis.rescuetime.importers.historical_daily_importer import RescueTimeHistoricalDailyImporter 4 | from betterself import celery_app 5 | 6 | User = get_user_model() 7 | 8 | 9 | @celery_app.task() 10 | def import_user_rescuetime_history_via_api(user, start_date, end_date, rescuetime_api_key): 11 | importer = RescueTimeHistoricalDailyImporter(user, rescuetime_api_key) 12 | importer.import_history(start_date, end_date) 13 | importer.save() 14 | -------------------------------------------------------------------------------- /apis/rescuetime/v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/rescuetime/v1/__init__.py -------------------------------------------------------------------------------- /apis/rescuetime/v1/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | 4 | class RescueTimeAPIRequestSerializer(serializers.Serializer): 5 | rescuetime_api_key = serializers.CharField(max_length=200) 6 | # add a check to make sure end_date is greater than start_date 7 | start_date = serializers.DateField() 8 | end_date = serializers.DateField() 9 | 10 | def validate(self, data): 11 | """ 12 | Check that the start is before the stop. 13 | """ 14 | if data['start_date'] > data['end_date']: 15 | raise serializers.ValidationError('Finish must occur after start') 16 | 17 | # Do something to be nice to RescueTime's servers 18 | days_difference = data['end_date'] - data['start_date'] 19 | if days_difference.days > 370: 20 | raise serializers.ValidationError('Start and end dates must be within 370 days') 21 | 22 | return data 23 | -------------------------------------------------------------------------------- /apis/rescuetime/v1/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from apis.rescuetime.v1.views import UpdateRescueTimeAPIView 4 | 5 | urlpatterns = [ 6 | # /api/v1/rescuetime/update-productivity-history 7 | url(r'^update-productivity-history$', UpdateRescueTimeAPIView.as_view(), name='rescuetime-user-update-productivity-history'), # noqa 8 | ] 9 | -------------------------------------------------------------------------------- /apis/rescuetime/v1/views.py: -------------------------------------------------------------------------------- 1 | from django.utils.datastructures import MultiValueDictKeyError 2 | from rest_framework.response import Response 3 | from rest_framework.views import APIView 4 | 5 | from apis.rescuetime.tasks import import_user_rescuetime_history_via_api 6 | from apis.rescuetime.v1.serializers import RescueTimeAPIRequestSerializer 7 | 8 | 9 | class UpdateRescueTimeAPIView(APIView): 10 | # don't slam rescuetime's servers, so you won't get banned 11 | throttle_scope = 'rescuetime-api-sync' 12 | 13 | def post(self, request): 14 | user = request.user 15 | data = request.data 16 | 17 | try: 18 | initial_data = { 19 | 'rescuetime_api_key': data['rescuetime_api_key'], 20 | 'start_date': data['start_date'], 21 | 'end_date': data['end_date'], 22 | } 23 | except (MultiValueDictKeyError, KeyError) as exc: 24 | return Response('Missing POST parameters {}'.format(exc), status=400) 25 | 26 | serializer = RescueTimeAPIRequestSerializer(data=initial_data) 27 | serializer.is_valid(raise_exception=True) 28 | 29 | # send the job off to celery so it's an async task 30 | import_user_rescuetime_history_via_api.delay(user=user, **serializer.validated_data) 31 | 32 | return Response(status=202) 33 | -------------------------------------------------------------------------------- /apis/twilio/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/apis/twilio/__init__.py -------------------------------------------------------------------------------- /apis/twilio/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from apis.twilio.views import TwilioTextMessageResponse 4 | 5 | urlpatterns = [ 6 | url(r'^text/reply/$', TwilioTextMessageResponse.as_view(), name='twilio-text-response'), 7 | ] 8 | -------------------------------------------------------------------------------- /apis/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | 3 | # note for api urls, even though app is plural, link is singular! 4 | # aka /api/v1, NOT /apis/v1 5 | urlpatterns = [ 6 | url(r'^v1/', include('apis.betterself.v1.urls')), 7 | url(r'^v1/rescuetime/', include('apis.rescuetime.v1.urls')), 8 | url(r'^fitbit/', include('apis.fitbit.urls')), 9 | url(r'^twilio/', include('apis.twilio.urls')), 10 | ] 11 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BetterSelf", 3 | "description": "Quantify Your Health", 4 | "env": { 5 | "BUILDPACK_URL": "https://github.com/heroku/heroku-buildpack-python" 6 | }, 7 | "scripts": { 8 | "postdeploy": "python manage.py migrate" 9 | }, 10 | "addons": [ 11 | "heroku-postgresql:hobby-dev", 12 | "heroku-redis:hobby-dev", 13 | "mailgun" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /assets/fonts/Simple-Line-Icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/Simple-Line-Icons.eot -------------------------------------------------------------------------------- /assets/fonts/Simple-Line-Icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/Simple-Line-Icons.ttf -------------------------------------------------------------------------------- /assets/fonts/Simple-Line-Icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/Simple-Line-Icons.woff -------------------------------------------------------------------------------- /assets/fonts/Simple-Line-Icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/Simple-Line-Icons.woff2 -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /assets/fonts/glyphicons-filetypes-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-filetypes-regular.eot -------------------------------------------------------------------------------- /assets/fonts/glyphicons-filetypes-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-filetypes-regular.ttf -------------------------------------------------------------------------------- /assets/fonts/glyphicons-filetypes-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-filetypes-regular.woff -------------------------------------------------------------------------------- /assets/fonts/glyphicons-filetypes-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-filetypes-regular.woff2 -------------------------------------------------------------------------------- /assets/fonts/glyphicons-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-regular.eot -------------------------------------------------------------------------------- /assets/fonts/glyphicons-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-regular.ttf -------------------------------------------------------------------------------- /assets/fonts/glyphicons-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-regular.woff -------------------------------------------------------------------------------- /assets/fonts/glyphicons-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-regular.woff2 -------------------------------------------------------------------------------- /assets/fonts/glyphicons-social-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-social-regular.eot -------------------------------------------------------------------------------- /assets/fonts/glyphicons-social-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-social-regular.ttf -------------------------------------------------------------------------------- /assets/fonts/glyphicons-social-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-social-regular.woff -------------------------------------------------------------------------------- /assets/fonts/glyphicons-social-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/fonts/glyphicons-social-regular.woff2 -------------------------------------------------------------------------------- /assets/img/Sorting icons.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/Sorting icons.psd -------------------------------------------------------------------------------- /assets/img/icons/001-brain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/icons/001-brain.png -------------------------------------------------------------------------------- /assets/img/logos/white_logo_transparent_background_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/logos/white_logo_transparent_background_small.png -------------------------------------------------------------------------------- /assets/img/navigation/fast-forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /assets/img/navigation/left-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /assets/img/navigation/restart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /assets/img/rescuetime/rescuetime_description.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/rescuetime/rescuetime_description.jpg -------------------------------------------------------------------------------- /assets/img/rescuetime/rescuetime_example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/rescuetime/rescuetime_example.jpg -------------------------------------------------------------------------------- /assets/img/rescuetime/rescuetime_example_breakdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/rescuetime/rescuetime_example_breakdown.jpg -------------------------------------------------------------------------------- /assets/img/rescuetime/rescuetime_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/rescuetime/rescuetime_logo.jpg -------------------------------------------------------------------------------- /assets/img/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/select.png -------------------------------------------------------------------------------- /assets/img/select2-spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/select2-spinner.gif -------------------------------------------------------------------------------- /assets/img/select2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/select2.png -------------------------------------------------------------------------------- /assets/img/select2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/select2x2.png -------------------------------------------------------------------------------- /assets/img/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/sort_asc.png -------------------------------------------------------------------------------- /assets/img/sort_asc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/sort_asc_disabled.png -------------------------------------------------------------------------------- /assets/img/sort_both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/sort_both.png -------------------------------------------------------------------------------- /assets/img/sort_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/sort_desc.png -------------------------------------------------------------------------------- /assets/img/sort_desc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/img/sort_desc_disabled.png -------------------------------------------------------------------------------- /assets/js/analytics/constants.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { getSupplementOverviewURLFromUUID } from "../routing/routing_utils"; 3 | import { Link } from "react-router-dom"; 4 | 5 | // This entire analytics folder needs to be refactored and cleaned up 6 | 7 | export const SupplementCorrelationTableRow = data => { 8 | const details = data.object; 9 | // Try to see if the details contain a supplement UUID, if so, render that way 10 | const supplementUUID = data.supplementUUID; 11 | const valueFormatted = details[1] ? details[1].toFixed(3) : null; 12 | 13 | if (supplementUUID) { 14 | const supplementOverviewLink = getSupplementOverviewURLFromUUID( 15 | supplementUUID 16 | ); 17 | return ( 18 | 19 | {details[0]} 20 | {valueFormatted} 21 | 22 | ); 23 | } 24 | 25 | return ( 26 | 27 | {details[0]} 28 | {valueFormatted} 29 | 30 | ); 31 | }; 32 | 33 | export const UserActivitiesCorrelationTableRow = data => { 34 | const details = data.object; 35 | const valueFormatted = details[1] ? details[1].toFixed(3) : null; 36 | 37 | return ( 38 | 39 | {details[0]} 40 | {valueFormatted} 41 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /assets/js/authentication/authentication.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | DASHBOARD_INDEX_URL, 4 | HOME_URL, 5 | USER_INFO_URL 6 | } from "../constants/urls"; 7 | import LoggedInHeader from "../header/internal_header"; 8 | import Sidebar from "../sidebar/sidebar"; 9 | 10 | // For any pages that are already SessionAuthenticated 11 | 12 | export class AuthenticationView extends Component { 13 | constructor() { 14 | super(); 15 | 16 | fetch(USER_INFO_URL, { 17 | method: "GET", 18 | credentials: "same-origin" 19 | }) 20 | .then(responseData => { 21 | return responseData.json(); 22 | }) 23 | .then(responseData => { 24 | if ("token" in responseData) { 25 | localStorage.token = responseData.token; 26 | localStorage.userName = responseData.username; 27 | } else { 28 | alert("Error. Unable to login. Please contact support."); 29 | window.location.assign(HOME_URL); 30 | } 31 | }) 32 | .then(e => { 33 | window.location.assign(DASHBOARD_INDEX_URL); 34 | }); 35 | } 36 | 37 | render() { 38 | return ( 39 |
40 | 41 |
42 | 43 |
44 |
45 |
46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /assets/js/authentication/login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { DJANGO_BASE_LOGIN } from "../constants/urls"; 3 | 4 | // After a bit of debate, just switch back to using a Django form for a login to make it all easier 5 | export class LoginView extends Component { 6 | constructor() { 7 | super(); 8 | 9 | window.location.assign(DJANGO_BASE_LOGIN); 10 | } 11 | 12 | render() { 13 |
; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /assets/js/authentication/logout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import { Authenticator } from "./auth"; 4 | 5 | export class LogoutView extends Component { 6 | render(cb) { 7 | Authenticator.logout(cb); 8 | 9 | // Per React spec return back an empty page even though the location will be changing so quickly 10 | return
; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /assets/js/constants/dates_and_times.js: -------------------------------------------------------------------------------- 1 | export const YEAR_MONTH_DAY_FORMAT = "MMMM Do YYYY"; 2 | export const READABLE_DATE_TIME_FORMAT = "dddd, MMMM Do YYYY, h:mm:ss a"; 3 | export const READABLE_TIME_FORMAT = "h:mm:ss a"; 4 | export const DATE_REQUEST_FORMAT = "YYYY-MM-DD"; 5 | export const DATETIME_CREATED_FORMAT = "l - h:mm:ss a"; 6 | export const TEXT_TIME_FORMAT = "LT"; // 9:32 PM 7 | 8 | // 12/5/2016 Tu (this is what it looks like) 9 | export const ABBREVIATED_CHART_DATE = "l dd"; 10 | 11 | export const minutesToHours = (minutes, decimal_places = 2) => { 12 | if (minutes) { 13 | return (minutes / 60).toFixed(decimal_places); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /assets/js/constants/designs.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export const TrueCheckBox = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export const RenderTrueFalseCheckBox = boolFlag => { 12 | if (boolFlag) { 13 | return ( 14 |
15 | 16 |
17 | ); 18 | } else { 19 | return ( 20 |
21 |
22 | 23 |
24 |
25 | ); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /assets/js/constants/image_paths.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // Keep all commonly used images in one place for fragments 4 | export const LOGO_BACKGROUND_PATH = require("../../../betterself/static/images/logos/logojoy/png/white_logo_transparent_background.png"); 5 | export const FAVICON_PATH = require("../../../betterself/static/images/logos/logojoy/favicon.png"); 6 | export const LOGIN_SIDE_PHOTO_PATH = require("../../../betterself/static/images/login/login_side_photo.jpeg"); 7 | export const DASHBOARD_EXAMPLE_PATH = require("../../../betterself/static/images/dashboard/dashboard_example_4.png"); 8 | // export const DASHBOARD_EXAMPLE_PATH = require("../../../betterself/static/images/dashboard/dashboard_example_5.jpg"); 9 | // HEADER_IMAGE_* eventually have an image that's a dropdown login/logout 10 | export const DASHBOARD_HEART_RATE = require("../../../betterself/static/images/dashboard/dashboard_heart_rate.jpg"); 11 | export const DASHBOARD_SUPPLEMENTS_HISTORY = require("../../../betterself/static/images/dashboard/dashboard_supplements_events_history.jpg"); 12 | export const HEADER_BRAIN_PATH = require("../../../betterself/static/images/header/control_panel/brain.png"); 13 | export const HEADER_HEART_PATH = require("../../../betterself/static/images/header/control_panel/heart.png"); 14 | 15 | export const RESCUETIME_LOGO = require("../../img/rescuetime/rescuetime_logo.jpg"); 16 | export const RESCUETIME_DESCRIPTION = require("../../img/rescuetime/rescuetime_description.jpg"); 17 | export const RESCUETIME_EXAMPLE = require("../../img/rescuetime/rescuetime_example.jpg"); 18 | export const RESCUETIME_EXAMPLE_BREAKDOWN = require("../../img/rescuetime/rescuetime_example_breakdown.jpg"); 19 | -------------------------------------------------------------------------------- /assets/js/constants/loading_styles.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const CubeLoadingStyle = () => ( 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ); 18 | -------------------------------------------------------------------------------- /assets/js/constants/productivity.js: -------------------------------------------------------------------------------- 1 | export const VERY_PRODUCTIVE_MINUTES_LABEL = "Very Productive Minutes"; 2 | export const PRODUCTIVE_MINUTES_LABEL = "Productive Minutes"; 3 | export const NEUTRAL_MINUTES_LABEL = "Neutral Minutes"; 4 | export const DISTRACTING_MINUTES_LABEL = "Distracting Minutes"; 5 | export const VERY_DISTRACTING_MINUTES_LABEL = "Very Distracting Minutes"; 6 | 7 | export const VERY_PRODUCTIVE_MINUTES_VARIABLE = "very_productive_time_minutes"; 8 | export const PRODUCTIVE_MINUTES_VARIABLE = "productive_time_minutes"; 9 | export const NEUTRAL_MINUTES_VARIABLE = "neutral_time_minutes"; 10 | export const DISTRACTING_MINUTES_VARIABLE = "distracting_time_minutes"; 11 | export const VERY_DISTRACTING_MINUTES_VARIABLE = 12 | "very_distracting_time_minutes"; 13 | 14 | export const POSITIVELY_CORRELATED_LABEL = "Positively Correlated"; 15 | export const NEGATIVELY_CORRELATED_LABEL = "Negatively Correlated"; 16 | export const NOT_CORRELATED_LABEL = "Not Correlated"; 17 | 18 | export const ProductivityColumnMappingToKey = { 19 | [VERY_PRODUCTIVE_MINUTES_LABEL]: VERY_PRODUCTIVE_MINUTES_VARIABLE, 20 | [PRODUCTIVE_MINUTES_LABEL]: PRODUCTIVE_MINUTES_VARIABLE, 21 | [NEUTRAL_MINUTES_LABEL]: NEUTRAL_MINUTES_VARIABLE, 22 | [DISTRACTING_MINUTES_LABEL]: DISTRACTING_MINUTES_VARIABLE, 23 | [VERY_DISTRACTING_MINUTES_LABEL]: VERY_DISTRACTING_MINUTES_VARIABLE 24 | }; 25 | -------------------------------------------------------------------------------- /assets/js/constants/requests.js: -------------------------------------------------------------------------------- 1 | export const JSON_HEADERS = { 2 | Accept: "application/json", 3 | "Content-Type": "application/json" 4 | }; 5 | 6 | export const JSON_AUTHORIZATION_HEADERS = { 7 | Authorization: `Token ${localStorage.token}` 8 | }; 9 | 10 | export const JSON_POST_AUTHORIZATION_HEADERS = { 11 | Accept: "application/json", 12 | "Content-Type": "application/json", 13 | Authorization: `Token ${localStorage.token}` 14 | }; 15 | -------------------------------------------------------------------------------- /assets/js/create_demo_user/create_demo_user.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { DASHBOARD_INDEX_URL } from "../constants/urls"; 3 | 4 | export class CreateDemoUserView extends Component { 5 | constructor() { 6 | super(); 7 | 8 | // Okay this obviously doesn't need any real state, but I'm going to keep this here 9 | // while I refactor dynamic logins. This once had a lot of logic to create demo users, but 10 | // its all done magically via celery 11 | this.createDemoUser(); 12 | } 13 | 14 | createDemoUser() { 15 | return fetch("/api/v1/user-signup-demo/", { 16 | method: "GET" 17 | }) 18 | .then(response => { 19 | return response.json(); 20 | }) 21 | .then(responseData => { 22 | if ("token" in responseData) { 23 | // If the token is in the response, set localStorage correctly 24 | // and then redirect to the dashboard 25 | localStorage.token = responseData["token"]; 26 | localStorage.userName = responseData["username"]; 27 | window.location.assign(DASHBOARD_INDEX_URL); 28 | } 29 | return responseData; 30 | }); 31 | } 32 | 33 | render() { 34 | return
; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /assets/js/dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Sidebar from "./sidebar/sidebar.js"; 3 | import LoggedInHeader from "./header/internal_header"; 4 | 5 | export class Dashboard extends Component { 6 | // This was a really regrettable idea now that I'm struggling to figure out how to get my Routing 7 | // parameters 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | render() { 13 | return ( 14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /assets/js/export/export.js: -------------------------------------------------------------------------------- 1 | import { Redirect } from "react-router-dom"; 2 | import React, { Component } from "react"; 3 | import { JSON_AUTHORIZATION_HEADERS } from "../constants/requests"; 4 | import { saveAs } from "file-saver"; 5 | import { DASHBOARD_USER_ACTIVITIES_EVENTS_LOGS_URL } from "../constants/urls"; 6 | 7 | export class UserExportAllDataView extends Component { 8 | constructor() { 9 | super(); 10 | this.state = { 11 | loaded: false 12 | }; 13 | } 14 | componentDidMount() { 15 | // get the download data and then immediately save it 16 | // after this happens, render happens to another page 17 | this.getData(); 18 | } 19 | 20 | getData() { 21 | fetch("/api/v1/user/export-data", { 22 | method: "GET", 23 | headers: JSON_AUTHORIZATION_HEADERS 24 | }) 25 | .then(response => { 26 | return response.blob(); 27 | }) 28 | .then(blob => { 29 | saveAs(blob, "historical_data.xlsx"); 30 | this.setState({ loaded: true }); 31 | }); 32 | } 33 | 34 | render() { 35 | const loaded = this.state.loaded; 36 | 37 | return ( 38 |
39 | {loaded 40 | ? 41 | :
} 42 |
43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /assets/js/fitbit/fitbit.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import qs from "query-string"; 3 | import { postFetchJSONAPI } from "../utils/fetch_utils"; 4 | import { FITBIT_BACKEND_API_CALLBACK_URL } from "../constants/urls"; 5 | 6 | export class FitBitCompleteCallbackView extends Component { 7 | constructor(props) { 8 | super(props); 9 | 10 | const { location } = props; 11 | const fitbitCodeDetails = qs.parse(location.search); 12 | const { code } = fitbitCodeDetails; 13 | 14 | const params = { 15 | code: code 16 | }; 17 | 18 | postFetchJSONAPI( 19 | FITBIT_BACKEND_API_CALLBACK_URL, 20 | params 21 | ).then(responseData => { 22 | const { next_url } = responseData; 23 | this.props.history.push(next_url); 24 | }); 25 | } 26 | 27 | render() { 28 | return
; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /assets/js/footer/footer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { LOGO_BACKGROUND_PATH } from "../constants/image_paths"; 3 | 4 | const BetterSelfAddress = () => ( 5 |

6 | 7 | BetterHealth, Inc. 8 |
9 | 99 St Marks, 3D 10 |
11 | New York, NY, 10009 12 |
13 | jeffshek@gmail.com 14 |

15 | ); 16 | 17 | export default class Footer extends Component { 18 | render() { 19 | return ( 20 |
21 |
22 |
23 | 27 |
28 |
29 |

30 | We  analytics 31 |

32 | 33 |
34 |
35 |

36 | Please take a few minutes to read our 37 | {" "} 38 | Terms & Conditions 39 | {" "} 40 | and 41 | {" "} 42 | Privacy Policy 43 |

44 |
45 |
46 |
47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /assets/js/header/css/internal_header.css: -------------------------------------------------------------------------------- 1 | @import url("../../../css/coreui-style.css"); 2 | -------------------------------------------------------------------------------- /assets/js/header/external_header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { LOGO_BACKGROUND_PATH } from "../constants/image_paths"; 3 | import { DASHBOARD_INDEX_URL, HOME_URL, SIGNUP_URL } from "../constants/urls"; 4 | import CSSModules from "react-css-modules"; 5 | import styles from "./css/external_header.css"; 6 | 7 | class LoggedOutHeader extends Component { 8 | render() { 9 | return ( 10 |
11 | 35 |
36 | ); 37 | } 38 | } 39 | 40 | export default CSSModules(LoggedOutHeader, styles); 41 | -------------------------------------------------------------------------------- /assets/js/home/components/FontAwesomeIconBox.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import CSSModules from "react-css-modules"; 3 | import styles from "../css/FontAwesomeIconBox.css"; 4 | 5 | class FontAwesomeIconBox extends Component { 6 | render() { 7 | return ( 8 |
9 |
10 |
11 |   12 | 13 |   14 |
15 |

{this.props.header}

16 |

{this.props.description}

17 |
18 |
19 | ); 20 | } 21 | } 22 | 23 | export default CSSModules(FontAwesomeIconBox, styles); 24 | -------------------------------------------------------------------------------- /assets/js/home/components/HomePageAnalyticsDescriptionSection.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import CSSModules from "react-css-modules"; 3 | import styles from "../css/HomePageAnalyticsDescriptionSection.css"; 4 | import { DASHBOARD_EXAMPLE_PATH } from "../../constants/image_paths"; 5 | 6 | class HomePageAnalyticsDescriptionSection extends React.Component { 7 | render() { 8 | return ( 9 |
10 |
11 |
12 |
13 |

Personalized Analytics

14 |

15 | What do your most creative days and weeks have in common? Will a hangover still impact you three days after the binge? Does your productivity really take a hit with only five hours of sleep? 16 | {" "} 17 |

18 |

19 | Track your decisions, activities, and supplements, to see what's really impacting you, in colourful, easy-to-understand graphs and charts. 20 |

21 |

22 | If you’ve ever wanted a meaningful way to see how your decisions affect your body, you've come to the right place. 23 |

24 | 25 |
26 |
27 |
28 | 32 |
33 |
34 |
35 |
36 | ); 37 | } 38 | } 39 | 40 | export default CSSModules(HomePageAnalyticsDescriptionSection, styles); 41 | -------------------------------------------------------------------------------- /assets/js/home/components/HomePageFeaturesShortDescriptionSection.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import CSSModules from "react-css-modules"; 3 | import FontAwesomeIconBox from "./FontAwesomeIconBox"; 4 | 5 | import styles from "../css/HomePageFeaturesShortDescriptionSection.css"; 6 | 7 | class HomePageFeaturesShortDescriptionSection extends React.Component { 8 | render() { 9 | return ( 10 |
11 |
12 |
13 | 18 | 23 | 28 | 33 |
34 |
35 |
36 | ); 37 | } 38 | } 39 | 40 | export default CSSModules(HomePageFeaturesShortDescriptionSection, styles); 41 | -------------------------------------------------------------------------------- /assets/js/home/components/OpenSourceAndApplicationSupport.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import CSSModules from "react-css-modules"; 3 | 4 | import styles from "../css/OpenSourceAndApplicationSupport.css"; 5 | import { DEMO_SIGNUP_URL } from "../../constants/urls"; 6 | 7 | class OpenSourceAndApplicationSupport extends Component { 8 | render() { 9 | return ( 10 |
11 |
12 | 13 |

14 | TL;DR—we got frustrated at tracking supplements and habits, so we made an app. 15 |

16 |

17 | So stop wondering whether you’re doing things right, and start seeing results. Track what you’re putting into your body and see what really matters. Make better decisions. Make a better you. 18 |

19 | 20 | 24 | Words Don't Mean Much 25 | {" "} 26 | 27 | {" "} 28 | See A Working Demo 29 | 30 |
31 |
32 | ); 33 | } 34 | } 35 | 36 | export default CSSModules(OpenSourceAndApplicationSupport, styles); 37 | -------------------------------------------------------------------------------- /assets/js/home/css/FontAwesomeIconBox.css: -------------------------------------------------------------------------------- 1 | .feature-box { 2 | background: #193441; 3 | margin: 20px 0; 4 | padding: 35px 30px; 5 | text-align: center; 6 | border-radius: 4px; 7 | -webkit-border-radius: 4px; 8 | -moz-border-radius: 4px; 9 | -o-border-radius: 4px; 10 | -ms-border-radius: 4px; 11 | } 12 | .feature-box p { 13 | line-height: 25px; 14 | color: #ffffff; 15 | opacity: 0.5; 16 | } 17 | .feature-box h3 { 18 | padding: 15px 0px 0px; 19 | color: #ffffff; 20 | } 21 | .feature-box:hover { 22 | transition: all 0.4s ease-in 0s; 23 | -moz-transition: all 0.4s ease-in 0s; 24 | -webkit-transition: all 0.4s ease-in 0s; 25 | -o-transition: all 0.4s ease-in 0s; 26 | -ms-transition: all 0.4s ease-in 0s; 27 | } 28 | /*you can put some hover in ... */ 29 | .icon { 30 | margin: 0 auto; 31 | background-color: #ffffff; 32 | text-align: center; 33 | color: #723147; 34 | font-size: 60px; 35 | height: 140px; 36 | width: 140px; 37 | line-height: 130px; 38 | border-radius: 50%; 39 | -webkit-border-radius: 50%; 40 | -moz-border-radius: 50%; 41 | -o-border-radius: 50%; 42 | -ms-border-radius: 50%; 43 | } 44 | -------------------------------------------------------------------------------- /assets/js/home/css/HomePageAnalyticsDescriptionSection.css: -------------------------------------------------------------------------------- 1 | .description-block { 2 | background-color: #193441 !important; 3 | width: 100%; 4 | padding: 60px 0 60px 0; 5 | z-index: 100; 6 | position: relative; 7 | color: #3498DB; 8 | 9 | } 10 | .lead-white { 11 | margin-bottom: 30px; 12 | font-size: 20px; 13 | line-height: 1.4; 14 | font-weight: 300; 15 | color: #FFFFFF !important; 16 | } 17 | -------------------------------------------------------------------------------- /assets/js/home/css/HomePageDescription.css: -------------------------------------------------------------------------------- 1 | .background-image-holder { 2 | background: #cccccc url('../img/dark-logs-bg.jpg') no-repeat center center; 3 | -webkit-background-size: cover; 4 | -moz-background-size: cover; 5 | -o-background-size: cover; 6 | background-size: cover; 7 | height: 100%; 8 | } 9 | .image-container { 10 | position: absolute; 11 | height: 100%; 12 | padding: 0px; 13 | top: 0px; 14 | bottom: 20px; 15 | } 16 | .content-block-description { 17 | padding: 0px; 18 | padding-top: 100px; 19 | padding-bottom: 40px; 20 | position: relative; 21 | width: 100%; 22 | z-index: 100; 23 | background-color: #ffffff; 24 | } 25 | .content-1-4-lead { 26 | margin-bottom: 20px; 27 | } 28 | -------------------------------------------------------------------------------- /assets/js/home/css/HomePageFeaturesShortDescriptionSection.css: -------------------------------------------------------------------------------- 1 | .content-format { 2 | width: 100%; 3 | z-index: 100; 4 | position: relative; 5 | background: #ffffff; 6 | margin: 20px 0; 7 | padding: 35px 30px; 8 | text-align: center; 9 | border-radius: 4px; 10 | -webkit-border-radius: 4px; 11 | -moz-border-radius: 4px; 12 | -o-border-radius: 4px; 13 | -ms-border-radius: 4px; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /assets/js/home/css/OpenSourceAndApplicationSupport.css: -------------------------------------------------------------------------------- 1 | /*.bg-deepocean {*/ 2 | /*background-color: #193441 !important;*/ 3 | /*}*/ 4 | .content-2-1 .btn-outline { 5 | margin-bottom: 30px; 6 | } 7 | .content-2-1 h2 { 8 | color: #ffffff; 9 | } 10 | .content-block { 11 | width: 100%; 12 | padding: 60px 0 60px 0; 13 | z-index: 100; 14 | position: relative; 15 | background-color: #ffffff; 16 | } 17 | .content-block-nopad { 18 | width: 100%; 19 | z-index: 100; 20 | position: relative; 21 | background-color: #ffffff; 22 | } 23 | 24 | .content-deep-ocean { 25 | background-color: #193441 !important; 26 | width: 100%; 27 | padding: 60px 0 60px 0; 28 | z-index: 100; 29 | position: relative; 30 | color: #ffffff; 31 | } 32 | 33 | .text-center .panel-title > a span:after { 34 | top: -.75em; 35 | left: 0; 36 | opacity: 0; 37 | } 38 | .text-center-title { 39 | top: -.75em; 40 | left: 0; 41 | opacity: 0; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /assets/js/home/home.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import LoggedOutHeader from "../header/external_header"; 3 | import Footer from "../footer/footer"; 4 | import HomePageDescriptionSection from "./components/HomePageDescription"; 5 | import HomePageAnalyticsDescriptionSection 6 | from "./components/HomePageAnalyticsDescriptionSection"; 7 | import HomePageFeaturesShortDescriptionSection 8 | from "./components/HomePageFeaturesShortDescriptionSection"; 9 | import OpenSourceAndApplicationSupport 10 | from "./components/OpenSourceAndApplicationSupport"; 11 | 12 | export const HomePage = () => { 13 | return ( 14 |
15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /assets/js/home/img/#dark-logs-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/js/home/img/#dark-logs-bg.jpg -------------------------------------------------------------------------------- /assets/js/home/img/dark-logs-bg-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/js/home/img/dark-logs-bg-2.jpg -------------------------------------------------------------------------------- /assets/js/home/img/dark-logs-bg-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/js/home/img/dark-logs-bg-3.jpg -------------------------------------------------------------------------------- /assets/js/home/img/dark-logs-bg-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/js/home/img/dark-logs-bg-4.jpg -------------------------------------------------------------------------------- /assets/js/home/img/dark-logs-bg-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/js/home/img/dark-logs-bg-5.jpg -------------------------------------------------------------------------------- /assets/js/home/img/dark-logs-bg-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/js/home/img/dark-logs-bg-6.jpg -------------------------------------------------------------------------------- /assets/js/home/img/dark-logs-bg-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/js/home/img/dark-logs-bg-8.jpg -------------------------------------------------------------------------------- /assets/js/home/img/dark-logs-bg-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/js/home/img/dark-logs-bg-9.jpg -------------------------------------------------------------------------------- /assets/js/home/img/dark-logs-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/assets/js/home/img/dark-logs-bg.jpg -------------------------------------------------------------------------------- /assets/js/mood_log/constants.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import moment from "moment"; 3 | import { READABLE_DATE_TIME_FORMAT } from "../constants/dates_and_times"; 4 | 5 | export const MoodHistoryRow = props => { 6 | // Used to render the data from the API 7 | const data = props.object; 8 | const { uuid, time, value, notes, source } = data; 9 | const timeFormatted = moment(time).format(READABLE_DATE_TIME_FORMAT); 10 | 11 | return ( 12 | 13 | {timeFormatted} 14 | {value} 15 | {notes} 16 | 17 | {source} 18 | 19 | 20 |
21 |
24 | props.confirmDelete(uuid, timeFormatted, value, notes)} 25 | > 26 | 27 |
28 |
29 | 30 | 31 | ); 32 | }; 33 | 34 | export const MoodHistoryTableHeader = () => ( 35 | 36 | 37 | Time 38 | Mood Value 39 | Notes 40 | Source 41 | Actions 42 | 43 | 44 | ); 45 | -------------------------------------------------------------------------------- /assets/js/mood_log/mood_events_view.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { AddMoodEvent } from "./add_mood_event"; 4 | import { MoodEntryLogTable } from "./mood_events_table"; 5 | import { BasePaginatedLogView } from "../resources_table/resource_view"; 6 | 7 | export class MoodEventsLogView extends BasePaginatedLogView { 8 | constructor() { 9 | super(); 10 | this.resourceName = "mood_logs"; 11 | } 12 | 13 | render() { 14 | return ( 15 |
16 | 17 | 24 |
25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /assets/js/productivity_log/productivity_event_view.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { AddProductivityEvent } from "./add_productivity_event"; 3 | import { ProductivityLogTable } from "./productivity_table"; 4 | import { BasePaginatedLogView } from "../resources_table/resource_view"; 5 | 6 | export class ProductivityLogView extends BasePaginatedLogView { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | eventHistory: [{}], 11 | loadedHistory: false 12 | }; 13 | this.addEventEntry = this.addEventEntry.bind(this); 14 | this.getEventHistory = this.getEventHistory.bind(this); 15 | this.resourceName = "productivity_log"; 16 | } 17 | 18 | render() { 19 | return ( 20 |
21 | 22 | 29 |
30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /assets/js/productivity_log/productivity_table.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { CubeLoadingStyle } from "../constants/loading_styles"; 3 | import { BaseLogTable } from "../resources_table/resource_table"; 4 | import { 5 | ProductivityHistoryRow, 6 | ProductivityHistoryTableHeader 7 | } from "./constants"; 8 | 9 | export class ProductivityLogTable extends BaseLogTable { 10 | getTableRender() { 11 | const historicalData = this.props.eventHistory; 12 | const historicalDataKeys = Object.keys(historicalData); 13 | 14 | return ( 15 | 16 | 17 | 18 | {historicalDataKeys.map(key => ( 19 | 20 | ))} 21 | 22 |
23 | ); 24 | } 25 | 26 | renderReady() { 27 | if (!this.props.renderReady) { 28 | return ; 29 | } 30 | 31 | return ( 32 |
33 |
34 | {this.getNavPaginationControlRender()} 35 |
36 | {this.getTableRender()} 37 | {this.getNavPaginationControlRender()} 38 |
39 | ); 40 | } 41 | 42 | render() { 43 | return ( 44 |
45 |
46 | 47 | Productivity History 48 |
49 | {this.renderReady()} 50 |
51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /assets/js/resources_table/resource_view.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { getFetchJSONAPI } from "../utils/fetch_utils"; 3 | 4 | export class BasePaginatedLogView extends Component { 5 | constructor() { 6 | super(); 7 | this.state = { 8 | loadedHistory: false 9 | }; 10 | 11 | // This gets set by inheriting classes 12 | this.resourceName = null; 13 | } 14 | 15 | componentDidMount() { 16 | this.getEventHistory(); 17 | } 18 | 19 | getEventHistory = (page = 1) => { 20 | // Fetch the specific page we want, defaulting at 1 21 | const url = `api/v1/${this.resourceName}/?page=${page}`; 22 | getFetchJSONAPI(url).then(responseData => { 23 | this.setState({ eventHistory: responseData.results }); 24 | this.setState({ currentPageNumber: responseData.current_page }); 25 | this.setState({ lastPageNumber: responseData.last_page }); 26 | 27 | // After we've gotten the data, now safe to render 28 | this.setState({ loadedHistory: true }); 29 | }); 30 | }; 31 | 32 | addEventEntry = entry => { 33 | let updatedEventHistory = [entry, ...this.state.eventHistory.slice()]; 34 | this.setState({ eventHistory: updatedEventHistory }); 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /assets/js/routing/routing_utils.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { DATE_REQUEST_FORMAT } from "../constants/dates_and_times"; 3 | import { 4 | DASHBOARD_DAILY_OVERVIEW_ANALYTICS_URL, 5 | DASHBOARD_SUPPLEMENT_OVERVIEW_ANALYTICS_URL 6 | } from "../constants/urls"; 7 | 8 | export const getDailyOverViewURLFromDate = date => { 9 | const dateString = date.format(DATE_REQUEST_FORMAT); 10 | return `${DASHBOARD_DAILY_OVERVIEW_ANALYTICS_URL}${dateString}`; 11 | }; 12 | 13 | export const getSupplementOverviewURLFromUUID = uuid => { 14 | return `${DASHBOARD_SUPPLEMENT_OVERVIEW_ANALYTICS_URL}${uuid}`; 15 | }; 16 | 17 | export const getSupplementAnalyticsSummaryURL = supplement => { 18 | return `/api/v1/supplements/${supplement.uuid}/analytics/summary/`; 19 | }; 20 | 21 | export const getSupplementSleepAnalyticsURL = supplement => { 22 | return `/api/v1/supplements/${supplement.uuid}/analytics/sleep/`; 23 | }; 24 | 25 | export const getSupplementProductivityAnalyticsURL = supplement => { 26 | return `/api/v1/supplements/${supplement.uuid}/analytics/productivity/`; 27 | }; 28 | 29 | export const getSupplementDosagesAnalyticsURL = supplement => { 30 | return `/api/v1/supplements/${supplement.uuid}/analytics/dosages/`; 31 | }; 32 | 33 | export const getSupplementAggregatesAnalyticsURL = supplement => { 34 | return `/api/v1/supplements/${supplement.uuid}/log/aggregate/`; 35 | }; 36 | -------------------------------------------------------------------------------- /assets/js/sleep_log/sleep_events_view.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { AddSleepEvent } from "./add_sleep_event"; 4 | import { SleepEntryLogTable } from "./sleep_events_table"; 5 | import { BasePaginatedLogView } from "../resources_table/resource_view"; 6 | 7 | export class SleepEventsLogView extends BasePaginatedLogView { 8 | constructor(props) { 9 | super(props); 10 | this.resourceName = "sleep_activities"; 11 | } 12 | 13 | render() { 14 | return ( 15 |
16 | 17 | 24 |
25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /assets/js/supplement_reminders/supplement_reminder_table.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BaseLogTable } from "../resources_table/resource_table"; 3 | import { 4 | SupplementReminderRow, 5 | SupplementReminderTableHeader 6 | } from "./constants"; 7 | 8 | export class SupplementReminderTable extends BaseLogTable { 9 | constructor(props) { 10 | const { reminders } = props; 11 | super(); 12 | this.state = { 13 | reminders: reminders 14 | }; 15 | 16 | this.resourceURL = "/api/v1/supplement_reminders/"; 17 | } 18 | 19 | confirmDelete = (uuid, name, reminder_time) => { 20 | const answer = confirm( 21 | `WARNING: This will delete the following supplement reminder \n\n${name} at ${reminder_time} \n\nConfirm? ` 22 | ); 23 | 24 | if (answer) { 25 | this.deleteUUID(uuid); 26 | } 27 | }; 28 | 29 | renderTable() { 30 | if (!this.state.reminders) { 31 | return
; 32 | } 33 | 34 | const reminders = this.state.reminders; 35 | const remindersKeys = Object.keys(reminders); 36 | 37 | return ( 38 |
39 | 40 | 41 | 42 | {remindersKeys.map(key => ( 43 | 48 | ))} 49 | 50 |
51 |
52 | ); 53 | } 54 | 55 | render() { 56 | return ( 57 |
58 |
59 | 60 | Supplements 61 |
62 | {this.renderTable()} 63 |
64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /assets/js/supplement_reminders/supplement_reminders_view.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import { AddSupplementReminderView } from "./add_supplement_reminder"; 4 | import { SupplementReminderTable } from "./supplement_reminder_table"; 5 | import { getFetchJSONAPI } from "../utils/fetch_utils"; 6 | 7 | export class SupplementRemindersView extends Component { 8 | constructor() { 9 | super(); 10 | 11 | this.state = {}; 12 | this.getReminders(); 13 | } 14 | 15 | getReminders() { 16 | const url = "api/v1/supplement_reminders/"; 17 | getFetchJSONAPI(url).then(responseData => { 18 | this.setState({ supplementReminders: responseData }); 19 | }); 20 | } 21 | 22 | render() { 23 | if (!this.state.supplementReminders) { 24 | return
; 25 | } 26 | 27 | return ( 28 |
29 |
30 | 31 | Add Texting Reminders (Limit 5 A Day) 32 | 33 |
34 | 35 | 36 |
37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /assets/js/supplements/add_supplement.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { CreateSupplementThenReload } from "./constants"; 3 | 4 | export class AddSupplementView extends Component { 5 | constructor() { 6 | super(); 7 | } 8 | 9 | addSupplementFormData = e => { 10 | e.preventDefault(); 11 | CreateSupplementThenReload(this.supplementName.value); 12 | }; 13 | 14 | render() { 15 | return ( 16 |
17 |
18 | Create Supplement (Per Serving) 19 |
20 |
21 |
this.addSupplementFormData(e)}> 22 |
23 |
24 | 25 | this.supplementName = input} 30 | /> 31 |
32 |
33 | 34 | 42 | 43 | 44 |
45 |
46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /assets/js/supplements/constants.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import moment from "moment"; 3 | import { Link } from "react-router-dom"; 4 | import { getSupplementOverviewURLFromUUID } from "../routing/routing_utils"; 5 | import { DATETIME_CREATED_FORMAT } from "../constants/dates_and_times"; 6 | import { SUPPLEMENT_RESOURCE_URL } from "../constants/urls"; 7 | import { postFetchJSONAPI } from "../utils/fetch_utils"; 8 | 9 | export const SupplementHistoryTableHeader = () => ( 10 | 11 | 12 | Name 13 | Actions 14 | Date Added 15 | 16 | 17 | ); 18 | 19 | export const SupplementRow = props => { 20 | const data = props.object; 21 | 22 | const { uuid, name } = data; 23 | const dateCreated = data.created; 24 | const timeFormatted = moment(dateCreated).format(DATETIME_CREATED_FORMAT); 25 | const supplementOverviewLink = getSupplementOverviewURLFromUUID(uuid); 26 | 27 | return ( 28 | 29 | {name} 30 | 31 |
32 |
props.selectModalEdit(data)}> 33 | 34 |
35 |   36 |
props.confirmDelete(uuid, name)} 39 | > 40 | 41 |
42 |
43 | 44 | {timeFormatted} 45 | 46 | ); 47 | }; 48 | 49 | export const CreateSupplementThenReload = supplementName => { 50 | const params = { 51 | name: supplementName 52 | }; 53 | 54 | postFetchJSONAPI(SUPPLEMENT_RESOURCE_URL, params).then(responseData => { 55 | window.location.reload(); 56 | }); 57 | }; 58 | -------------------------------------------------------------------------------- /assets/js/supplements/supplements_view.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { SupplementTable } from "./supplements_table"; 3 | import { AddSupplementView } from "./add_supplement"; 4 | 5 | export class SupplementView extends Component { 6 | render() { 7 | return ( 8 |
9 | 10 | 11 |
12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /assets/js/supplements_log/supplement_log_view.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { AddSupplementLog } from "./add_supplement_log"; 4 | import { SupplementLogTable } from "./supplement_log_table"; 5 | import { BasePaginatedLogView } from "../resources_table/resource_view"; 6 | import { getFetchJSONAPI } from "../utils/fetch_utils"; 7 | import { SUPPLEMENT_STACKS_RESOURCE_URL } from "../constants/urls"; 8 | import { SUPPLEMENT_RESOURCE_URL } from "../constants/urls"; 9 | 10 | export class SupplementLogView extends BasePaginatedLogView { 11 | constructor() { 12 | super(); 13 | this.resourceName = "supplement_events"; 14 | } 15 | 16 | componentDidMount() { 17 | this.getEventHistory(); 18 | this.getSupplements(); 19 | this.getSupplementStacks(); 20 | } 21 | 22 | getSupplements() { 23 | getFetchJSONAPI(SUPPLEMENT_RESOURCE_URL).then(responseData => { 24 | this.setState({ supplements: responseData }); 25 | }); 26 | } 27 | 28 | getSupplementStacks() { 29 | getFetchJSONAPI(SUPPLEMENT_STACKS_RESOURCE_URL).then(responseData => { 30 | this.setState({ supplementStacks: responseData }); 31 | }); 32 | } 33 | 34 | render() { 35 | return ( 36 |
37 | 42 | 50 |
51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /assets/js/supplements_stacks/constants.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import moment from "moment"; 3 | import { READABLE_DATE_TIME_FORMAT } from "../constants/dates_and_times"; 4 | 5 | export const SupplementStackRow = props => { 6 | const data = props.object; 7 | const { name, uuid, created, compositions } = data; 8 | const createTimeFormat = moment(created).format(READABLE_DATE_TIME_FORMAT); 9 | 10 | let compositionsFormat; 11 | if (compositions) { 12 | compositionsFormat = compositions.map(e => { 13 | return `${e.quantity} ${e.supplement.name}`; 14 | }); 15 | } 16 | 17 | compositionsFormat = compositionsFormat.join(", "); 18 | 19 | return ( 20 | 21 | {name} 22 | 23 | {compositions.length > 0 24 | ? compositionsFormat 25 | :
props.selectedStackChange(data)} 28 | > 29 | Click to add a supplement 30 |
} 31 | 32 | 33 |
34 |
props.selectedStackChange(data)} 37 | > 38 | 39 |
40 |   41 |
props.confirmDelete(uuid, name)} 44 | > 45 | 46 |
47 |
48 | 49 | {createTimeFormat} 50 | 51 | ); 52 | }; 53 | 54 | export const SupplementStackTableHeader = () => ( 55 | 56 | 57 | Stack Name 58 | Supplements 59 | Actions 60 | Created 61 | 62 | 63 | ); 64 | -------------------------------------------------------------------------------- /assets/js/supplements_stacks/supplement_stack_view.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { AddSupplementStack } from "./add_supplement_stack"; 4 | import { SupplementStackTable } from "./supplement_stack_table"; 5 | import { BaseLogTable } from "../resources_table/resource_table"; 6 | 7 | export class SupplementsStackView extends BaseLogTable { 8 | constructor() { 9 | super(); 10 | this.resourceName = "supplements_stacks"; 11 | } 12 | 13 | render() { 14 | return ( 15 |
16 | 17 | 18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /assets/js/user_activities_events_log/user_activites_events_view.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BasePaginatedLogView } from "../resources_table/resource_view"; 3 | import { AddUserActivityEvent } from "./add_user_activities_event"; 4 | import { UserActivityEventLogTable } from "./user_activities_events_table"; 5 | import { JSON_AUTHORIZATION_HEADERS } from "../constants/requests"; 6 | 7 | export class UserActivitiesEventsLogView extends BasePaginatedLogView { 8 | constructor() { 9 | super(); 10 | this.resourceName = "user_activity_events"; 11 | // Set the state so ActivityTypes 12 | this.state.loadedActivityTypes = false; 13 | } 14 | 15 | componentDidMount() { 16 | // Override base class of componentDidMount 17 | this.getEventHistory(); 18 | this.getPossibleActivities(); 19 | } 20 | 21 | getPossibleActivities() { 22 | fetch("/api/v1/user_activities/", { 23 | method: "GET", 24 | headers: JSON_AUTHORIZATION_HEADERS 25 | }) 26 | .then(response => { 27 | return response.json(); 28 | }) 29 | .then(responseData => { 30 | this.setState({ userActivityTypes: responseData.results }); 31 | this.setState({ loadedActivityTypes: true }); 32 | }); 33 | } 34 | 35 | render() { 36 | return ( 37 |
38 | 43 | 53 |
54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /assets/js/user_activities_log/constants.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { TrueCheckBox } from "../constants/designs"; 3 | 4 | export const UserActivityHistoryRow = props => { 5 | const data = props.object; 6 | const { 7 | name, 8 | is_significant_activity, 9 | is_negative_activity, 10 | uuid, 11 | is_all_day_activity 12 | } = data; 13 | 14 | return ( 15 | 16 | {name} 17 | {is_significant_activity ? :
} 18 | {is_all_day_activity ? :
} 19 | {is_negative_activity ? :
} 20 | 21 |
22 |
props.selectModalEdit(data)}> 23 | 24 |
25 |   26 |
props.confirmDelete(uuid, name)} 29 | > 30 | 31 |
32 |
33 | 34 | 35 | ); 36 | }; 37 | 38 | export const UserActivityHistoryTableHeader = () => ( 39 | 40 | 41 | Activity Name 42 | Significant 43 | All Day 44 | Negative 45 | Actions 46 | 47 | 48 | ); 49 | 50 | export const RenderCreateActivityButton = () => { 51 | return ( 52 |
53 | Create Activity Type 54 |
55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /assets/js/user_activities_log/user_activities_view.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BasePaginatedLogView } from "../resources_table/resource_view"; 3 | import { UserActivityLogTable } from "./user_activities_table"; 4 | import { AddUserActivity } from "./add_user_activity"; 5 | 6 | export class UserActivitiesLogView extends BasePaginatedLogView { 7 | constructor() { 8 | super(); 9 | this.resourceName = "user_activities"; 10 | } 11 | 12 | render() { 13 | return ( 14 |
15 | 16 | 23 |
24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assets/js/utils/fetch_utils.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | JSON_AUTHORIZATION_HEADERS, 4 | JSON_POST_AUTHORIZATION_HEADERS 5 | } from "../constants/requests"; 6 | import { LOGOUT_URL } from "../constants/urls"; 7 | 8 | export const getFetch = url => { 9 | return fetch(url, { 10 | method: "GET", 11 | headers: JSON_AUTHORIZATION_HEADERS 12 | }); 13 | }; 14 | 15 | export const getFetchJSONAPI = url => { 16 | return getFetch(url).then(response => { 17 | // If not authenticated, means token has expired 18 | // force a hard logout 19 | if (response.status === 401) { 20 | window.location.assign(LOGOUT_URL); 21 | } 22 | const results = response.json(); 23 | return results; 24 | }); 25 | }; 26 | 27 | export const postFetchJSONAPI = (url, postParams) => { 28 | return fetch(url, { 29 | method: "POST", 30 | headers: JSON_POST_AUTHORIZATION_HEADERS, 31 | body: JSON.stringify(postParams) 32 | }).then(response => { 33 | if (!response.ok) { 34 | alert("Invalid Request Entered"); 35 | } 36 | return response.json(); 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /assets/js/utils/select_utils.js: -------------------------------------------------------------------------------- 1 | import { CreateSupplementThenReload } from "../supplements/constants"; 2 | 3 | export const SelectDetailsSerializer = detailsList => { 4 | // Formats a list of resources into a format that react-select uses 5 | 6 | const detailsKeys = Object.keys(detailsList); 7 | const selectDetails = detailsKeys.map(e => { 8 | let label = detailsList[e].name; 9 | if (detailsList[e].description) { 10 | label = label + " - " + detailsList[e].description; 11 | } 12 | return { 13 | value: e, 14 | label: label 15 | }; 16 | }); 17 | 18 | return selectDetails; 19 | }; 20 | 21 | export const CreateSupplementOnNewOptionClick = props => { 22 | const label = props.label; 23 | CreateSupplementThenReload(label); 24 | }; 25 | -------------------------------------------------------------------------------- /betterself/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | 3 | # This will make sure the app is always imported when 4 | # Django starts so that shared_task will use this app. 5 | from .celery import app as celery_app 6 | 7 | # -*- coding: utf-8 -*- 8 | __version__ = '0.1.0' 9 | __version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')]) 10 | 11 | __all__ = ['celery_app'] 12 | -------------------------------------------------------------------------------- /betterself/base_models.py: -------------------------------------------------------------------------------- 1 | # maybe change the location of this file, but don't have a better place at the moment 2 | import uuid as uuid 3 | 4 | from django.conf import settings 5 | from django.db import models 6 | 7 | 8 | class BaseModel(models.Model): 9 | created = models.DateTimeField(auto_now_add=True) 10 | modified = models.DateTimeField(auto_now=True) 11 | uuid = models.UUIDField(primary_key=False, default=uuid.uuid4, editable=False, unique=True) 12 | 13 | class Meta: 14 | abstract = True 15 | 16 | def __str__(self): 17 | return self.name if hasattr(self, 'name') else self.__class__.__name__ 18 | 19 | def __repr__(self): 20 | return self.__str__() 21 | 22 | 23 | class BaseModelWithUserGeneratedContent(BaseModel): 24 | user = models.ForeignKey(settings.AUTH_USER_MODEL) 25 | 26 | class Meta: 27 | abstract = True 28 | -------------------------------------------------------------------------------- /betterself/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | # -*- coding: utf-8 -*- 7 | -------------------------------------------------------------------------------- /betterself/contrib/sites/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | # -*- coding: utf-8 -*- 7 | -------------------------------------------------------------------------------- /betterself/contrib/sites/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.contrib.sites.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Site', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), 18 | ('domain', models.CharField(verbose_name='domain name', max_length=100, validators=[django.contrib.sites.models._simple_domain_name_validator])), 19 | ('name', models.CharField(verbose_name='display name', max_length=50)), 20 | ], 21 | options={ 22 | 'verbose_name_plural': 'sites', 23 | 'verbose_name': 'site', 24 | 'db_table': 'django_site', 25 | 'ordering': ('domain',), 26 | }, 27 | managers=[ 28 | (b'objects', django.contrib.sites.models.SiteManager()), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /betterself/contrib/sites/migrations/0002_set_site_domain_and_name.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | # -*- coding: utf-8 -*- 7 | 8 | from __future__ import unicode_literals 9 | 10 | from django.conf import settings 11 | from django.db import migrations 12 | 13 | 14 | def update_site_forward(apps, schema_editor): 15 | """Set site domain and name.""" 16 | Site = apps.get_model('sites', 'Site') 17 | Site.objects.update_or_create( 18 | id=settings.SITE_ID, 19 | defaults={ 20 | 'domain': 'betterself.io', 21 | 'name': 'betterself' 22 | } 23 | ) 24 | 25 | 26 | def update_site_backward(apps, schema_editor): 27 | """Revert site domain and name to default.""" 28 | Site = apps.get_model('sites', 'Site') 29 | Site.objects.update_or_create( 30 | id=settings.SITE_ID, 31 | defaults={ 32 | 'domain': 'example.com', 33 | 'name': 'example.com' 34 | } 35 | ) 36 | 37 | 38 | class Migration(migrations.Migration): 39 | 40 | dependencies = [ 41 | ('sites', '0001_initial'), 42 | ] 43 | 44 | operations = [ 45 | migrations.RunPython(update_site_forward, update_site_backward), 46 | ] 47 | -------------------------------------------------------------------------------- /betterself/contrib/sites/migrations/0003_auto_20160524_0259.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-24 02:59 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.sites.models 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('sites', '0002_set_site_domain_and_name'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterModelManagers( 17 | name='site', 18 | managers=[ 19 | ('objects', django.contrib.sites.models.SiteManager()), 20 | ], 21 | ), 22 | migrations.AlterField( 23 | model_name='site', 24 | name='domain', 25 | field=models.CharField(max_length=100, unique=True, validators=[django.contrib.sites.models._simple_domain_name_validator], verbose_name='domain name'), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /betterself/contrib/sites/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | # -*- coding: utf-8 -*- 7 | -------------------------------------------------------------------------------- /betterself/static/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /betterself/static/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /betterself/static/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /betterself/static/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /betterself/static/css/project.css: -------------------------------------------------------------------------------- 1 | /* These styles are generated from project.scss. */ 2 | 3 | .alert-debug { 4 | color: black; 5 | background-color: white; 6 | border-color: #d6e9c6; 7 | } 8 | 9 | .alert-error { 10 | color: #b94a48; 11 | background-color: #f2dede; 12 | border-color: #eed3d7; 13 | } 14 | 15 | /* This is a fix for the bootstrap4 alpha release */ 16 | @media (max-width: 47.9em) { 17 | .navbar-nav .nav-item { 18 | float: none; 19 | width: 100%; 20 | display: inline-block; 21 | } 22 | 23 | .navbar-nav .nav-item + .nav-item { 24 | margin-left: 0; 25 | } 26 | 27 | .nav.navbar-nav.pull-xs-right { 28 | float: none !important; 29 | } 30 | } 31 | 32 | /* Display django-debug-toolbar. 33 | See https://github.com/django-debug-toolbar/django-debug-toolbar/issues/742 34 | and https://github.com/pydanny/cookiecutter-django/issues/317 35 | */ 36 | [hidden][style="display: block;"] { 37 | display: block !important; 38 | } 39 | -------------------------------------------------------------------------------- /betterself/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /betterself/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /betterself/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /betterself/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /betterself/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /betterself/static/images/dashboard/dashboard_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/dashboard/dashboard_example.png -------------------------------------------------------------------------------- /betterself/static/images/dashboard/dashboard_example_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/dashboard/dashboard_example_2.png -------------------------------------------------------------------------------- /betterself/static/images/dashboard/dashboard_example_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/dashboard/dashboard_example_3.png -------------------------------------------------------------------------------- /betterself/static/images/dashboard/dashboard_example_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/dashboard/dashboard_example_4.png -------------------------------------------------------------------------------- /betterself/static/images/dashboard/dashboard_example_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/dashboard/dashboard_example_5.jpg -------------------------------------------------------------------------------- /betterself/static/images/dashboard/dashboard_example_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/dashboard/dashboard_example_6.jpg -------------------------------------------------------------------------------- /betterself/static/images/dashboard/dashboard_heart_rate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/dashboard/dashboard_heart_rate.jpg -------------------------------------------------------------------------------- /betterself/static/images/dashboard/dashboard_supplements_events_history.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/dashboard/dashboard_supplements_events_history.jpg -------------------------------------------------------------------------------- /betterself/static/images/header/control_panel/brain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/header/control_panel/brain.png -------------------------------------------------------------------------------- /betterself/static/images/header/control_panel/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/header/control_panel/heart.png -------------------------------------------------------------------------------- /betterself/static/images/header/control_panel/heart_2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /betterself/static/images/login/login_side_photo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/login/login_side_photo.jpeg -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/favicon.png -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/favicon_legacy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/favicon_legacy.png -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/favicon_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/favicon_transparent.png -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/png/color_logo_transparent_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/png/color_logo_transparent_background.png -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/png/color_logo_transparent_background_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/png/color_logo_transparent_background_small.png -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/png/dark_logo_transparent_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/png/dark_logo_transparent_background.png -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/png/dark_logo_transparent_background_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/png/dark_logo_transparent_background_small.png -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/png/white_logo_color_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/png/white_logo_color_background.png -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/png/white_logo_color_background_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/png/white_logo_color_background_small.png -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/png/white_logo_transparent_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/png/white_logo_transparent_background.png -------------------------------------------------------------------------------- /betterself/static/images/logos/logojoy/png/white_logo_transparent_background_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/static/images/logos/logojoy/png/white_logo_transparent_background_small.png -------------------------------------------------------------------------------- /betterself/static/sass/project.scss: -------------------------------------------------------------------------------- 1 | // project specific CSS goes here 2 | 3 | // Alert colors 4 | 5 | $white: #fff; 6 | $mint-green: #d6e9c6; 7 | $black: #000; 8 | $pink: #f2dede; 9 | $dark-pink: #eed3d7; 10 | $red: #b94a48; 11 | 12 | // bootstrap alert CSS, translated to the django-standard levels of 13 | // debug, info, success, warning, error 14 | 15 | .alert-debug { 16 | background-color: $white; 17 | border-color: $mint-green; 18 | color: $black; 19 | } 20 | 21 | .alert-error { 22 | background-color: $pink; 23 | border-color: $dark-pink; 24 | color: $red; 25 | } 26 | 27 | // This is a fix for the bootstrap4 alpha release 28 | 29 | @media (max-width: 47.9em) { 30 | .navbar-nav .nav-item { 31 | display: inline-block; 32 | float: none; 33 | width: 100%; 34 | } 35 | 36 | .navbar-nav .nav-item + .nav-item { 37 | margin-left: 0; 38 | } 39 | 40 | .nav.navbar-nav.pull-xs-right { 41 | float: none !important; 42 | } 43 | } 44 | 45 | // Display django-debug-toolbar. 46 | // See https://github.com/django-debug-toolbar/django-debug-toolbar/issues/742 47 | // and https://github.com/pydanny/cookiecutter-django/issues/317 48 | 49 | [hidden][style="display: block;"] { 50 | display: block !important; 51 | } 52 | -------------------------------------------------------------------------------- /betterself/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Page Not found{% endblock %} 4 | 5 | {% block content %} 6 |

Page Not found

7 | 8 |

This is not the page you were looking for.

9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /betterself/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Server Error{% endblock %} 4 | 5 | {% block content %} 6 |

Ooops!!! 500

7 | 8 |

Looks like something went wrong!

9 | 10 |

We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.

11 | {% endblock content %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /betterself/templates/account/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %} 3 | -------------------------------------------------------------------------------- /betterself/templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} 7 | 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |

{% trans "Confirm E-mail Address" %}

14 | 15 | {% if confirmation %} 16 | 17 | {% user_display confirmation.email_address.user as user_display %} 18 | 19 |

{% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

20 | 21 |
22 | {% csrf_token %} 23 | 24 |
25 | 26 | {% else %} 27 | 28 | {% url 'account_email' as email_url %} 29 | 30 |

{% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktrans %}

31 | 32 | {% endif %} 33 |
34 |
35 |
36 | {% endblock %} 37 | 38 | -------------------------------------------------------------------------------- /betterself/templates/account/email_confirmed.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} 7 | 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |

{% trans "Confirm E-mail Address" %}

14 | 15 | {% user_display email_address.user as user_display %} 16 | 17 |

{% blocktrans with email_address.email as email %}You have confirmed that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

18 |
19 |
20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /betterself/templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Sign Out" %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 | 12 |

{% trans "Sign Out" %}

13 | 14 |

{% trans 'Are you sure you want to sign out?' %}

15 | 16 |
17 | {% csrf_token %} 18 | {% if redirect_field_value %} 19 | 20 | {% endif %} 21 | 22 |
23 |
24 |
25 |
26 | 27 | {% endblock %} 28 | 29 | -------------------------------------------------------------------------------- /betterself/templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |

{% trans "Change Password" %}

12 | 13 |
14 | {% csrf_token %} 15 | {{ form|crispy }} 16 | 17 |
18 |
19 |
20 |
21 | {% endblock %} 22 | 23 | -------------------------------------------------------------------------------- /betterself/templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Password Reset" %}{% endblock %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 | 14 |

{% trans "Password Reset" %}

15 | {% if user.is_authenticated %} 16 | {% include "account/snippets/already_logged_in.html" %} 17 | {% endif %} 18 | 19 |

{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

20 | 21 |
22 | {% csrf_token %} 23 | {{ form|crispy }} 24 | 25 |
26 | 27 |

{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}

28 |
29 |
30 |
31 | {% endblock %} 32 | 33 | {% block javascript %} 34 | {{ block.super }} 35 | 38 | {% endblock javascript %} 39 | 40 | -------------------------------------------------------------------------------- /betterself/templates/account/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Password Reset" %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |

{% trans "Password Reset" %}

13 | 14 | {% if user.is_authenticated %} 15 | {% include "account/snippets/already_logged_in.html" %} 16 | {% endif %} 17 | 18 |

{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /betterself/templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |

{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

13 | 14 | {% if token_fail %} 15 | {% url 'account_reset_password' as passwd_reset_url %} 16 |

{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

17 | {% else %} 18 | {% if form %} 19 |
20 | {% csrf_token %} 21 | {{ form|crispy }} 22 | 23 |
24 | {% else %} 25 |

{% trans 'Your password is now changed.' %}

26 | {% endif %} 27 | {% endif %} 28 |
29 |
30 |
31 | {% endblock %} 32 | 33 | -------------------------------------------------------------------------------- /betterself/templates/account/password_reset_from_key_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 5 | 6 | {% block content %} 7 |
8 |
9 |
10 |

{% trans "Change Password" %}

11 |

{% trans 'Your password is now changed.' %}

12 |
13 |
14 |
15 | {% endblock %} 16 | 17 | -------------------------------------------------------------------------------- /betterself/templates/account/password_set.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "account/base.html" %} 3 | 4 | {% load i18n crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Set Password" %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |

{% trans "Set Password" %}

13 | 14 |
15 | {% csrf_token %} 16 | {{ form|crispy }} 17 | 18 |
19 |
20 |
21 |
22 | {% endblock %} 23 | 24 | -------------------------------------------------------------------------------- /betterself/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block title %}{% trans "Signup" %}{% endblock title %} 7 | 8 | {% block content %} 9 | 10 |
11 |
12 |
13 |

{% trans "Sign Up" %}

14 | 15 |

{% blocktrans %}Already have an account? Then please sign in.{% endblocktrans %}

16 | 17 | 25 |
26 |
27 |
28 | 29 | 30 | {% endblock content %} 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /betterself/templates/account/signup_closed.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Sign Up Closed" %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |

{% trans "Sign Up Closed" %}

12 | 13 |

{% trans "We are sorry, but the sign up is currently closed." %}

14 |
15 |
16 |
17 | {% endblock %} 18 | 19 | -------------------------------------------------------------------------------- /betterself/templates/account/verification_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |

{% trans "Verify Your E-mail Address" %}

12 | 13 |

{% blocktrans %}We have sent an e-mail to {{ email }} for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

14 |
15 |
16 |
17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /betterself/templates/account/verified_email_required.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |

{% trans "Verify Your E-mail Address" %}

12 | 13 | {% url 'account_email' as email_url %} 14 | 15 |

{% blocktrans %}This part of the site requires us to verify that 16 | you are who you claim to be. For this purpose, we require that you 17 | verify ownership of your e-mail address. {% endblocktrans %}

18 | 19 |

{% blocktrans %}We have sent an e-mail to you for 20 | verification. Please click on the link inside this e-mail. Please 21 | contact us if you do not receive it within a few minutes.{% endblocktrans %}

22 | 23 |

{% blocktrans %}Note: you can still change your e-mail address.{% endblocktrans %}

24 |
25 |
26 |
27 | {% endblock %} 28 | 29 | -------------------------------------------------------------------------------- /betterself/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles i18n %} 2 | 3 | 4 | 5 | 6 | BetterSelf - Your Body's Dashboard 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% block content %} 26 | {% endblock content %} 27 | {% block modal %} 28 | {% endblock modal %} 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /betterself/templates/users/user_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block title %}User: {{ object.username }}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | 9 |
10 |
11 |

{{ object.username }}

12 | {% if object.name %} 13 |

{{ object.name }}

14 | {% endif %} 15 |
16 |
17 | 18 | {% if object == request.user %} 19 | 20 |
21 |
22 | My Info 23 | My Dashboard 24 |
25 |
26 | 27 | {% endif %} 28 | 29 |
30 | {% endblock content %} 31 | 32 | -------------------------------------------------------------------------------- /betterself/templates/users/user_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block title %}{{ user.username }}{% endblock %} 5 | 6 | {% block content %} 7 |

{{ user.username }}

8 |
9 | {% csrf_token %} 10 | {{ form|crispy }} 11 |
12 |
13 | 14 |
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /betterself/templates/users/user_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %}{% load i18n %} 3 | {% block title %}Members{% endblock %} 4 | 5 | {% block content %} 6 | 7 |
8 | 9 |

Users

10 | 11 |
12 | {% for user in user_list %} 13 | 14 |

{{ user.username }}

15 |
16 | {% endfor %} 17 | 18 |
19 | 20 |
21 | 22 | {% endblock content %} 23 | -------------------------------------------------------------------------------- /betterself/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from betterself.utils.django_utils import create_django_choice_tuple_from_list 4 | 5 | 6 | class DataUtilsTester(TestCase): 7 | def test_create_django_choice_tuple_from_list(self): 8 | list_a = ['jack', 'jill'] 9 | returned_tuple = create_django_choice_tuple_from_list(list_a) 10 | 11 | expected_result = (('jack', 'Jack'), ('jill', 'Jill')) 12 | self.assertEqual(returned_tuple, expected_result) 13 | 14 | def test_create_django_choice_tuple_from_list_with_strings(self): 15 | list_a = [1, 'jill'] 16 | returned_tuple = create_django_choice_tuple_from_list(list_a) 17 | 18 | expected_result = ((1, 1), ('jill', 'Jill')) 19 | self.assertEqual(returned_tuple, expected_result) 20 | -------------------------------------------------------------------------------- /betterself/users/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /betterself/users/adapters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf import settings 3 | from allauth.account.adapter import DefaultAccountAdapter 4 | from allauth.socialaccount.adapter import DefaultSocialAccountAdapter 5 | 6 | 7 | class AccountAdapter(DefaultAccountAdapter): 8 | def is_open_for_signup(self, request): 9 | return getattr(settings, 'ACCOUNT_ALLOW_REGISTRATION', True) 10 | 11 | 12 | class SocialAccountAdapter(DefaultSocialAccountAdapter): 13 | def is_open_for_signup(self, request, sociallogin): 14 | return getattr(settings, 'ACCOUNT_ALLOW_REGISTRATION', True) 15 | -------------------------------------------------------------------------------- /betterself/users/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | from django import forms 5 | from django.contrib import admin 6 | from django.contrib.auth.admin import UserAdmin as AuthUserAdmin 7 | from django.contrib.auth.forms import UserChangeForm, UserCreationForm 8 | 9 | from betterself.users.models import User, DemoUserLog 10 | 11 | 12 | class MyUserChangeForm(UserChangeForm): 13 | class Meta(UserChangeForm.Meta): 14 | model = User 15 | 16 | 17 | class MyUserCreationForm(UserCreationForm): 18 | 19 | error_message = UserCreationForm.error_messages.update({ 20 | 'duplicate_username': 'This username has already been taken.' 21 | }) 22 | 23 | class Meta(UserCreationForm.Meta): 24 | model = User 25 | 26 | def clean_username(self): 27 | username = self.cleaned_data['username'] 28 | try: 29 | User.objects.get(username=username) 30 | except User.DoesNotExist: 31 | return username 32 | raise forms.ValidationError(self.error_messages['duplicate_username']) 33 | 34 | 35 | @admin.register(User) 36 | class UserAdmin(AuthUserAdmin): 37 | form = MyUserChangeForm 38 | add_form = MyUserCreationForm 39 | list_display = ('username', 'date_joined', 'email') 40 | ordering = ['-date_joined'] 41 | 42 | def get_queryset(self, request): 43 | qs = super(UserAdmin, self).get_queryset(request) 44 | demo_users_id = DemoUserLog.objects.all().values_list('user__id', flat=True) 45 | return qs.exclude(id__in=demo_users_id) 46 | 47 | 48 | admin.site.register(DemoUserLog) 49 | -------------------------------------------------------------------------------- /betterself/users/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/users/fixtures/__init__.py -------------------------------------------------------------------------------- /betterself/users/fixtures/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | 3 | 4 | class UserFactory(factory.django.DjangoModelFactory): 5 | username = factory.Faker('name') 6 | password = factory.PostGenerationMethodCall('set_password', 'password') 7 | 8 | class Meta: 9 | model = 'users.User' 10 | django_get_or_create = ('username', ) 11 | -------------------------------------------------------------------------------- /betterself/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/users/migrations/__init__.py -------------------------------------------------------------------------------- /betterself/users/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/users/tests/__init__.py -------------------------------------------------------------------------------- /betterself/users/tests/mixins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/users/tests/mixins/__init__.py -------------------------------------------------------------------------------- /betterself/users/tests/mixins/test_mixins.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | 3 | User = get_user_model() 4 | 5 | DEFAULT_CREDENTIALS = { 6 | 'username': 'default', 7 | 'email': 'username@gmail.com', 8 | 'password': 'secret_password', 9 | } 10 | 11 | SECONDARY_DEFAULT_CREDENTIALS = { 12 | 'username': 'tester2', 13 | 'email': 'username@gmail.com', # i hate that django allows multiple emails to be created 14 | 'password': 'secret_password', 15 | } 16 | 17 | 18 | class UsersTestsFixturesMixin(object): 19 | @classmethod 20 | def create_user(cls, credentials=DEFAULT_CREDENTIALS): 21 | # pass username, email and password 22 | user = User.objects.create_user(**credentials) 23 | return user 24 | 25 | @classmethod 26 | def create_authenticated_user_on_client(cls, client, user): 27 | client.force_login(user) 28 | 29 | # just a quick check just in case 30 | assert user.is_authenticated() 31 | 32 | return client 33 | 34 | @classmethod 35 | def create_user_fixtures(cls): 36 | # setup the user once 37 | cls.user_1, _ = User.objects.get_or_create(username='default') 38 | 39 | # create some random fake user_2 to test duplicates and no information leakage 40 | cls.user_2 = cls.create_user(SECONDARY_DEFAULT_CREDENTIALS) 41 | -------------------------------------------------------------------------------- /betterself/users/tests/test_admin.py: -------------------------------------------------------------------------------- 1 | from test_plus.test import TestCase 2 | 3 | from ..admin import MyUserCreationForm 4 | 5 | 6 | class TestMyUserCreationForm(TestCase): 7 | 8 | def setUp(self): 9 | self.user = self.make_user() 10 | 11 | def test_clean_username_success(self): 12 | # Instantiate the form with a new username 13 | form = MyUserCreationForm({ 14 | 'username': 'alamode', 15 | 'password1': '123456', 16 | 'password2': '123456', 17 | }) 18 | # Run is_valid() to trigger the validation 19 | valid = form.is_valid() 20 | self.assertTrue(valid) 21 | 22 | # Run the actual clean_username method 23 | username = form.clean_username() 24 | self.assertEqual('alamode', username) 25 | 26 | def test_clean_username_false(self): 27 | # Instantiate the form with the same username as self.user 28 | form = MyUserCreationForm({ 29 | 'username': self.user.username, 30 | 'password1': '123456', 31 | 'password2': '123456', 32 | }) 33 | # Run is_valid() to trigger the validation, which is going to fail 34 | # because the username is already taken 35 | valid = form.is_valid() 36 | self.assertFalse(valid) 37 | 38 | # The form.errors dict should contain a single error called 'username' 39 | self.assertTrue(len(form.errors) == 1) 40 | self.assertTrue('username' in form.errors) 41 | -------------------------------------------------------------------------------- /betterself/users/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from test_plus.test import TestCase 2 | 3 | from betterself.users.tests.mixins.test_mixins import UsersTestsFixturesMixin 4 | 5 | 6 | class TestUser(TestCase, UsersTestsFixturesMixin): 7 | 8 | def setUp(self): 9 | self.user = self.make_user() 10 | 11 | def test__str__(self): 12 | self.assertEqual( 13 | self.user.__str__(), 14 | 'testuser' # This is the default username for self.make_user() 15 | ) 16 | 17 | def test_get_absolute_url(self): 18 | self.assertEqual( 19 | self.user.get_absolute_url(), 20 | '/users/testuser/' 21 | ) 22 | 23 | def test_user_login(self): 24 | credentials = { 25 | 'username': 'test_user_1', 26 | 'email': 'username@gmail.com', 27 | 'password': 'secret_password', 28 | } 29 | 30 | self.create_user(credentials) 31 | result = self.client.login(**credentials) 32 | 33 | self.assertEqual(result, True) 34 | -------------------------------------------------------------------------------- /betterself/users/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | from django.conf.urls import url 5 | 6 | from betterself.users import views 7 | 8 | 9 | urlpatterns = [ 10 | url(regex=r'^~update/$', view=views.UserUpdateView.as_view(), name='update'), 11 | url(regex=r'^~redirect/$', view=views.UserRedirectView.as_view(), name='redirect'), 12 | url(regex=r'^(?P[\w.@+-]+)/$', view=views.UserDetailView.as_view(), name='detail'), 13 | ] 14 | -------------------------------------------------------------------------------- /betterself/users/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | from django.contrib.auth.mixins import LoginRequiredMixin 5 | from django.core.urlresolvers import reverse 6 | from django.views.generic import DetailView, ListView, RedirectView, UpdateView 7 | from rest_framework.authentication import TokenAuthentication 8 | from rest_auth.registration.views import LoginView 9 | 10 | from .models import User 11 | 12 | 13 | class UserDetailView(LoginRequiredMixin, DetailView): 14 | model = User 15 | # These next two lines tell the view to index lookups by username 16 | slug_field = 'username' 17 | slug_url_kwarg = 'username' 18 | 19 | 20 | class UserRedirectView(LoginRequiredMixin, RedirectView): 21 | permanent = False 22 | 23 | def get_redirect_url(self): 24 | return reverse('users:detail', 25 | kwargs={'username': self.request.user.username}) 26 | 27 | 28 | class UserUpdateView(LoginRequiredMixin, UpdateView): 29 | 30 | fields = ['name', 'timezone'] 31 | 32 | # we already imported User in the view code above, remember? 33 | model = User 34 | 35 | # send the user back to their own page after a successful update 36 | def get_success_url(self): 37 | return reverse('users:detail', 38 | kwargs={'username': self.request.user.username}) 39 | 40 | def get_object(self): 41 | # Only get the User record for the user making the request 42 | return User.objects.get(username=self.request.user.username) 43 | 44 | 45 | class UserListView(LoginRequiredMixin, ListView): 46 | model = User 47 | # These next two lines tell the view to index lookups by username 48 | slug_field = 'username' 49 | slug_url_kwarg = 'username' 50 | 51 | 52 | class LoginViewCustom(LoginView): 53 | authentication_classes = (TokenAuthentication,) 54 | -------------------------------------------------------------------------------- /betterself/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/utils/__init__.py -------------------------------------------------------------------------------- /betterself/utils/api_utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import logging 3 | import numpy as np 4 | import math 5 | 6 | from apis.betterself.v1.constants import UNIQUE_KEY_CONSTANT 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def get_api_value_formatted(key, value, label, data_type=None): 12 | """ 13 | Force a constraint to make sure going forward all the API responses are ideal 14 | 15 | Key: Should be unique for a complete response (think of React's key concept) 16 | Value : Value of Object 17 | data_type : string, value, etc. 18 | label : What the value represents (in proper English) 19 | """ 20 | if not data_type: 21 | data_type = guess_data_type(value) 22 | 23 | # json doesn't render nan well 24 | if data_type == 'float' and math.isnan(value): 25 | value = None 26 | 27 | # taking a step back and thinking about this, the whole dict within key structure is stupid 28 | response = { 29 | UNIQUE_KEY_CONSTANT: key, 30 | 'value': value, 31 | 'data_type': data_type, 32 | 'label': label 33 | } 34 | 35 | return response 36 | 37 | 38 | def guess_data_type(value): 39 | """ 40 | Passes logic to the frontend, a string that becomes useful in rendering how a specific value should be displayed 41 | ie. if certain datetime constants are passed, that can be used to generate links 42 | """ 43 | if isinstance(value, str): 44 | return 'str' 45 | elif isinstance(value, (int, np.int64, np.int32)): 46 | return 'int' 47 | elif isinstance(value, (float, np.float64)): 48 | return 'float' 49 | elif isinstance(value, list): 50 | return 'list' 51 | elif isinstance(value, datetime.date): 52 | return 'date' 53 | elif isinstance(value, datetime.datetime): 54 | return 'datetime' 55 | else: 56 | logger.exception('Unable to determine data type of {}'.format(value)) 57 | -------------------------------------------------------------------------------- /betterself/utils/django_utils.py: -------------------------------------------------------------------------------- 1 | def create_django_choice_tuple_from_list(list_a): 2 | if list_a is None: 3 | return () 4 | 5 | tuples_list = [] 6 | for item in list_a: 7 | if isinstance(item, str): 8 | tuple_item_title = item.title() 9 | else: 10 | tuple_item_title = item 11 | 12 | tuple_item = (item, tuple_item_title) 13 | tuples_list.append(tuple_item) 14 | 15 | return tuple(tuples_list) 16 | -------------------------------------------------------------------------------- /betterself/utils/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/betterself/utils/tests/__init__.py -------------------------------------------------------------------------------- /betterself/utils/tests/test_api_utils.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from apis.betterself.v1.constants import UNIQUE_KEY_CONSTANT 4 | from betterself.utils.api_utils import get_api_value_formatted 5 | 6 | 7 | class TestAPIResponse(TestCase): 8 | def test_get_api_value_formatted_as_expected(self): 9 | key = 'one-potato' 10 | value = 5 11 | data_type = 'number' 12 | label = 'Potato Quantity' 13 | 14 | data = get_api_value_formatted( 15 | key=key, 16 | value=value, 17 | data_type=data_type, 18 | label=label 19 | ) 20 | 21 | expected_response = { 22 | UNIQUE_KEY_CONSTANT: key, 23 | 'value': 5, 24 | 'data_type': 'number', 25 | 'label': 'Potato Quantity' 26 | } 27 | 28 | self.assertEqual( 29 | data, expected_response 30 | ) 31 | -------------------------------------------------------------------------------- /betterself/utils/tests/test_date_utils.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.test import TestCase 3 | 4 | from betterself.utils.date_utils import get_datetime_in_eastern_timezone, EASTERN_TZ 5 | 6 | User = get_user_model() 7 | 8 | 9 | class TestDateUtils(TestCase): 10 | @classmethod 11 | def setUpClass(cls): 12 | super().setUpClass() 13 | 14 | def test_eastern_function_returns_eastern(self): 15 | datetime = get_datetime_in_eastern_timezone(2017, 1, 1, 1, 1, 1) 16 | self.assertEqual(datetime.tzinfo, EASTERN_TZ) 17 | -------------------------------------------------------------------------------- /betterself/utils/tests/test_pandas_utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | import pandas as pd 4 | from django.contrib.auth import get_user_model 5 | from django.test import TestCase 6 | from random import randint 7 | 8 | from betterself.utils.date_utils import get_current_date_months_ago 9 | from betterself.utils.pandas_utils import force_start_end_data_to_dataframe 10 | 11 | User = get_user_model() 12 | 13 | 14 | class PandasUtilsTests(TestCase): 15 | @classmethod 16 | def setUpTestData(cls): 17 | cls.default_user, _ = User.objects.get_or_create(username='default') 18 | 19 | def _get_mock_dataframe(self, user): 20 | two_months_ago = get_current_date_months_ago(2) 21 | one_month_ago = get_current_date_months_ago(1) 22 | default_index = pd.date_range(start=two_months_ago, end=one_month_ago, freq='D') 23 | 24 | random_values = [randint(0, 3) for _ in default_index] 25 | 26 | mock_dataframe = pd.DataFrame(index=default_index, data=random_values) 27 | mock_dataframe = mock_dataframe.tz_localize(user.pytz_timezone) 28 | return mock_dataframe 29 | 30 | def test_dataframe_creation(self): 31 | start_date = get_current_date_months_ago(3) 32 | end_date = datetime.date.today() 33 | 34 | dataframe = self._get_mock_dataframe(self.default_user) 35 | 36 | dataframe_date_appended = force_start_end_data_to_dataframe(user=self.default_user, dataframe=dataframe, 37 | start_date=start_date, end_date=end_date) 38 | 39 | # if it's appended new dates correctly, the index will be greater than before 40 | self.assertGreater(dataframe_date_appended.index.size, dataframe.index.size) 41 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/config/__init__.py -------------------------------------------------------------------------------- /config/development/vagrant/developer_aliases: -------------------------------------------------------------------------------- 1 | # set the default folder where virtualenv is being stored 2 | export WORKON_HOME=/betterself/config/development/virtualenv 3 | export DJANGO_SETTINGS_MODULE=config.settings.local 4 | source /usr/local/bin/virtualenvwrapper.sh 5 | 6 | # Display Git Branch 7 | parse_git_branch() { 8 | git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/' 9 | } 10 | export PS1="\D{ %T} \u@\h \W\[\033[32m\]\$(parse_git_branch)\[\033[00m\] \$ " 11 | # End of Display Git Branch 12 | 13 | workon betterself 14 | cd /betterself 15 | 16 | # use alias like d-XXXXX to represent django do X" 17 | alias dtest="python manage.py test" 18 | alias dtestp="python manage.py test --parallel" 19 | alias drun="python manage.py runserver [::]:9000" 20 | alias dplus="python manage.py shell_plus" 21 | alias dstatic="python manage.py collectstatic --noinput" 22 | alias dshow="python manage.py show_urls" 23 | alias startcelery="celery -A betterself worker -l info" 24 | 25 | # always show what's the git status 26 | git status 27 | 28 | alias gs='git status' 29 | alias gcam="git commit -am" 30 | alias gk='git commit -m' 31 | alias gco='git checkout' 32 | alias gb='git branch -vv' 33 | alias gp='git pull' 34 | alias ga='git add --all' 35 | alias gd='git diff' 36 | alias gg='git grep' 37 | alias gpos='git pull origin staging' 38 | alias gset='git push -u origin' 39 | alias gahead='git branch -vv|grep ahead' 40 | alias gbehind='git branch -vv|grep behind' 41 | 42 | alias ls='ls -la' 43 | alias pack='node_modules/.bin/webpack --watch' 44 | alias start-honcho='honcho start -f Procfile_local' 45 | 46 | export NVM_DIR="$HOME/.nvm" 47 | [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm 48 | nvm use 6.11.4 49 | -------------------------------------------------------------------------------- /config/pagination.py: -------------------------------------------------------------------------------- 1 | from rest_framework.pagination import PageNumberPagination 2 | from rest_framework.response import Response 3 | import math 4 | 5 | 6 | class ModifiedPageNumberPagination(PageNumberPagination): 7 | # http://127.0.0.1:8001/api/v1/supplement_events?page=last 8 | last_page_strings = ('last', ) 9 | page_size = 100 10 | page_size_query_param = 'page_size' 11 | max_page_size = 1000 12 | 13 | def get_paginated_response(self, data): 14 | return Response({ 15 | 'links': { 16 | 'next': self.get_next_link(), 17 | 'previous': self.get_previous_link() 18 | }, 19 | 'count': self.page.paginator.count, 20 | 'last_page': self.get_last_page_number(self.page.paginator.count), 21 | 'results': data, 22 | 'current_page': self.page.number 23 | }) 24 | 25 | def get_last_page_number(self, count): 26 | if not count: 27 | return None 28 | 29 | return math.ceil(count / self.page_size) 30 | -------------------------------------------------------------------------------- /config/settings/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /config/settings/constants.py: -------------------------------------------------------------------------------- 1 | PRODUCTION = 'PRODUCTION' 2 | STAGING = 'STAGING' 3 | LOCAL = 'LOCAL' 4 | TESTING = 'TESTING' 5 | -------------------------------------------------------------------------------- /config/settings/staging.py: -------------------------------------------------------------------------------- 1 | # TODO - setup staging environment 2 | print ('Using {} configurations'.format(__name__)) 3 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for betterself project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | """ 15 | import os 16 | 17 | from whitenoise.django import DjangoWhiteNoise 18 | from django.core.wsgi import get_wsgi_application 19 | 20 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 21 | # if running multiple sites in the same mod_wsgi process. To fix this, use 22 | # mod_wsgi daemon mode with each site in its own daemon process, or use 23 | # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production" 24 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production') 25 | 26 | # This application object is used by any WSGI server configured to use this 27 | # file. This includes Django's development server, if the WSGI_APPLICATION 28 | # setting points here. 29 | application = get_wsgi_application() 30 | application = DjangoWhiteNoise(application) 31 | 32 | if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production': 33 | from raven.contrib.django.raven_compat.middleware.wsgi import Sentry 34 | 35 | application = Sentry(application) 36 | # see if this helps performance behind CloudFront 37 | application = DjangoWhiteNoise(application) 38 | -------------------------------------------------------------------------------- /constants.py: -------------------------------------------------------------------------------- 1 | # Dataframe Constants 2 | SLEEP_MINUTES_COLUMN = 'Sleep Minutes' 3 | 4 | LOOKBACK_PARAM_NAME = 'lookback' 5 | 6 | # the sleep cutoff time determines if sleep duration should be calculated for the previous day or next 7 | SLEEP_CUTOFF_TIME = 11 8 | VERY_PRODUCTIVE_TIME_LABEL = 'Very Productive Minutes' 9 | PRODUCTIVE_TIME_LABEL = 'Productive Minutes' 10 | NEUTRAL_TIME_LABEL = 'Neutral Minutes' 11 | DISTRACTING_TIME_LABEL = 'Distracting Minutes' 12 | VERY_DISTRACTING_TIME_LABEL = 'Very Distracting Minutes' 13 | VERY_PRODUCTIVE_MINUTES_VARIABLE = 'very_productive_time_minutes' 14 | PRODUCTIVE_MINUTES_VARIABLE = 'productive_time_minutes' 15 | NEUTRAL_MINUTES_VARIABLE = 'neutral_time_minutes' 16 | DISTRACTING_MINUTES_VARIABLE = 'distracting_time_minutes' 17 | VERY_DISTRACTING_MINUTES_VARIABLE = 'very_distracting_time_minutes' 18 | PRODUCTIVITY_DRIVERS_LABELS = [ 19 | VERY_PRODUCTIVE_TIME_LABEL, 20 | PRODUCTIVE_TIME_LABEL, 21 | NEUTRAL_TIME_LABEL, 22 | DISTRACTING_TIME_LABEL, 23 | VERY_DISTRACTING_TIME_LABEL, 24 | ] 25 | PRODUCTIVITY_DRIVERS_KEYS = [ 26 | VERY_PRODUCTIVE_MINUTES_VARIABLE, 27 | PRODUCTIVE_MINUTES_VARIABLE, 28 | NEUTRAL_MINUTES_VARIABLE, 29 | DISTRACTING_MINUTES_VARIABLE, 30 | VERY_DISTRACTING_MINUTES_VARIABLE, 31 | ] 32 | PRODUCTIVITY_KEYS_TO_LABELS = dict(zip(PRODUCTIVITY_DRIVERS_LABELS, PRODUCTIVITY_DRIVERS_KEYS)) 33 | -------------------------------------------------------------------------------- /events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/events/__init__.py -------------------------------------------------------------------------------- /events/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.conf import settings 3 | 4 | from betterself.users.models import UserPhoneNumberDetails 5 | from events.models import SupplementLog, DailyProductivityLog, SupplementReminder, UserMoodLog 6 | 7 | 8 | @admin.register(SupplementLog) 9 | class SupplementEventAdmin(admin.ModelAdmin): 10 | list_display = ('user', 'supplement', 'quantity', 'time', 'source') 11 | search_fields = ('supplement__name',) 12 | ordering = ['-time'] 13 | 14 | class Meta: 15 | model = SupplementLog 16 | 17 | def get_queryset(self, request): 18 | return super().get_queryset(request).exclude(user__username__contains=settings.EXCLUDE_ADMINS_USERNAMES) 19 | 20 | 21 | @admin.register(DailyProductivityLog) 22 | class DailyProductivityLogAdmin(admin.ModelAdmin): 23 | list_display = [ 24 | 'date', 25 | 'very_productive_time_minutes', 26 | 'productive_time_minutes', 27 | 'neutral_time_minutes', 28 | 'distracting_time_minutes', 29 | 'very_distracting_time_minutes', 30 | 'user' 31 | ] 32 | 33 | class Meta: 34 | model = DailyProductivityLog 35 | 36 | 37 | admin.site.register(UserPhoneNumberDetails) 38 | admin.site.register(SupplementReminder) 39 | admin.site.register(UserMoodLog) 40 | -------------------------------------------------------------------------------- /events/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class EventsConfig(AppConfig): 5 | name = 'events' 6 | -------------------------------------------------------------------------------- /events/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/events/fixtures/__init__.py -------------------------------------------------------------------------------- /events/fixtures/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from django.utils import timezone 3 | from factory.fuzzy import FuzzyInteger 4 | 5 | from betterself.users.fixtures.factories import UserFactory 6 | from events.models import SupplementLog, DailyProductivityLog, UserActivity, UserActivityLog, UserMoodLog 7 | 8 | 9 | class SupplementEventFactory(factory.DjangoModelFactory): 10 | source = 'api' 11 | quantity = 1 12 | time = timezone.now() 13 | user = factory.SubFactory(UserFactory) 14 | 15 | class Meta: 16 | model = SupplementLog 17 | 18 | 19 | class DailyProductivityLogFactory(factory.DjangoModelFactory): 20 | source = 'api' 21 | 22 | very_productive_time_minutes = FuzzyInteger(10, 30) 23 | productive_time_minutes = FuzzyInteger(10, 30) 24 | neutral_time_minutes = FuzzyInteger(10, 30) 25 | distracting_time_minutes = FuzzyInteger(10, 30) 26 | very_distracting_time_minutes = FuzzyInteger(10, 30) 27 | 28 | class Meta: 29 | model = DailyProductivityLog 30 | 31 | 32 | class UserActivityFactory(factory.DjangoModelFactory): 33 | name = factory.Faker('name') 34 | 35 | class Meta: 36 | model = UserActivity 37 | 38 | 39 | class UserActivityEventFactory(factory.DjangoModelFactory): 40 | time = timezone.now() 41 | # way to get the passed user to this factory and pass it down to UserActivityFactory 42 | user_activity = factory.SubFactory(UserActivityFactory, user=factory.SelfAttribute('..user')) 43 | duration_minutes = 0 44 | 45 | class Meta: 46 | model = UserActivityLog 47 | 48 | 49 | class UserMoodLogFactory(factory.DjangoModelFactory): 50 | time = timezone.now() 51 | value = FuzzyInteger(1, 10) 52 | 53 | class Meta: 54 | model = UserMoodLog 55 | -------------------------------------------------------------------------------- /events/migrations/0002_add_notes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.7 on 2017-12-20 01:10 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('events', '0001_squashed_0028_auto_20171113_0332'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='dailyproductivitylog', 17 | name='notes', 18 | field=models.TextField(default=''), 19 | ), 20 | migrations.AddField( 21 | model_name='sleeplog', 22 | name='notes', 23 | field=models.TextField(default=''), 24 | ), 25 | migrations.AddField( 26 | model_name='supplementlog', 27 | name='notes', 28 | field=models.TextField(default=''), 29 | ), 30 | migrations.AddField( 31 | model_name='useractivity', 32 | name='notes', 33 | field=models.TextField(default=''), 34 | ), 35 | migrations.AddField( 36 | model_name='useractivitylog', 37 | name='notes', 38 | field=models.TextField(default=''), 39 | ), 40 | migrations.AlterField( 41 | model_name='usermoodlog', 42 | name='notes', 43 | field=models.TextField(default=''), 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /events/migrations/0003_auto_20171221_0336.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.7 on 2017-12-21 03:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('events', '0002_add_notes'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='supplementlog', 17 | options={'ordering': ['user', '-time'], 'verbose_name': 'Supplement Log', 'verbose_name_plural': 'Supplement Logs'}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /events/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/events/migrations/__init__.py -------------------------------------------------------------------------------- /events/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/events/utils/__init__.py -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | import environ 6 | 7 | env = environ.Env() 8 | 9 | if __name__ == '__main__': 10 | if 'test' in sys.argv: 11 | os.environ['DJANGO_SETTINGS_MODULE'] = 'config.settings.settings_testing' 12 | default = 'config.settings.settings_testing' 13 | else: 14 | default = 'config.settings.local' 15 | 16 | # for production / staging environments, the correct settings 17 | # module is already set as an environment variable 18 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', default) 19 | 20 | from django.core.management import execute_from_command_line 21 | 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements/production.txt 2 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | amqp==2.2.2 2 | babel==2.5.3 3 | billiard==3.5.0.3 4 | celery==4.1.0 5 | contextlib2==0.5.5 6 | coverage==4.4.2 7 | cython==0.27.3 8 | django==1.11.7 9 | django-admin-honeypot==1.0.0 10 | django-allauth==0.34.0 11 | django-anymail==1.2 12 | django-autoslug==1.9.3 13 | django-braces==1.12.0 14 | django-celery-beat==1.1.0 15 | django-crispy-forms==1.7.0 16 | django-environ==0.4.4 17 | django-extensions==1.9.8 18 | django-filter==1.1.0 19 | django-floppyforms==1.7.0 20 | django-model-utils==3.0.0 21 | django-phonenumber-field==1.3.0 22 | django-redis==4.8.0 23 | django-rest-auth==0.9.3 24 | django-secure==1.0.1 25 | django-webpack-loader==0.6.0 26 | djangorestframework==3.7.7 27 | factory-boy==2.9.2 28 | Faker==0.8.8 29 | fitbit==0.3.0 30 | honcho==1.0.1 31 | httplib2==0.10.3 32 | ipython==6.2.1 33 | kombu==4.1.0 34 | MagicMock==0.3 35 | Markdown==2.6.10 36 | numpy==1.13.3 37 | oauthlib==2.0.6 38 | openpyxl==2.4.9 39 | pandas==0.22.0 40 | patch==1.16 41 | patsy==0.4.1 42 | phonenumberslite==8.8.8 43 | Pillow==4.3.0 44 | psycopg2==2.7.3.2 45 | PyJWT==1.5.3 46 | pysocks==1.6.8 47 | pytz==2017.3 48 | raven==6.4.0 49 | readline==6.2.4.1 50 | redis==2.10.6 51 | requests==2.18.4 52 | requests-oauthlib==0.8.0 53 | scipy==1.0.0 54 | setuptools==38.2.5 55 | simplejson==3.13.2 56 | sqlparse==0.2.4 57 | text-unidecode==1.2 58 | twilio==6.10.0 59 | unicode-slugify==0.1.3 60 | uuid==1.30 61 | vine==1.1.4 62 | wheel==0.30.0 63 | whitenoise==3.3.1 64 | xlrd==1.1.0 65 | XlsxWriter==1.0.2 66 | -------------------------------------------------------------------------------- /requirements/local.txt: -------------------------------------------------------------------------------- 1 | # overlaps with test 2 | django-debug-toolbar==1.9.1 3 | django-slowtests==0.5.1 4 | 5 | # local only specific 6 | ipdb==0.10.3 7 | pre-commit==1.4.1 8 | tblib==1.3.2 9 | -------------------------------------------------------------------------------- /requirements/production.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | boto==2.48.0 3 | django-storages-redux==1.3.3 4 | gevent==1.2.2 5 | gunicorn==19.7.1 6 | opbeat==3.6.1 7 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | -r base.txt 2 | django-coverage-plugin==1.5.0 3 | django-test-plus==1.0.21 4 | flake8==3.5.0 5 | pytest-django==3.1.2 6 | pytest-sugar==0.9.0 7 | tblib==1.3.2 8 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.6.2 2 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/scripts/__init__.py -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,*production.py,*settings_testing.py 4 | 5 | [pep8] 6 | max-line-length = 120 7 | exclude=.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules 8 | -------------------------------------------------------------------------------- /supplements/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/supplements/__init__.py -------------------------------------------------------------------------------- /supplements/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SupplementsConfig(AppConfig): 5 | name = 'supplements' 6 | -------------------------------------------------------------------------------- /supplements/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/supplements/fixtures/__init__.py -------------------------------------------------------------------------------- /supplements/migrations/0002_supplement_notes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.7 on 2017-12-20 01:10 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('supplements', '0001_squashed_0007_auto_20171023_0402'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='supplement', 17 | name='notes', 18 | field=models.TextField(default=''), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /supplements/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/supplements/migrations/__init__.py -------------------------------------------------------------------------------- /supplements/tests.py: -------------------------------------------------------------------------------- 1 | from django.db import IntegrityError 2 | from django.test import TestCase 3 | 4 | from supplements.fixtures.factories import DEFAULT_INGREDIENT_NAME_1, DEFAULT_INGREDIENT_HL_MINUTE_1 5 | from supplements.fixtures.mixins import SupplementModelsFixturesGenerator 6 | from supplements.models import Ingredient, Supplement 7 | 8 | 9 | class SupplementFixtureCreationTests(TestCase, SupplementModelsFixturesGenerator): 10 | @classmethod 11 | def setUpTestData(cls): 12 | SupplementModelsFixturesGenerator.create_fixtures() 13 | 14 | def test_ingredients_creation(self): 15 | ingredient = Ingredient.objects.all().first() 16 | self.assertTrue(ingredient) 17 | 18 | def test_default_ingredient(self): 19 | default_ingredient = Ingredient.objects.all().first() 20 | 21 | self.assertEqual(default_ingredient.name, DEFAULT_INGREDIENT_NAME_1) 22 | self.assertEqual(default_ingredient.half_life_minutes, DEFAULT_INGREDIENT_HL_MINUTE_1) 23 | 24 | def test_default_ingredient_saved(self): 25 | # realized this happened when factory_boy wasn't saving 26 | # because it doesn't natively call django create 27 | saved_ingredients = Ingredient.objects.all().count() 28 | self.assertTrue(saved_ingredients > 0) 29 | 30 | 31 | class SupplementEventsTests(TestCase): 32 | def test_cannot_create_obj_without_user(self): 33 | with self.assertRaises(IntegrityError): 34 | Supplement.objects.create(name='pop') 35 | -------------------------------------------------------------------------------- /utility/install_python_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORK_DIR="$(dirname "$0")" 4 | PROJECT_DIR="$(dirname "$WORK_DIR")" 5 | 6 | pip --version >/dev/null 2>&1 || { 7 | echo >&2 -e "\npip is required but it's not installed." 8 | echo >&2 -e "You can install it by running the following command:\n" 9 | echo >&2 "wget https://bootstrap.pypa.io/get-pip.py --output-document=get-pip.py; chmod +x get-pip.py; sudo -H python3 get-pip.py" 10 | 11 | echo >&2 -e "\n" 12 | echo >&2 -e "\nFor more information, see pip documentation: https://pip.pypa.io/en/latest/" 13 | exit 1; 14 | } 15 | 16 | virtualenv --version >/dev/null 2>&1 || { 17 | echo >&2 -e "\nvirtualenv is required but it's not installed." 18 | echo >&2 -e "You can install it by running the following command:\n" 19 | echo >&2 "sudo -H pip3 install virtualenv" 20 | 21 | echo >&2 -e "\n" 22 | echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/" 23 | exit 1; 24 | } 25 | 26 | if [ -z "$VIRTUAL_ENV" ]; then 27 | echo >&2 -e "\nYou need activate a virtualenv first" 28 | echo >&2 -e 'If you do not have a virtualenv created, run the following command to create and automatically activate a new virtualenv named "venv" on current folder:\n' 29 | echo >&2 -e "virtualenv venv --python=\`which python3\`" 30 | 31 | echo >&2 -e "\nTo leave/disable the currently active virtualenv, run the following command:\n" 32 | echo >&2 "deactivate" 33 | echo >&2 -e "\nTo activate the virtualenv again, run the following command:\n" 34 | echo >&2 "source venv/bin/activate" 35 | echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/" 36 | echo >&2 -e "\n" 37 | exit 1; 38 | else 39 | 40 | pip install -r $PROJECT_DIR/requirements/local.txt 41 | pip install -r $PROJECT_DIR/requirements/test.txt 42 | pip install -r $PROJECT_DIR/requirements.txt 43 | fi 44 | 45 | -------------------------------------------------------------------------------- /utility/requirements.apt: -------------------------------------------------------------------------------- 1 | ##basic build dependencies of various Django apps for Ubuntu 14.04 2 | #build-essential metapackage install: make, gcc, g++, 3 | build-essential 4 | #required to translate 5 | gettext 6 | python-dev 7 | 8 | ##shared dependencies of: 9 | ##Pillow, pylibmc 10 | zlib1g-dev 11 | 12 | ##Postgresql and psycopg2 dependencies 13 | libpq-dev 14 | 15 | ##Pillow dependencies 16 | libtiff4-dev 17 | libjpeg8-dev 18 | libfreetype6-dev 19 | liblcms1-dev 20 | libwebp-dev 21 | 22 | ##django-extensions 23 | graphviz-dev 24 | -------------------------------------------------------------------------------- /utility/requirements.apt.xenial: -------------------------------------------------------------------------------- 1 | ##basic build dependencies of various Django apps for Ubuntu 14.04 2 | #build-essential metapackage install: make, gcc, g++, 3 | build-essential 4 | #required to translate 5 | gettext 6 | python-dev 7 | 8 | ##shared dependencies of: 9 | ##Pillow, pylibmc 10 | zlib1g-dev 11 | 12 | ##Postgresql and psycopg2 dependencies 13 | libpq-dev 14 | 15 | ##Pillow dependencies 16 | libtiff5-dev 17 | libjpeg8-dev 18 | libfreetype6-dev 19 | liblcms2-dev 20 | libwebp-dev 21 | 22 | 23 | ##django-extensions 24 | graphviz-dev 25 | 26 | ##hitch 27 | python-setuptools 28 | python3-dev 29 | python-virtualenv 30 | python-pip 31 | firefox 32 | automake 33 | libtool 34 | libreadline6 35 | libreadline6-dev 36 | libreadline-dev 37 | libsqlite3-dev 38 | libxml2 39 | libxml2-dev 40 | libssl-dev 41 | libbz2-dev 42 | wget 43 | curl 44 | llvm 45 | -------------------------------------------------------------------------------- /vendors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/vendors/__init__.py -------------------------------------------------------------------------------- /vendors/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /vendors/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class VendorsConfig(AppConfig): 5 | name = 'vendors' 6 | -------------------------------------------------------------------------------- /vendors/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/vendors/fixtures/__init__.py -------------------------------------------------------------------------------- /vendors/fixtures/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | 3 | from vendors.models import Vendor 4 | 5 | DEFAULT_VENDOR_NAME = 'Advil' 6 | 7 | 8 | class VendorFactory(factory.DjangoModelFactory): 9 | class Meta: 10 | model = Vendor 11 | 12 | name = DEFAULT_VENDOR_NAME 13 | email = factory.LazyAttribute(lambda a: 'scientist@{0}.com'.format(a.name)) 14 | url = factory.LazyAttribute(lambda a: '{0}.com'.format(a.name)) 15 | -------------------------------------------------------------------------------- /vendors/fixtures/mixins.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | 3 | from vendors.fixtures.factories import VendorFactory, DEFAULT_VENDOR_NAME 4 | 5 | 6 | class VendorModelsFixturesGenerator(object): 7 | @classmethod 8 | def create_fixtures(cls): 9 | User = get_user_model() 10 | default_user, _ = User.objects.get_or_create(username='default') 11 | 12 | VendorFactory(user=default_user, name=DEFAULT_VENDOR_NAME) 13 | VendorFactory(user=default_user, name='MadScienceLabs') 14 | -------------------------------------------------------------------------------- /vendors/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2017-01-01 23:51 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import uuid 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Vendor', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('created', models.DateField(auto_now_add=True)), 25 | ('modified', models.DateTimeField(auto_now=True)), 26 | ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), 27 | ('name', models.CharField(max_length=200)), 28 | ('email', models.EmailField(blank=True, max_length=254, null=True)), 29 | ('url', models.URLField(blank=True, null=True)), 30 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 31 | ], 32 | ), 33 | migrations.AlterUniqueTogether( 34 | name='vendor', 35 | unique_together=set([('name', 'user')]), 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /vendors/migrations/0002_auto_20170521_2251.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2017-05-21 22:51 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('vendors', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='vendor', 17 | name='created', 18 | field=models.DateTimeField(auto_now_add=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /vendors/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffshek/betterself/51468253fc31373eb96e0e82189b9413f3d76ff5/vendors/migrations/__init__.py -------------------------------------------------------------------------------- /vendors/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from betterself.base_models import BaseModelWithUserGeneratedContent 4 | 5 | 6 | class Vendor(BaseModelWithUserGeneratedContent): 7 | RESOURCE_NAME = 'vendors' 8 | 9 | name = models.CharField(max_length=200) 10 | email = models.EmailField(max_length=254, null=True, blank=True) 11 | url = models.URLField(null=True, blank=True) 12 | 13 | class Meta: 14 | unique_together = ('name', 'user') 15 | -------------------------------------------------------------------------------- /vendors/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | --------------------------------------------------------------------------------