├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------