├── .buildpacks ├── .checkignore ├── .coveragerc ├── .eslintrc ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .jscsrc ├── .pre-commit-config.yaml ├── .pydocstyle ├── .pylintrc ├── .scss-lint.yml ├── CODE_OF_CONDUCT.md ├── EXTERNALS.md ├── LICENSE.txt ├── Procfile ├── README.md ├── bin ├── BrowserStackLocal-linux ├── BrowserStackLocal-osx └── post_compile ├── codecov.yml ├── common ├── __init__.py ├── activities.py ├── adapters.py ├── app_configs.py ├── fields.py ├── mixins.py ├── processors.py ├── tasks.py ├── testing.py ├── utils.py └── views.py ├── data_import ├── __init__.py ├── admin.py ├── apps.py ├── filters.py ├── forms.py ├── migrations │ ├── 0001_squashed_0020_auto_20160729_1632.py │ ├── 0002_auto_20160729_2012.py │ ├── 0003_datafile_archived.py │ ├── 0004_auto_20160822_1618.py │ ├── 0005_remove_datafile_is_latest.py │ ├── 0006_remove_datafile_archived.py │ ├── 0007_auto_20180813_2147.py │ ├── 0008_datafilekey.py │ ├── 0009_auto_20190103_1838.py │ ├── 0010_custom_20190107_1850.py │ ├── 0011_auto_20190107_1907.py │ ├── 0012_auto_20190312_2031.py │ ├── 0013_auto_20190326_1840.py │ ├── 0014_auto_20190327_0035.py │ ├── 0015_auto_20190327_1956.py │ ├── 0016_remove_awsdatafileaccesslog_data_file.py │ ├── 0017_auto_20190329_1638.py │ ├── 0018_auto_20190402_1947.py │ ├── 0019_datatype.py │ ├── 0020_auto_20190430_1756.py │ ├── 0021_auto_20190521_2031.py │ ├── 0022_auto_20191105_1924.py │ ├── 0023_auto_20191106_2330.py │ ├── 0024_auto_20201222_1757.py │ ├── 0025_auto_20201222_1934.py │ └── __init__.py ├── models.py ├── permissions.py ├── serializers.py ├── templates │ └── data_import │ │ ├── datatypes-create.html │ │ ├── datatypes-detail.html │ │ ├── datatypes-list.html │ │ └── datatypes-update.html ├── templatetags │ ├── __init__.py │ └── data_import.py ├── urls.py ├── utils.py └── views.py ├── dev-requirements.in ├── dev-requirements.txt ├── discourse ├── __init__.py ├── urls.py └── views.py ├── docs └── SETUP.md ├── env.example ├── gulp └── bundle-logger.js ├── gulpfile.js ├── manage.py ├── open_humans ├── __init__.py ├── account_views.py ├── admin.py ├── api_urls.py ├── api_views.py ├── apps.py ├── celery.py ├── filters.py ├── fixtures │ └── test-data.json ├── formatters.py ├── forms.py ├── hooksets.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── bulk_email.py │ │ ├── expunge_addresses.py │ │ ├── expunge_project_data_files.py │ │ ├── get_refresh_tokens.py │ │ ├── remove_expired_keys.py │ │ ├── rotate_file_access_logs.py │ │ ├── setup_api.py │ │ ├── stats.py │ │ ├── stats_new.py │ │ ├── suspend_users.py │ │ ├── switch_project_type.py │ │ ├── user_connections_json.py │ │ ├── user_growth.py │ │ └── vacuum_log_bucket.py ├── member_views.py ├── middleware.py ├── migrations │ ├── 0001_initial.py │ ├── 0001_squashed_0016_auto_20150410_2301.py │ ├── 0002_auto_20141111_1400.py │ ├── 0002_member_seen_pgp_interstitial.py │ ├── 0003_auto_20151223_1827.py │ ├── 0003_rename_profile_to_member.py │ ├── 0004_auto_20150106_1828.py │ ├── 0004_member_badges.py │ ├── 0005_rename_content_type.py │ ├── 0005_userevent.py │ ├── 0006_auto_20150123_2121.py │ ├── 0006_userevent_event_type.py │ ├── 0007_blogpost.py │ ├── 0007_member_name.py │ ├── 0008_member_member_id.py │ ├── 0008_rm_ghost_illuminauyg_table.py │ ├── 0009_grantproject.py │ ├── 0009_random_member_id.py │ ├── 0010_auto_20150311_1922.py │ ├── 0010_auto_20180611_2029.py │ ├── 0011_auto_20150326_0650.py │ ├── 0011_auto_20180709_2220.py │ ├── 0012_add_username_lower_index.py │ ├── 0012_auto_20180725_2020.py │ ├── 0013_auto_20150403_2323.py │ ├── 0013_remove_member_badges.py │ ├── 0014_member_password_reset_redirect.py │ ├── 0014_rename_openhumansuser.py │ ├── 0015_auto_20150410_0042.py │ ├── 0015_featureflag.py │ ├── 0016_auto_20150410_2301.py │ └── __init__.py ├── mixins.py ├── models.py ├── serializers.py ├── settings.py ├── signals.py ├── storage.py ├── templates │ ├── 404.html │ ├── 500.html │ ├── CSRF-error.html │ ├── account │ │ ├── delete.html │ │ ├── email │ │ │ ├── email_confirmation_message.txt │ │ │ ├── email_confirmation_subject.txt │ │ │ ├── invite_user.txt │ │ │ ├── invite_user_subject.txt │ │ │ ├── password_change.txt │ │ │ ├── password_change_subject.txt │ │ │ ├── password_reset.txt │ │ │ └── password_reset_subject.txt │ │ ├── email_confirm.html │ │ ├── login-form.html │ │ ├── login-modal-bs4.html │ │ ├── login-modal.html │ │ ├── login-oauth2.html │ │ ├── login-social-bs4.html │ │ ├── login-social.html │ │ ├── login.html │ │ ├── logout.html │ │ ├── password_change.html │ │ ├── password_reset.html │ │ ├── password_reset_from_key.html │ │ ├── password_reset_sent.html │ │ ├── password_reset_token_fail.html │ │ ├── password_set.html │ │ ├── signup-email.html │ │ ├── signup-form.html │ │ ├── signup-modal-bs4.html │ │ ├── signup-modal.html │ │ ├── signup.html │ │ └── signup_closed.html │ ├── base-bs4.html │ ├── base.html │ ├── email │ │ ├── activity-message.html │ │ ├── activity-message.txt │ │ ├── user-message.html │ │ ├── user-message.txt │ │ ├── welcome.html │ │ └── welcome.txt │ ├── member │ │ ├── activity-management.html │ │ ├── activity-message.html │ │ ├── activity.html │ │ ├── member-detail-content.html │ │ ├── member-detail.html │ │ ├── member-email.html │ │ ├── member-list.html │ │ ├── my-member-change-email.html │ │ ├── my-member-change-name.html │ │ ├── my-member-change-username.html │ │ ├── my-member-connections-delete.html │ │ ├── my-member-connections.html │ │ ├── my-member-dashboard.html │ │ ├── my-member-data.html │ │ ├── my-member-joined.html │ │ ├── my-member-profile-edit.html │ │ ├── my-member-settings.html │ │ ├── my-member-source-data-files-delete.html │ │ └── my-member-study-grants-delete.html │ ├── pages │ │ ├── 201805-notice-of-terms-update.html │ │ ├── 201903-notice-of-terms-update.html │ │ ├── about.html │ │ ├── add-data.html │ │ ├── community_guidelines.html │ │ ├── contact_us.html │ │ ├── copyright-policy.html │ │ ├── create.html │ │ ├── data-processing-activities.html │ │ ├── data-use.html │ │ ├── explore-share.html │ │ ├── gdpr.html │ │ ├── grant_projects.html │ │ ├── home.html │ │ ├── news.html │ │ ├── project-grants.html │ │ ├── public-data-api.html │ │ ├── self-research.html │ │ └── terms.html │ ├── panel.html │ ├── partials │ │ ├── activity-feed-item.html │ │ ├── activity-info-short-bs4.html │ │ ├── activity-info-table.html │ │ ├── activity-management-join-add-button.html │ │ ├── activity-management-project-permissions.html │ │ ├── activity-management-sharing-opportunities.html │ │ ├── activity-management-your-data.html │ │ ├── activity-panel-data.html │ │ ├── activity-panel-info.html │ │ ├── activity-panel-needs.html │ │ ├── activity-panel-news.html │ │ ├── activity-panel.html │ │ ├── activity-permissions-bs4.html │ │ ├── connection-activity.html │ │ ├── connection-study.html │ │ ├── my-member-data-source.html │ │ ├── project-grants-blurb.html │ │ ├── project-in-development.html │ │ ├── public-sharing-button.html │ │ ├── source-in-development.html │ │ ├── upload-activity.html │ │ └── visible-public-sharing.html │ ├── scopes │ │ └── scope-container.html │ └── socialaccount │ │ ├── authentication_error.html │ │ ├── connections.html │ │ ├── login_cancelled.html │ │ ├── messages │ │ ├── account_connected.txt │ │ ├── account_connected_other.txt │ │ └── account_disconnected.txt │ │ ├── signup.html │ │ └── snippets │ │ ├── login_extra.html │ │ └── provider_list.html ├── templatetags │ ├── __init__.py │ └── utilities.py ├── testing.py ├── tests.py ├── urls.py ├── views.py └── wsgi.py ├── package.json ├── private_sharing ├── __init__.py ├── admin.py ├── api_authentication.py ├── api_filter_backends.py ├── api_permissions.py ├── api_urls.py ├── api_views.py ├── fixtures │ └── test-data.json ├── forms.py ├── migrations │ ├── 0001_squashed_0034_auto_20160727_2138.py │ ├── 0002_add_project_data_file.py │ ├── 0003_auto_20160909_0427.py │ ├── 0004_projectdatafile_completed.py │ ├── 0005_auto_20160926_1717.py │ ├── 0006_auto_20161102_1932.py │ ├── 0007_auto_20171220_2038.py │ ├── 0008_featuredproject.py │ ├── 0009_auto_20180317_2209.py │ ├── 0010_datarequestprojectmember_visible.py │ ├── 0011_auto_20180912_2206.py │ ├── 0012_datarequestproject_approval_history.py │ ├── 0013_auto_20190121_2142.py │ ├── 0014_datarequestproject_no_public_data.py │ ├── 0015_auto_20190124_1849.py │ ├── 0016_auto_20190128_2111.py │ ├── 0017_auto_20190212_0511.py │ ├── 0018_oauth2datarequestproject_terms_url.py │ ├── 0019_auto_20190214_1915.py │ ├── 0020_auto_20190222_0036.py │ ├── 0021_auto_20190412_1908.py │ ├── 0022_auto_20190507_1843.py │ ├── 0023_auto_20190528_1826.py │ ├── 0024_auto_20190716_0504.py │ ├── 0025_datarequestproject_any_datatypes.py │ ├── 0026_auto_20191202_2105.py │ ├── 0027_oauth2datarequestproject_webhook_secret.py │ ├── 0028_datarequestproject_jogl_page.py │ └── __init__.py ├── models.py ├── serializers.py ├── static │ └── images │ │ └── badge.png ├── templates │ ├── direct-sharing │ │ ├── approval.html │ │ ├── layout.html │ │ ├── navigation-items.html │ │ ├── oauth2-data-access.html │ │ ├── oauth2-data-upload.html │ │ ├── oauth2-features.html │ │ ├── oauth2-member-removal.html │ │ ├── oauth2-messages.html │ │ ├── oauth2-setup.html │ │ ├── on-site-data-access.html │ │ ├── on-site-data-upload.html │ │ ├── on-site-features.html │ │ ├── on-site-member-removal.html │ │ ├── on-site-messages.html │ │ ├── on-site-setup.html │ │ ├── overview.html │ │ └── partials │ │ │ ├── about-master-token.html │ │ │ ├── about-projects.html │ │ │ ├── data-access.html │ │ │ ├── data-upload.html │ │ │ ├── features.html │ │ │ ├── member-removal.html │ │ │ ├── messages.html │ │ │ ├── oauth2-appropriate-if.html │ │ │ ├── on-site-appropriate-if.html │ │ │ ├── project-info.html │ │ │ └── setup.html │ ├── email │ │ ├── notify-withdrawal.html │ │ ├── notify-withdrawal.txt │ │ └── project-message.txt │ └── private_sharing │ │ ├── authorize-base.html │ │ ├── authorize-inactive.html │ │ ├── authorize-oauth2.html │ │ ├── authorize-on-site.html │ │ ├── create-project.html │ │ ├── in-development.html │ │ ├── join-on-site.html │ │ ├── leave-project.html │ │ ├── manage.html │ │ ├── message-project-members.html │ │ ├── project-detail.html │ │ ├── project-withdrawn-members-view.html │ │ ├── remove-project-members.html │ │ ├── select-datatypes.html │ │ └── update-project.html ├── templatetags │ ├── __init__.py │ └── private_sharing.py ├── testing.py ├── tests.py ├── urls.py ├── utilities.py └── views.py ├── public_data ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_squashed_0004_auto_20151230_0050.py │ ├── 0002_auto_20171213_1947.py │ ├── 0003_auto_20190508_2341.py │ ├── 0004_migrate_data_20190508.py │ ├── 0005_auto_20190508_2342.py │ └── __init__.py ├── models.py ├── serializers.py ├── signals.py ├── static │ ├── config │ │ └── questions.yaml │ ├── docs │ │ ├── Consent_Document_20141212_(stamped).pdf │ │ ├── Consent_Document_20160128_(stamped).pdf │ │ ├── Research_Protocol_20141212.pdf │ │ └── Research_Protocol_20160128.pdf │ └── images │ │ ├── public-data-sharing-badge.png │ │ └── public-data-sharing-badge.svg ├── templates │ └── public_data │ │ ├── consent.html │ │ ├── home.html │ │ ├── overview.html │ │ ├── quiz.html │ │ └── withdraw.html ├── tests.py ├── urls.py └── views.py ├── requirements.in ├── requirements.txt ├── requirements.txt.bck ├── runtime.txt ├── scripts ├── benchmark.sh ├── dump-production.sh ├── filter-test-data.js ├── migrate_s3.py └── user_stat_summaries.py └── static ├── css ├── _buttons.css ├── _divs.css ├── _footer.css ├── _global-variables.css ├── _navbar.css ├── _pages-home.css ├── _pages-members.css ├── _pages-public-data-quiz.css ├── _pages.css ├── _text.css ├── main.css └── oh-proj-theming.css ├── images ├── FB-F.png ├── FB-f-Logo__blue_29.png ├── Mad-Ball.jpg ├── apple-logo-white-large-3x.png ├── bastian-greshake-tzovaras.jpg ├── connect-data.png ├── connect-data.svg ├── default-badge.png ├── default-badge.svg ├── email-icon.png ├── get2014_background.jpg ├── google-logo-g.png ├── knight-logo-300.jpg ├── mdulaney.jpg ├── oh-connect-study.png ├── oh-connect-study.svg ├── oh-public-data.png ├── oh-public-data.svg ├── oh-research-help.png ├── oh-research-partner.png ├── oh-research-partner.svg ├── open-humans-logo-horizontal-80px.png ├── open_humans_favicon.png ├── open_humans_logo_only.png ├── open_humans_logo_only.svg ├── profile-placeholder.png ├── profile-placeholder.svg ├── project-1-draft.png ├── project-1-draft.svg ├── project-2-test.png ├── project-2-test.svg ├── project-3-try.png ├── project-3-try.svg ├── rwjf_logo1.png ├── share-data.png ├── share-data.svg ├── twitter-xs-logo.png └── your-data-plus-you-sketch.jpg ├── js ├── about.js ├── account-password-reset-token.js ├── account-password.js ├── activity-management.js ├── activity.js ├── add-data.js ├── community-guidelines.js ├── explore-share.js ├── home.js ├── lib │ ├── enable-csrf.js │ ├── nav-pills.js │ ├── public-sharing-toggle.js │ └── public-visible-toggle.js ├── main-bs4.js ├── main.js ├── member-detail.js ├── member-list.js ├── member-me-research-data.js ├── my-member-data-selfie.js ├── public-data-activate-3-quiz.js ├── public-data-api.js └── studies-connect.js └── scss └── oh-proj-theming.scss /.buildpacks: -------------------------------------------------------------------------------- 1 | https://github.com/heroku/heroku-buildpack-nodejs.git 2 | https://github.com/heroku/heroku-buildpack-python.git 3 | -------------------------------------------------------------------------------- /.checkignore: -------------------------------------------------------------------------------- 1 | # Ignore all migrations files 2 | */migrations/* 3 | */*/migrations/* 4 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = 3 | activities 4 | common 5 | data_import 6 | open_humans 7 | private_sharing 8 | public_data 9 | studies 10 | 11 | plugins = 12 | django_coverage_plugin 13 | 14 | [report] 15 | exclude_lines = 16 | pragma: no cover 17 | 18 | # Don't complain about missing debug-only code: 19 | def __unicode__ 20 | def __repr__ 21 | if self\.debug 22 | if settings\.DEBUG 23 | pass 24 | 25 | # Don't complain if tests don't hit defensive assertion code: 26 | raise AssertionError 27 | raise NotImplementedError 28 | 29 | # Don't complain if non-runnable code isn't run: 30 | if 0: 31 | if __name__ == .__main__.: 32 | 33 | omit = 34 | */migrations/* 35 | 36 | show_missing = True 37 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## General Checkups 4 | - [ ] Have you checked that there isn't already an existing issue that describes what you report below? 5 | - [ ] Have you checked that there isn't already an open pull requests for this issue/update/change? 6 | 7 | 10 | 11 | 12 | ## Description 13 | 14 | 15 | ## Related Issue(s) 16 | 19 | 20 | ## Example 21 | 22 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Related Issue 7 | 10 | 11 | ## Testing 12 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.pyc 3 | 4 | db.sqlite3 5 | 6 | .env* 7 | .coverage 8 | 9 | build/ 10 | media/ 11 | static-files/ 12 | static/vendor/ 13 | tmp/ 14 | package-lock.json 15 | src/ 16 | celeryconfig.py -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/ambv/black 3 | rev: stable 4 | hooks: 5 | - id: black 6 | language_version: python3.6 7 | -------------------------------------------------------------------------------- /.pydocstyle: -------------------------------------------------------------------------------- 1 | [pydocstyle] 2 | ignore = D100,D102,D104,D105,D200,D203,D205,D212,D400 3 | match-dir = ((?!migrations|scripts|tmp).)* 4 | -------------------------------------------------------------------------------- /EXTERNALS.md: -------------------------------------------------------------------------------- 1 | ## Modifications, customizations, workarounds, and decisions for external modules and code 2 | 3 | ### django-oauth-toolkit 4 | 5 | #### no support for access token in the query string 6 | 7 | I added middleware that takes an access token in the query string and applies 8 | it as an `Authorization: Bearer` header. 9 | 10 | ### django-user-accounts 11 | 12 | #### minimum password length 13 | 14 | A minimum password length is not enforced, causing us to subclass: 15 | 16 | - `SignupForm` 17 | - `ChangePasswordForm` 18 | - `PasswordResetTokenForm` 19 | 20 | There's an [open pull 21 | request](https://github.com/pinax/django-user-accounts/pull/155/files) from 22 | November, 2014 (but it only changes `SignupForm`). 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-2018 Open Humans Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn open_humans.wsgi 2 | worker: celery -A open_humans worker --concurrency 1 -l info 3 | -------------------------------------------------------------------------------- /bin/BrowserStackLocal-linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/bin/BrowserStackLocal-linux -------------------------------------------------------------------------------- /bin/BrowserStackLocal-osx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/bin/BrowserStackLocal-osx -------------------------------------------------------------------------------- /bin/post_compile: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # for old modules it's sometimes necessary to `migrate auth` before migrating 4 | # everything else 5 | ./manage.py migrate --noinput 6 | 7 | # clear the template cache on each deploy 8 | ./manage.py clear_cache 9 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: header, diff, changes, sunburst, uncovered, tree 3 | coverage: 4 | ignore: 5 | - open_humans/settings.py 6 | - common/testing.py 7 | - common/processors.py 8 | - open_humans/formatters.py 9 | - open_humans/testing.py 10 | - open_humans/__init__.py 11 | - scripts/.* 12 | - .*/.*_testing.py 13 | - .*/tests.py 14 | - .*/test_.*.py 15 | notify: 16 | slack: 17 | default: 18 | attachments: sunburst, diff 19 | url: secret:YeuAzAZBqzcn95nA0sohjQ0OJ0nEfBjzl37XnU5/CdMYTsSS9CQ7z2i78qCJMijKoCG0I6pIPG+mem8NXsuBwe/p2Ngyz0Kwuw/9R9OWljAlzo6zootBzxc6yHervMSpi9tfaHf8t6YnhUF/zldWmIIHNsjvckVdSDUDnjGpnwk= 20 | -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/common/__init__.py -------------------------------------------------------------------------------- /common/adapters.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.urls import reverse 3 | 4 | from allauth.account.adapter import DefaultAccountAdapter 5 | from allauth.socialaccount.adapter import DefaultSocialAccountAdapter 6 | 7 | 8 | class MyAccountAdapter(DefaultAccountAdapter): 9 | """ 10 | Subclass allauth's adapter to change the redirect on login behavior 11 | """ 12 | 13 | def get_login_redirect_url(self, request): 14 | """ 15 | We want to redirect based on what is set in the session rather than 16 | use the default method of passing a ?next= parameter on the URL. 17 | """ 18 | path = request.session.pop("next_url", reverse(settings.LOGIN_REDIRECT_URL)) 19 | return path 20 | 21 | 22 | class MySocialAccountAdapter(DefaultSocialAccountAdapter): 23 | """ 24 | Subclass allauth's social account adapter to change the redirect on login 25 | behavior 26 | """ 27 | 28 | def get_connect_redirect_url(self, request, socialaccount): 29 | """ 30 | We want to redirect based on what is set in the session rather than 31 | use the default method of passing a ?next= parameter on the URL. 32 | """ 33 | assert request.user.is_authenticated 34 | path = request.session.pop("next_url", reverse(settings.LOGIN_REDIRECT_URL)) 35 | return path 36 | -------------------------------------------------------------------------------- /common/app_configs.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BaseConnectionAppConfig(AppConfig): 5 | """ 6 | A base AppConfig that contains defaults for studies and activities. 7 | """ 8 | 9 | connect_verb = "connect" 10 | connect_complete = "Connected!" 11 | 12 | description = "" 13 | long_description = "" 14 | 15 | data_description = {"name": None, "description": None} 16 | 17 | href_connect = "" 18 | href_add_data = "" 19 | href_learn = "" 20 | 21 | retrieval_url = "" 22 | 23 | msg_add_data = "" 24 | 25 | leader = "" 26 | organization = "" 27 | product_website = "" 28 | 29 | # Can the user disconnect the study or activity? 30 | disconnectable = True 31 | 32 | # Should files from the study or activity be managed individually? 33 | individual_deletion = False 34 | 35 | # In-development studies and activities should present themselves as such 36 | # to the user and not start data retrieval 37 | in_development = False 38 | 39 | def ready(self): 40 | """ 41 | Try importing 'signals' relative to the subclassing module. This allows 42 | our signals to get hooked up when Django starts up. 43 | """ 44 | super(BaseConnectionAppConfig, self).ready() 45 | 46 | try: 47 | __import__("{}.signals".format(self.module.__name__)) 48 | except ImportError: 49 | # print 'failed to import signals from {}'.format( 50 | # self.module.__name__) 51 | 52 | pass 53 | -------------------------------------------------------------------------------- /data_import/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "data_import.apps.DataImportConfig" 2 | -------------------------------------------------------------------------------- /data_import/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from . import models 4 | 5 | admin.site.register(models.DataFile) 6 | admin.site.register(models.NewDataFileAccessLog) 7 | -------------------------------------------------------------------------------- /data_import/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DataImportConfig(AppConfig): 5 | """ 6 | Configure the data_import application. 7 | """ 8 | 9 | name = "data_import" 10 | verbose_name = "Data Import" 11 | -------------------------------------------------------------------------------- /data_import/migrations/0002_auto_20160729_2012.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.8 on 2016-07-29 20:12 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('data_import', '0001_squashed_0020_auto_20160729_1632'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='datafile', 16 | name='task', 17 | ), 18 | migrations.RemoveField( 19 | model_name='dataretrievaltask', 20 | name='user', 21 | ), 22 | migrations.DeleteModel( 23 | name='DataRetrievalTask', 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /data_import/migrations/0003_datafile_archived.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.8 on 2016-08-22 16:16 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('data_import', '0002_auto_20160729_2012'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='datafile', 16 | name='archived', 17 | field=models.DateTimeField(blank=True, null=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /data_import/migrations/0004_auto_20160822_1618.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.8 on 2016-08-22 16:18 3 | 4 | from datetime import datetime 5 | 6 | from django.db import migrations 7 | 8 | 9 | def set_archive_field(apps, *args): 10 | DataFile = apps.get_model('data_import', 'DataFile') 11 | 12 | now = datetime.now() 13 | 14 | DataFile.objects.filter(is_latest=False).update(archived=now) 15 | 16 | 17 | class Migration(migrations.Migration): 18 | 19 | dependencies = [ 20 | ('data_import', '0003_datafile_archived'), 21 | ] 22 | 23 | operations = [ 24 | migrations.RunPython(set_archive_field), 25 | ] 26 | -------------------------------------------------------------------------------- /data_import/migrations/0005_remove_datafile_is_latest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.8 on 2016-08-22 16:24 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('data_import', '0004_auto_20160822_1618'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='datafile', 16 | name='is_latest', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /data_import/migrations/0006_remove_datafile_archived.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2018-06-11 20:29 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('data_import', '0005_remove_datafile_is_latest'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='datafile', 16 | name='archived', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /data_import/migrations/0007_auto_20180813_2147.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1 on 2018-08-13 21:47 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('data_import', '0006_remove_datafile_archived'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='datafile', 16 | name='metadata', 17 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /data_import/migrations/0008_datafilekey.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.4 on 2018-12-28 23:02 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import uuid 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('data_import', '0007_auto_20180813_2147'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='DataFileKey', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('created', models.DateTimeField(auto_now=True)), 20 | ('key', models.CharField(default=uuid.uuid4, max_length=36, unique=True)), 21 | ('datafile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='data_import.DataFile')), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /data_import/migrations/0009_auto_20190103_1838.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.4 on 2019-01-03 18:38 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('data_import', '0008_datafilekey'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='datafilekey', 16 | name='access_token', 17 | field=models.CharField(max_length=64, null=True), 18 | ), 19 | migrations.AddField( 20 | model_name='datafilekey', 21 | name='ip_address', 22 | field=models.GenericIPAddressField(null=True), 23 | ), 24 | migrations.AddField( 25 | model_name='datafilekey', 26 | name='project_id', 27 | field=models.IntegerField(null=True), 28 | ), 29 | migrations.AddField( 30 | model_name='newdatafileaccesslog', 31 | name='data_file_key', 32 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /data_import/migrations/0010_custom_20190107_1850.py: -------------------------------------------------------------------------------- 1 | # Custom migration to make datafile not foreign key 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('data_import', '0009_auto_20190103_1838'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='datafilekey', 15 | name='datafile', 16 | field=models.IntegerField(db_column='datafile_id') 17 | ), 18 | migrations.RenameField( 19 | model_name='datafilekey', 20 | old_name='datafile', 21 | new_name='datafile_id', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /data_import/migrations/0011_auto_20190107_1907.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.4 on 2019-01-07 19:07 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('data_import', '0010_custom_20190107_1850'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='datafilekey', 15 | name='datafile_id', 16 | field=models.IntegerField(), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /data_import/migrations/0012_auto_20190312_2031.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.3 on 2019-03-12 20:31 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [("data_import", "0011_auto_20190107_1907")] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="newdatafileaccesslog", 15 | name="data_file", 16 | field=models.ForeignKey( 17 | null=True, 18 | on_delete=django.db.models.deletion.SET_NULL, 19 | related_name="access_logs", 20 | to="data_import.DataFile", 21 | ), 22 | ), 23 | migrations.AlterField( 24 | model_name="newdatafileaccesslog", 25 | name="user", 26 | field=models.ForeignKey( 27 | null=True, 28 | on_delete=django.db.models.deletion.SET_NULL, 29 | to=settings.AUTH_USER_MODEL, 30 | ), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /data_import/migrations/0014_auto_20190327_0035.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-03-27 00:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("data_import", "0013_auto_20190326_1840")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="newdatafileaccesslog", 13 | name="aws_url", 14 | field=models.CharField(max_length=400, null=True), 15 | ) 16 | ] 17 | -------------------------------------------------------------------------------- /data_import/migrations/0015_auto_20190327_1956.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.3 on 2019-03-27 19:56 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("data_import", "0014_auto_20190327_0035")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="awsdatafileaccesslog", 14 | name="serialized_data_file", 15 | field=django.contrib.postgres.fields.jsonb.JSONField( 16 | default=dict, null=True 17 | ), 18 | ), 19 | migrations.AddField( 20 | model_name="newdatafileaccesslog", 21 | name="serialized_data_file", 22 | field=django.contrib.postgres.fields.jsonb.JSONField( 23 | default=dict, null=True 24 | ), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /data_import/migrations/0016_remove_awsdatafileaccesslog_data_file.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.3 on 2019-03-28 16:25 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("data_import", "0015_auto_20190327_1956")] 9 | 10 | operations = [ 11 | migrations.RemoveField(model_name="awsdatafileaccesslog", name="data_file") 12 | ] 13 | -------------------------------------------------------------------------------- /data_import/migrations/0017_auto_20190329_1638.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.3 on 2019-03-29 16:38 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("data_import", "0016_remove_awsdatafileaccesslog_data_file")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="awsdatafileaccesslog", 13 | name="bucket_key", 14 | field=models.CharField(max_length=500, null=True), 15 | ), 16 | migrations.AlterField( 17 | model_name="awsdatafileaccesslog", 18 | name="referrer", 19 | field=models.CharField(max_length=500, null=True), 20 | ), 21 | migrations.AlterField( 22 | model_name="awsdatafileaccesslog", 23 | name="request_uri", 24 | field=models.CharField(max_length=500, null=True), 25 | ), 26 | migrations.AlterField( 27 | model_name="datafilekey", 28 | name="created", 29 | field=models.DateTimeField(auto_now_add=True), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /data_import/migrations/0018_auto_20190402_1947.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.3 on 2019-04-02 19:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("data_import", "0017_auto_20190329_1638")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="awsdatafileaccesslog", 13 | name="bytes_sent", 14 | field=models.BigIntegerField(null=True), 15 | ), 16 | migrations.AlterField( 17 | model_name="awsdatafileaccesslog", 18 | name="object_size", 19 | field=models.BigIntegerField(null=True), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /data_import/migrations/0020_auto_20190430_1756.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2019-04-30 17:56 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("data_import", "0019_datatype")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="datatype", 14 | name="history", 15 | field=django.contrib.postgres.fields.jsonb.JSONField( 16 | default=dict, editable=False 17 | ), 18 | ) 19 | ] 20 | -------------------------------------------------------------------------------- /data_import/migrations/0021_auto_20190521_2031.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-21 20:31 2 | 3 | import data_import.utils 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("data_import", "0020_auto_20190430_1756")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="datafile", 14 | name="file", 15 | field=models.FileField( 16 | max_length=1024, 17 | unique=True, 18 | upload_to=data_import.utils.get_upload_path, 19 | ), 20 | ) 21 | ] 22 | -------------------------------------------------------------------------------- /data_import/migrations/0022_auto_20191105_1924.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-11-05 19:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("data_import", "0021_auto_20190521_2031")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="datatype", name="details", field=models.TextField(blank=True) 13 | ), 14 | migrations.AddField( 15 | model_name="datatype", 16 | name="uploadable", 17 | field=models.BooleanField(default=False), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /data_import/migrations/0023_auto_20191106_2330.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-11-06 23:30 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("data_import", "0022_auto_20191105_1924")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="datatype", 14 | name="description", 15 | field=models.CharField(max_length=100), 16 | ), 17 | migrations.AlterField( 18 | model_name="datatype", 19 | name="name", 20 | field=models.CharField( 21 | max_length=40, 22 | unique=True, 23 | validators=[ 24 | django.core.validators.RegexValidator( 25 | "^[\\w\\-\\s]+$", 26 | "Only alphanumeric characters, space, dash, and underscore are allowed.", 27 | ) 28 | ], 29 | ), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /data_import/migrations/0024_auto_20201222_1757.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-12-22 17:57 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("data_import", "0023_auto_20191106_2330")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="datafilekey", name="datafile_id", field=models.BigIntegerField() 13 | ) 14 | ] 15 | -------------------------------------------------------------------------------- /data_import/migrations/0025_auto_20201222_1934.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-12-22 19:34 2 | 3 | from django.db import migrations, models 4 | import uuid 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("data_import", "0024_auto_20201222_1757")] 10 | 11 | operations = [ 12 | migrations.RemoveField(model_name="datafilekey", name="id"), 13 | migrations.AlterField( 14 | model_name="datafilekey", 15 | name="key", 16 | field=models.CharField( 17 | default=uuid.uuid4, 18 | max_length=36, 19 | primary_key=True, 20 | serialize=False, 21 | unique=True, 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /data_import/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/data_import/migrations/__init__.py -------------------------------------------------------------------------------- /data_import/permissions.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | from rest_framework.permissions import BasePermission 4 | 5 | 6 | class LogAPIAccessAllowed(BasePermission): 7 | """ 8 | Return True if the request is from OHLOG_PROJECT_ID. 9 | """ 10 | 11 | def has_permission(self, request, view): 12 | if settings.OHLOG_PROJECT_ID: 13 | if request.auth.id == int(settings.OHLOG_PROJECT_ID): 14 | return True 15 | return False 16 | -------------------------------------------------------------------------------- /data_import/templates/data_import/datatypes-list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-bs4.html' %} 2 | 3 | {% block main %} 4 |

DataTypes

5 | 6 | 14 | 15 |
16 | Add datatype 17 | 18 | 19 | {% endblock main %} -------------------------------------------------------------------------------- /data_import/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/data_import/templatetags/__init__.py -------------------------------------------------------------------------------- /data_import/templatetags/data_import.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.apps import apps 3 | 4 | from private_sharing.models import project_membership_visible 5 | from public_data.models import is_public 6 | 7 | register = template.Library() 8 | 9 | 10 | @register.simple_tag 11 | def source_is_connected(source, user): 12 | """ 13 | Return True if the given source is connected (has the required data for 14 | retrieving the user's data, like a huID or an access token). 15 | """ 16 | try: 17 | return getattr(user, source).is_connected 18 | except: # pylint: disable=bare-except 19 | return False 20 | 21 | 22 | @register.simple_tag 23 | def source_is_individual_deletion(source): 24 | """ 25 | Return True if the given source allows users to manage each file 26 | individually. 27 | """ 28 | return apps.get_app_config(source).individual_deletion 29 | 30 | 31 | @register.simple_tag(takes_context=True) 32 | def source_is_public(context, source): 33 | """ 34 | Return True if the given source is public for the user in the current 35 | request context. 36 | """ 37 | return is_public(context.request.user.member, source) 38 | 39 | 40 | @register.simple_tag(takes_context=True) 41 | def source_is_visible(context, source): 42 | """ 43 | Returns true if the given source is publicly visible. 44 | """ 45 | return project_membership_visible(context.request.user.member, source) 46 | -------------------------------------------------------------------------------- /data_import/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, re_path 2 | 3 | from .views import ( 4 | AWSDataFileAccessLogView, 5 | DataFileDownloadView, 6 | DataTypesCreateView, 7 | DataTypesDetailView, 8 | DataTypesListView, 9 | DataTypesUpdateView, 10 | NewDataFileAccessLogView, 11 | ) 12 | 13 | app_name = "data-management" 14 | 15 | urlpatterns = [ 16 | re_path( 17 | r"^datafile-download/(?P[0-9]+)/", 18 | DataFileDownloadView.as_view(), 19 | name="datafile-download", 20 | ), 21 | # DataType endpoints 22 | re_path( 23 | r"^datatypes/create/", DataTypesCreateView.as_view(), name="datatypes-create" 24 | ), 25 | re_path( 26 | r"^datatypes/update/(?P[\w-]+)$", 27 | DataTypesUpdateView.as_view(), 28 | name="datatypes-update", 29 | ), 30 | re_path( 31 | r"^datatypes/(?P[\w-]+)$", 32 | DataTypesDetailView.as_view(), 33 | name="datatypes-detail", 34 | ), 35 | re_path(r"^datatypes/", DataTypesListView.as_view(), name="datatypes-list"), 36 | # Custom API endpoints for OHLOG_PROJECT_ID 37 | path( 38 | "awsdatafileaccesslog/", 39 | AWSDataFileAccessLogView.as_view(), 40 | name="awsdatafileaccesslog", 41 | ), 42 | path( 43 | "newdatafileaccesslog/", 44 | NewDataFileAccessLogView.as_view(), 45 | name="newdatafileaccesslog", 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /data_import/utils.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid1 2 | 3 | 4 | def get_upload_path(instance, filename): 5 | """ 6 | Construct a unique S3 path for a source and filename. 7 | """ 8 | return "{0}{1}".format(get_upload_dir(instance=instance), filename) 9 | 10 | 11 | def get_source(instance): 12 | """ 13 | Given an instance, return the associated source. 14 | """ 15 | if hasattr(instance, "source"): 16 | return instance.source 17 | 18 | if hasattr(instance, "_meta"): 19 | return instance._meta.app_label 20 | 21 | return instance 22 | 23 | 24 | def get_upload_dir(instance): 25 | """ 26 | Construct a unique S3 key for a source. 27 | """ 28 | return "member-files/{0}/{1}/".format(get_source(instance), str(uuid1())) 29 | 30 | 31 | def get_upload_dir_validator(instance): 32 | """ 33 | Return regexp string that should match a path generated by get_upload_dir. 34 | """ 35 | return r"^member-files/{0}/[0-9a-f-]{{36}}/".format(get_source(instance)) 36 | -------------------------------------------------------------------------------- /dev-requirements.in: -------------------------------------------------------------------------------- 1 | black 2 | flake8 3 | flake8-quotes 4 | ipython 5 | pip-tools 6 | pre-commit 7 | pydocstyle 8 | pylint 9 | -------------------------------------------------------------------------------- /discourse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/discourse/__init__.py -------------------------------------------------------------------------------- /discourse/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import re_path 2 | 3 | from .views import SingleSignOn 4 | 5 | urlpatterns = [re_path(r"^sso/$", SingleSignOn.as_view())] 6 | -------------------------------------------------------------------------------- /docs/SETUP.md: -------------------------------------------------------------------------------- 1 | ## CORS setup for Amazon S3 2 | 3 | The Dropzone.js script allows us to upload files to Amazon S3 directly from the 4 | client. To do this we need to set up CORS on S3 using the following template: 5 | 6 | ``` 7 | 8 | 9 | 10 | https://www.openhumans.org/ 11 | PUT 12 | POST 13 | GET 14 | 3000 15 | * 16 | 17 | 18 | ``` 19 | -------------------------------------------------------------------------------- /gulp/bundle-logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulpUtil = require('gulp-util'); 4 | var prettyHrtime = require('pretty-hrtime'); 5 | var startTime; 6 | 7 | module.exports = { 8 | start: function (filepath) { 9 | startTime = process.hrtime(); 10 | 11 | gulpUtil.log('Bundling', gulpUtil.colors.green(filepath) + '...'); 12 | }, 13 | 14 | watch: function (filepath) { 15 | gulpUtil.log('Watching files required by', 16 | gulpUtil.colors.yellow(filepath)); 17 | }, 18 | 19 | end: function (filepath) { 20 | var taskTime = process.hrtime(startTime); 21 | var prettyTime = prettyHrtime(taskTime); 22 | 23 | gulpUtil.log('Bundled', gulpUtil.colors.green(filepath), 'in', 24 | gulpUtil.colors.magenta(prettyTime)); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /open_humans/__init__.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.test.runner import DiscoverRunner 3 | 4 | from .celery import app as celery_app 5 | 6 | __all__ = ("celery_app",) 7 | 8 | 9 | default_app_config = "open_humans.apps.OpenHumansConfig" 10 | 11 | 12 | class OpenHumansDiscoverRunner(DiscoverRunner): 13 | """ 14 | A test runner that silences a warning that occurs during testing against 15 | sqlite databases. 16 | """ 17 | 18 | def __init__(self, **kwargs): 19 | # ./manage.py test adds back DeprecationWarnings if verbose is > 0 20 | if settings.IGNORE_SPURIOUS_WARNINGS: 21 | import logging 22 | 23 | logger = logging.getLogger("py.warnings") 24 | logger.handlers = [] 25 | 26 | super(OpenHumansDiscoverRunner, self).__init__(**kwargs) 27 | 28 | def setup_test_environment(self, **kwargs): 29 | super(OpenHumansDiscoverRunner, self).setup_test_environment(**kwargs) 30 | 31 | import warnings 32 | import builtins 33 | 34 | # Filter out the 'naive timezone' warning when using a sqlite database 35 | warnings.filterwarnings( 36 | "ignore", 37 | category=builtins.RuntimeWarning, 38 | module="django.db.models.fields", 39 | lineno=1282, 40 | ) 41 | -------------------------------------------------------------------------------- /open_humans/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | 4 | from .models import FeatureFlag, GrantProject, Member, User 5 | 6 | 7 | class MemberAdmin(admin.ModelAdmin): 8 | """ 9 | Speed up loading users 10 | """ 11 | 12 | raw_id_fields = ("user",) 13 | search_fields = ("user__username", "user__email") 14 | 15 | 16 | class FlagAdmin(admin.ModelAdmin): 17 | """ 18 | With improved User management. 19 | """ 20 | 21 | raw_id_fields = ("users",) 22 | 23 | 24 | class GrantProjectAdmin(admin.ModelAdmin): 25 | list_display = [field.name for field in GrantProject._meta.get_fields()] 26 | 27 | 28 | admin.site.register(Member, MemberAdmin) 29 | admin.site.register(User, UserAdmin) 30 | admin.site.register(FeatureFlag, FlagAdmin) 31 | admin.site.register(GrantProject, GrantProjectAdmin) 32 | -------------------------------------------------------------------------------- /open_humans/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.conf import settings 3 | 4 | 5 | class OpenHumansConfig(AppConfig): 6 | """ 7 | Configure the main Open Humans application. 8 | """ 9 | 10 | name = "open_humans" 11 | verbose_name = "Open Humans" 12 | 13 | def ready(self): 14 | # Import this last as it's going to import settings itself... 15 | from django.contrib.sites import models as sites_models 16 | 17 | # Make sure our signal handlers get hooked up 18 | # pylint: disable=unused-variable 19 | import open_humans.signals # noqa 20 | -------------------------------------------------------------------------------- /open_humans/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.conf import settings 4 | 5 | from celery import Celery 6 | 7 | 8 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "open_humans.settings") 9 | 10 | app = Celery("open_humans") 11 | 12 | 13 | app.conf.update( 14 | { 15 | "BROKER_URL": settings.CELERY_BROKER_URL, 16 | # Recommended settings. See: https://www.cloudamqp.com/docs/celery.html 17 | "BROKER_POOL_LIMIT": None, 18 | "BROKER_HEARTBEAT": None, 19 | "BROKER_CONNECTION_TIMEOUT": 30, 20 | "CELERY_RESULT_BACKEND": settings.CELERY_BROKER_URL, 21 | "CELERY_SEND_EVENTS": False, 22 | "CELERY_EVENT_QUEUE_EXPIRES": 60, 23 | } 24 | ) 25 | app.config_from_object("django.conf:settings", namespace="CELERY") 26 | 27 | if os.getenv("ON_HEROKU"): 28 | # if running on Heroku, set the redis_backend_use_ssl config var 29 | app.conf.redis_backend_use_ssl = {"ssl_cert_reqs": "none"} 30 | 31 | # Load task modules from all registered Django app configs. 32 | app.autodiscover_tasks() 33 | 34 | 35 | @app.task(bind=True) 36 | def debug_task(self): 37 | print("Request: {0!r}".format(self.request)) 38 | -------------------------------------------------------------------------------- /open_humans/formatters.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from colors import color 4 | 5 | 6 | class LocalFormat(logging.Formatter): 7 | """ 8 | A logging format for local development. 9 | """ 10 | 11 | def format(self, record): 12 | if record.levelname == "DEBUG": 13 | record.levelname = color(record.levelname, fg=240) 14 | elif record.levelname == "INFO": 15 | record.levelname = color(record.levelname, fg=248) 16 | elif record.levelname == "WARNING": 17 | record.levelname = color(record.levelname, fg=220) 18 | elif record.levelname == "ERROR": 19 | record.levelname = color(record.levelname, fg=9) 20 | 21 | record.context = color("{0.name}:{0.lineno}".format(record), fg=5) 22 | 23 | return super(LocalFormat, self).format(record) 24 | -------------------------------------------------------------------------------- /open_humans/hooksets.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from smtplib import SMTPRecipientsRefused 4 | 5 | from account.hooks import AccountDefaultHookSet 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class OpenHumansHookSet(AccountDefaultHookSet): 11 | """ 12 | Override for sending the confirmation email that doesn't explode if the 13 | mailing process fails. 14 | """ 15 | 16 | def send_confirmation_email(self, *args, **kwargs): 17 | try: 18 | super(OpenHumansHookSet, self).send_confirmation_email(*args, **kwargs) 19 | except SMTPRecipientsRefused: 20 | logger.warn("Unable to send confirmation mail.") 21 | -------------------------------------------------------------------------------- /open_humans/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/open_humans/management/__init__.py -------------------------------------------------------------------------------- /open_humans/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/open_humans/management/commands/__init__.py -------------------------------------------------------------------------------- /open_humans/management/commands/expunge_addresses.py: -------------------------------------------------------------------------------- 1 | import arrow 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from data_import.models import NewDataFileAccessLog 6 | 7 | 8 | class Command(BaseCommand): 9 | """ 10 | A management command for expunging IP addresses. 11 | """ 12 | 13 | help = "Expunge IP addresses" 14 | 15 | def handle(self, *args, **options): 16 | self.stdout.write("Expunging addresses") 17 | 18 | ninety_days_ago = arrow.utcnow().shift(days=-90) 19 | 20 | # remove the IP address from all logs older than 90 days 21 | expunged_logs = NewDataFileAccessLog.objects.filter( 22 | date__lt=ninety_days_ago.datetime, ip_address__isnull=False 23 | ).update(ip_address=None) 24 | 25 | self.stdout.write("Removed {} IP addresses".format(expunged_logs)) 26 | -------------------------------------------------------------------------------- /open_humans/management/commands/expunge_project_data_files.py: -------------------------------------------------------------------------------- 1 | import arrow 2 | 3 | from django.conf import settings 4 | from django.core.management.base import BaseCommand 5 | 6 | from private_sharing.models import ProjectDataFile 7 | 8 | 9 | class Command(BaseCommand): 10 | """ 11 | A management command for expunging incomplete project data files. 12 | 13 | A ProjectDataFile is incomplete when its `complete` attribute is set to 14 | False and more time has elapsed than the number of hours in 15 | INCOMPLETE_FILE_EXPIRATION_HOURS. 16 | """ 17 | 18 | help = "Expunge incomplete project data files" 19 | 20 | def handle(self, *args, **options): 21 | self.stdout.write("Expunging incomplete project data files") 22 | 23 | expired_time = arrow.utcnow().shift( 24 | hours=-settings.INCOMPLETE_FILE_EXPIRATION_HOURS 25 | ) 26 | 27 | # remove incomplete files older than the expiration time 28 | expunged_files = ProjectDataFile.all_objects.filter( 29 | created__lt=expired_time.datetime, completed=False 30 | ).delete() 31 | 32 | self.stdout.write("Removed {} data files".format(expunged_files)) 33 | -------------------------------------------------------------------------------- /open_humans/management/commands/get_refresh_tokens.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from oauth2_provider.models import RefreshToken 6 | from private_sharing.models import DataRequestProject, DataRequestProjectMember 7 | 8 | 9 | class Command(BaseCommand): 10 | help = "Get refresh tokens" 11 | args = "" 12 | 13 | def add_arguments(self, parser): 14 | parser.add_argument("--proj-id", type=str, help="ID of project to transfer to") 15 | 16 | def handle(self, *args, **options): 17 | drp = DataRequestProject.objects.get(id=options["proj_id"]) 18 | app = drp.oauth2datarequestproject.application 19 | 20 | refresh_tokens = { 21 | rt.user.id: rt for rt in RefreshToken.objects.filter(application=app) 22 | } 23 | drpms = { 24 | drpm.member.user.id: drpm 25 | for drpm in DataRequestProjectMember.objects.filter(project=drp) 26 | } 27 | 28 | auth_refresh_tokens = {} 29 | 30 | for uid in sorted(drpms.keys()): 31 | print( 32 | "{},{}".format(drpms[uid].project_member_id, refresh_tokens[uid].token) 33 | ) 34 | -------------------------------------------------------------------------------- /open_humans/management/commands/remove_expired_keys.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from data_import.models import DataFileKey 6 | 7 | 8 | class Command(BaseCommand): 9 | """ 10 | A management command for expunging expired keys 11 | """ 12 | 13 | help = "Expunge expired keys" 14 | 15 | def handle(self, *args, **options): 16 | self.stdout.write("Expunging expired keys") 17 | now = datetime.datetime.utcnow() 18 | # Note: astimezone reapplies the timezone so that django doesn't 19 | # complain 20 | six_hours_ago = (now - datetime.timedelta(hours=6)).astimezone() 21 | keys = DataFileKey.objects.filter(created__lte=six_hours_ago) 22 | num_deletes = keys.delete()[0] 23 | self.stdout.write("Removed {0} keys".format(num_deletes)) 24 | -------------------------------------------------------------------------------- /open_humans/management/commands/rotate_file_access_logs.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from data_import.models import AWSDataFileAccessLog, NewDataFileAccessLog 6 | 7 | LOG_ROTATE_DAYS = 120 8 | 9 | 10 | class Command(BaseCommand): 11 | """ 12 | A management command for rotating file access logs 13 | """ 14 | 15 | help = "Expunge old log entries" 16 | 17 | def handle(self, *args, **options): 18 | self.stdout.write("Removing logs older than {0} days".format(LOG_ROTATE_DAYS)) 19 | now = datetime.datetime.utcnow() 20 | # Note: astimezone reapplies the timezone so that django doesn't 21 | # complain 22 | log_rotate_days_ago = ( 23 | now - datetime.timedelta(days=LOG_ROTATE_DAYS) 24 | ).astimezone() 25 | aws_logs = AWSDataFileAccessLog.objects.filter(created__lte=log_rotate_days_ago) 26 | num_deletes = aws_logs.delete()[0] 27 | self.stdout.write("Removed {0} AWS log entries".format(num_deletes)) 28 | oh_logs = NewDataFileAccessLog.objects.filter(date__lte=log_rotate_days_ago) 29 | num_deletes = oh_logs.delete()[0] 30 | self.stdout.write("Removed {0} Open Humans log entries".format(num_deletes)) 31 | -------------------------------------------------------------------------------- /open_humans/management/commands/user_growth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.core.management.base import BaseCommand 4 | from open_humans.models import Member 5 | 6 | 7 | class Command(BaseCommand): 8 | help = "for plotting user growth" 9 | args = "" 10 | 11 | def handle(self, *args, **options): 12 | user_number = 1 13 | members = Member.objects.order_by("user__date_joined").filter( 14 | user__is_active=True 15 | ) 16 | for member in members: 17 | print("{0}\t{1}".format(member.user.date_joined, user_number)) 18 | user_number += 1 19 | -------------------------------------------------------------------------------- /open_humans/migrations/0002_auto_20141111_1400.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models, migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='profile', 15 | name='allow_user_messages', 16 | field=models.BooleanField(default=True, verbose_name='Allow members to contact me'), 17 | preserve_default=True, 18 | ), 19 | migrations.AddField( 20 | model_name='profile', 21 | name='newsletter', 22 | field=models.BooleanField(default=True, verbose_name='Receive Open Humans news and updates'), 23 | preserve_default=True, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /open_humans/migrations/0002_member_seen_pgp_interstitial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0001_squashed_0016_auto_20150410_2301'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='member', 15 | name='seen_pgp_interstitial', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /open_humans/migrations/0003_rename_profile_to_member.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations 4 | from django.conf import settings 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ('open_humans', '0002_auto_20141111_1400'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameModel('Profile', 'Member') 16 | ] 17 | -------------------------------------------------------------------------------- /open_humans/migrations/0004_auto_20150106_1828.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models, migrations 4 | import open_humans.models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('open_humans', '0003_rename_profile_to_member'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='member', 16 | name='profile_image', 17 | field=models.ImageField(upload_to=open_humans.models.get_member_profile_image_upload_path, blank=True), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /open_humans/migrations/0004_member_badges.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('open_humans', '0003_auto_20151223_1827'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='member', 16 | name='badges', 17 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /open_humans/migrations/0005_rename_content_type.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations 4 | from django.conf import settings 5 | 6 | sql = """UPDATE django_content_type 7 | SET model = 'member' 8 | WHERE model = 'profile' AND 9 | app_label = 'open_humans';""" 10 | 11 | reverse_sql = """UPDATE django_content_type 12 | SET model = 'profile' 13 | WHERE model = 'member' AND 14 | app_label = 'open_humans';""" 15 | 16 | 17 | class Migration(migrations.Migration): 18 | 19 | dependencies = [ 20 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 21 | ('open_humans', '0004_auto_20150106_1828'), 22 | ] 23 | 24 | operations = [ 25 | migrations.RunSQL(sql, reverse_sql) 26 | ] 27 | -------------------------------------------------------------------------------- /open_humans/migrations/0005_userevent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.conf import settings 4 | import django.contrib.postgres.fields.jsonb 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('open_humans', '0004_member_badges'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='UserEvent', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('timestamp', models.DateTimeField(auto_now_add=True)), 21 | ('data', django.contrib.postgres.fields.jsonb.JSONField(default=dict)), 22 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /open_humans/migrations/0006_auto_20150123_2121.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models, migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0005_rename_content_type'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='member', 15 | name='allow_user_messages', 16 | field=models.BooleanField(default=False, verbose_name='Allow members to contact me'), 17 | preserve_default=True, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /open_humans/migrations/0006_userevent_event_type.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0005_userevent'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='userevent', 15 | name='event_type', 16 | field=models.CharField(default='', max_length=32), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /open_humans/migrations/0007_blogpost.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0006_userevent_event_type'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='BlogPost', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('rss_id', models.CharField(max_length=120, unique=True)), 18 | ('title', models.CharField(blank=True, max_length=120)), 19 | ('summary_long', models.TextField(blank=True)), 20 | ('summary_short', models.TextField(blank=True)), 21 | ('image_url', models.CharField(blank=True, max_length=120)), 22 | ('published', models.DateTimeField()), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /open_humans/migrations/0007_member_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models, migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0006_auto_20150123_2121'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='member', 15 | name='name', 16 | field=models.CharField(default='', max_length=30), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /open_humans/migrations/0008_member_member_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models, migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0007_member_name'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='member', 15 | name='member_id', 16 | field=models.CharField(max_length=8, blank=True), 17 | preserve_default=True, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /open_humans/migrations/0008_rm_ghost_illuminauyg_table.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations 4 | 5 | DROP_UNUSED_TABLES = """\ 6 | DROP TABLE IF EXISTS illumina_uyg_userdata; 7 | """ 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('open_humans', '0007_blogpost'), 14 | ] 15 | 16 | operations = [ 17 | migrations.RunSQL(DROP_UNUSED_TABLES), 18 | ] 19 | -------------------------------------------------------------------------------- /open_humans/migrations/0009_grantproject.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations, models 4 | import open_humans.models 5 | import open_humans.storage 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('open_humans', '0008_rm_ghost_illuminauyg_table'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='GrantProject', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=255, unique=True)), 20 | ('grant_date', models.DateField(null=True)), 21 | ('status', models.CharField(max_length=120)), 22 | ('github', models.TextField(blank=True)), 23 | ('grantee_name', models.CharField(max_length=255)), 24 | ('photo', models.ImageField(blank=True, max_length=1024, storage=open_humans.storage.PublicStorage(), upload_to=open_humans.models.get_member_profile_image_upload_path)), 25 | ('blog_url', models.TextField()), 26 | ('project_desc', models.TextField()), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /open_humans/migrations/0009_random_member_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations 4 | import open_humans.models 5 | 6 | 7 | def add_member_ids(apps, *args): 8 | Member = apps.get_model('open_humans', 'Member') 9 | 10 | for member in Member.objects.all(): 11 | if not member.member_id: 12 | member.member_id = open_humans.models.random_member_id() 13 | member.save() 14 | 15 | 16 | class Migration(migrations.Migration): 17 | dependencies = [ 18 | ('open_humans', '0008_member_member_id'), 19 | ] 20 | 21 | operations = [ 22 | # add member IDs for all members without them 23 | migrations.RunPython(add_member_ids), 24 | ] 25 | -------------------------------------------------------------------------------- /open_humans/migrations/0010_auto_20150311_1922.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models, migrations 4 | import open_humans.models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('open_humans', '0009_random_member_id'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='member', 16 | name='member_id', 17 | field=models.CharField(default=open_humans.models.random_member_id, unique=True, max_length=8), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /open_humans/migrations/0010_auto_20180611_2029.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations, models 4 | import open_humans.models 5 | import open_humans.storage 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('open_humans', '0009_grantproject'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='grantproject', 17 | name='photo', 18 | field=models.ImageField(blank=True, max_length=1024, storage=open_humans.storage.PublicStorage(), upload_to=open_humans.models.get_grant_project_image_upload_path), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /open_humans/migrations/0011_auto_20150326_0650.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models, migrations 4 | import open_humans.models 5 | import open_humans.storage 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('open_humans', '0010_auto_20150311_1922'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='member', 17 | name='profile_image', 18 | field=models.ImageField(storage=open_humans.storage.PublicStorage(), upload_to=open_humans.models.get_member_profile_image_upload_path, blank=True), 19 | preserve_default=True, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /open_humans/migrations/0011_auto_20180709_2220.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0010_auto_20180611_2029'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='blogpost', 15 | name='image_url', 16 | field=models.CharField(blank=True, max_length=2083), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /open_humans/migrations/0012_add_username_lower_index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations 4 | 5 | forward_sql = """CREATE UNIQUE INDEX auth_user_username_lower 6 | ON auth_user (LOWER(username));""" 7 | 8 | reverse_sql = """DROP INDEX auth_user_username_lower;""" 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('open_humans', '0011_auto_20150326_0650'), 15 | ] 16 | 17 | operations = [ 18 | migrations.RunSQL(forward_sql, reverse_sql) 19 | ] 20 | -------------------------------------------------------------------------------- /open_humans/migrations/0012_auto_20180725_2020.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.7 on 2018-07-25 20:20 2 | 3 | import django.contrib.auth.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('open_humans', '0011_auto_20180709_2220'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='user', 16 | name='last_name', 17 | field=models.CharField(blank=True, max_length=150, verbose_name='last name'), 18 | ), 19 | migrations.AlterField( 20 | model_name='user', 21 | name='username', 22 | field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /open_humans/migrations/0013_auto_20150403_2323.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models, migrations 4 | import open_humans.models 5 | import open_humans.storage 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('open_humans', '0012_add_username_lower_index'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='member', 17 | name='profile_image', 18 | field=models.ImageField(storage=open_humans.storage.PublicStorage(), max_length=1024, upload_to=open_humans.models.get_member_profile_image_upload_path, blank=True), 19 | preserve_default=True, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /open_humans/migrations/0013_remove_member_badges.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.7 on 2018-08-30 22:16 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0012_auto_20180725_2020'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='member', 15 | name='badges', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /open_humans/migrations/0014_member_password_reset_redirect.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.1 on 2018-09-24 23:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0013_remove_member_badges'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='member', 15 | name='password_reset_redirect', 16 | field=models.CharField(blank=True, default='', max_length=254, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /open_humans/migrations/0014_rename_openhumansuser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import migrations 4 | from django.conf import settings 5 | 6 | sql = """UPDATE django_content_type 7 | SET model = 'user' 8 | WHERE model = 'openhumansuser' AND 9 | app_label = 'open_humans';""" 10 | 11 | reverse_sql = """UPDATE django_content_type 12 | SET model = 'openhumansuser' 13 | WHERE model = 'user' AND 14 | app_label = 'open_humans';""" 15 | 16 | 17 | class Migration(migrations.Migration): 18 | 19 | dependencies = [ 20 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 21 | ('open_humans', '0013_auto_20150403_2323'), 22 | ] 23 | 24 | operations = [ 25 | migrations.RunSQL(sql, reverse_sql) 26 | ] 27 | -------------------------------------------------------------------------------- /open_humans/migrations/0016_auto_20150410_2301.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.db import models, migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('open_humans', '0015_auto_20150410_0042'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelManagers( 14 | name='member', 15 | managers=[ 16 | ], 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /open_humans/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/open_humans/migrations/__init__.py -------------------------------------------------------------------------------- /open_humans/mixins.py: -------------------------------------------------------------------------------- 1 | from common.utils import get_activities, get_source_labels_and_configs 2 | 3 | 4 | class SourcesContextMixin(object): 5 | """ 6 | A mixin for adding context for connection sources to a template. 7 | """ 8 | 9 | def get_context_data(self, **kwargs): 10 | context = super(SourcesContextMixin, self).get_context_data(**kwargs) 11 | 12 | context.update( 13 | { 14 | "sources": dict(get_source_labels_and_configs()), 15 | "activities": [ 16 | activity 17 | for activity in get_activities() 18 | if not activity[1].in_development 19 | ], 20 | "in_development_activities": [ 21 | activity 22 | for activity in get_activities() 23 | if activity[1].in_development 24 | ], 25 | } 26 | ) 27 | 28 | return context 29 | -------------------------------------------------------------------------------- /open_humans/storage.py: -------------------------------------------------------------------------------- 1 | from storages.backends.s3boto3 import S3Boto3Storage 2 | 3 | TEN_MINUTES = 60 * 10 4 | 5 | 6 | # pylint: disable=abstract-method 7 | class PrivateStorage(S3Boto3Storage): 8 | """ 9 | Private storage. 10 | """ 11 | 12 | def __init__(self, *args, **kwargs): 13 | kwargs["acl"] = "private" 14 | kwargs["querystring_auth"] = True 15 | kwargs["querystring_expires"] = TEN_MINUTES 16 | 17 | super(PrivateStorage, self).__init__(*args, **kwargs) 18 | 19 | 20 | # pylint: disable=abstract-method 21 | class PublicStorage(S3Boto3Storage): 22 | """ 23 | Public storage for user profile images. 24 | """ 25 | 26 | def __init__(self, *args, **kwargs): 27 | kwargs["acl"] = "public-read" 28 | kwargs["querystring_auth"] = False 29 | 30 | super(PublicStorage, self).__init__(*args, **kwargs) 31 | -------------------------------------------------------------------------------- /open_humans/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %} 6 | Page not found 7 | {% endblock %} 8 | 9 | {% block panel_content %} 10 |

11 | The requested page was not found. 12 |

13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /open_humans/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %} 6 | Server error 7 | {% endblock %} 8 | 9 | {% block panel_content %} 10 |

11 | Sorry, your request resulted in a server error. The error has been logged and 12 | we'll investigate it. 13 |

14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /open_humans/templates/CSRF-error.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %} 6 | CSRF error 7 | {% endblock %} 8 | 9 | {% block panel_content %} 10 |

11 | Reason: {{ reason }} 12 |

13 |

14 | The data submitted has raised a "cross-site request forgery" (CSRF) error, 15 | we were unable to validate and confirm authorization. 16 |

17 |

18 | This error can be caused by logging out or switching accounts. 19 |

20 |

21 | If you believe this error is due to a bug in the site, please contact us 22 | at support@openhumans.org. 23 |

24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /open_humans/templates/account/delete.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %}Delete My Account{% endblock %} 6 | 7 | {% block panel_content %} 8 |
9 |
10 |

11 | If you delete your account with username "{{ user.username }}" all of 12 | your Open Humans information and shared data will be deleted 13 | immediately. 14 |

15 |
16 |
17 | 18 |
19 |
20 |
21 | {% csrf_token %} 22 | 23 |

24 | 25 |

26 |
27 |
28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /open_humans/templates/account/email/email_confirmation_message.txt: -------------------------------------------------------------------------------- 1 | Dear {{ user.username }}, 2 | 3 | Thank you for joining Open Humans and creating an account! 4 | 5 | We'd like you to verify this email address for your account 6 | at {{ current_site }}. 7 | 8 | Doing this assures us that we have a valid email address for you. 9 | 10 | Please click the link below to confirm this email address: 11 | 12 | {{ activate_url }} 13 | -------------------------------------------------------------------------------- /open_humans/templates/account/email/email_confirmation_subject.txt: -------------------------------------------------------------------------------- 1 | [Open Humans] Please verify this email address for your account. 2 | -------------------------------------------------------------------------------- /open_humans/templates/account/email/invite_user.txt: -------------------------------------------------------------------------------- 1 | Open Humans helps you connect to exciting and innovative research studies – and 2 | gives you access to the data they produce from you. 3 | 4 | The link below contains a signup code you can use to create an account. Once you 5 | sign up you'll have access to link your Open Humans account with American Gut, 6 | PGP Harvard, and GoViral. 7 | 8 | Please don't share our "stealth" website with others just yet. Our public launch 9 | is next Tuesday, March 24. 10 | 11 | You can use the following URL to create your account: 12 | 13 | {{ signup_url }} 14 | -------------------------------------------------------------------------------- /open_humans/templates/account/email/invite_user_subject.txt: -------------------------------------------------------------------------------- 1 | You're invited to join Open Humans! -------------------------------------------------------------------------------- /open_humans/templates/account/email/password_change.txt: -------------------------------------------------------------------------------- 1 | {{ user.username }}, 2 | 3 | This email is a notification that the password for your account on Open Humans 4 | ({{ current_site.domain }}) has been changed on {{ user.account.now }} 5 | 6 | If you did not perform this change, you can contact us at 7 | support@openhumans.org. 8 | -------------------------------------------------------------------------------- /open_humans/templates/account/email/password_change_subject.txt: -------------------------------------------------------------------------------- 1 | [Open Humans] Your password has been changed 2 | -------------------------------------------------------------------------------- /open_humans/templates/account/email/password_reset.txt: -------------------------------------------------------------------------------- 1 | You're receiving this email because you or someone else has requested a 2 | password reset for your user account at {{ current_site }}. 3 | 4 | It can be safely ignored if you did not request a password reset. Click the 5 | link below to reset your password. 6 | 7 | {{ password_reset_url }} 8 | -------------------------------------------------------------------------------- /open_humans/templates/account/email/password_reset_subject.txt: -------------------------------------------------------------------------------- 1 | [Open Humans] Password Reset 2 | -------------------------------------------------------------------------------- /open_humans/templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% block head_title %}Confirm your email{% endblock %} 4 | 5 | {% block panel_content %} 6 |
7 |
8 |
10 |
11 | {% csrf_token %} 12 |

13 | Confirm the email address 14 | {{ confirmation.email_address.email }} for Open 15 | Humans member 16 | {{ confirmation.email_address.user.username }}? 17 |

18 |
19 | 20 |
21 |
22 |
23 |
24 |
25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /open_humans/templates/account/login-form.html: -------------------------------------------------------------------------------- 1 | {% load utilities %} 2 | 3 |
5 | 6 | {% csrf_token %} 7 | 8 | {% if form.non_field_errors %} 9 |
10 |

11 | {% for error in form.non_field_errors%} 12 | Error: {{ error }} 13 | {% endfor %} 14 |

15 |
16 | {% endif %} 17 | 18 |
19 | 20 | 21 | 24 | {% if form.username.errors %} 25 | {% for error in form.username.errors %} 26 | {{ error }} 27 | {% endfor %} 28 | {% endif %} 29 |
30 | 31 |
32 | 33 | 34 | 37 | {% if form.password.errors %} 38 | {% for error in form.password.errors %} 39 | {{ error }} 40 | {% endfor %} 41 | {% endif %} 42 |
43 | -------------------------------------------------------------------------------- /open_humans/templates/account/login-oauth2.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %}Connecting {{connection}}{% endblock %} 6 | 7 | {% block panel_content %} 8 |
9 |
10 |

11 | You'll need an Open Humans account to add a connection for 12 | {{ connection }}. 13 |

14 |
15 | 20 |
21 |
22 |
23 |
24 |

Already a member?

25 |
26 |
27 | Log in 30 |
31 |
32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /open_humans/templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% load i18n %} 6 | {% load account socialaccount %} 7 | 8 | {% block head_title %}{% trans "Log in to Open Humans" %}{% endblock %} 9 | {% block head_title_suffix %}{% endblock %} 10 | 11 | {% block panel_content %} 12 | 13 |
14 |
15 | {% include 'account/login-form.html' %} 16 | 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 | or 26 |
27 | {% include 'account/login-social.html' %} 28 |
29 |
30 | 31 |
32 |
33 |
34 | Reset password 35 | | 36 | 38 |
39 |
40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /open_humans/templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% block head_title %}Log out of Open Humans{% endblock %} 4 | {% block head_title_suffix %}{% endblock %} 5 | 6 | {% block panel_content %} 7 |
8 |

9 | Click the button below to log out. 10 |

11 |
12 | {% csrf_token %} 13 | 14 |
15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /open_humans/templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load utilities %} 5 | 6 | {% block head_title %}Change Password{% endblock %} 7 | 8 | {% block panel_content %} 9 |
10 |
12 | {% csrf_token %} 13 | 14 | 15 | 16 | {{ form|as_bootstrap_horizontal:'col-md-4' }} 17 | 18 | 20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /open_humans/templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load utilities %} 5 | {% load i18n %} 6 | 7 | {% block head_title %}Reset your Open Humans password{% endblock %} 8 | {% block head_title_suffix %}{% endblock %} 9 | 10 | {% block panel_content %} 11 |
12 |
13 |
14 | {{ form.non_field_errors }} 15 |

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

18 | 19 |
21 | {% csrf_token %} 22 | 23 | {{ form|as_bootstrap_horizontal:'col-md-4' }} 24 | 25 | 28 |
29 | 30 |
31 | 32 |

Still having trouble?

33 |

34 | Please wait several minutes for our email. If you're still having 35 | trouble, you can contact us at: 36 | support@openhumans.org. 37 |

38 | 39 |
40 |
41 |
42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /open_humans/templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load utilities %} 5 | 6 | {% block head_title %}Set your new password{% endblock %} 7 | 8 | {% block panel_content %} 9 |
10 |
11 | {{ form.non_field_errors }} 12 |

13 | You can set your new password using the form below. 14 |

15 | 16 |
19 | {% csrf_token %} 20 | 21 | 22 | 23 | {{ form|as_bootstrap_horizontal:'col-md-4' }} 24 | 25 | 28 |
29 |
30 |
31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /open_humans/templates/account/password_reset_sent.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% block head_title %}Password reset sent{% endblock %} 4 | 5 | {% block panel_content %} 6 |

7 | Thanks! We've sent you a password reset email. 8 |

9 |

If you don't receive it within a few minutes, you can contact us at 10 | support@openhumans.org. 11 |

12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /open_humans/templates/account/password_reset_token_fail.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %}Bad token{% endblock %} 6 | 7 | {% block panel_content %} 8 |
9 |
10 | {% url "account_reset_password" as url %} 11 | 12 |

13 | The password reset link was invalid, possibly because it has already been 14 | used. Please request a new password reset. 15 |

16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /open_humans/templates/account/password_set.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load utilities %} 5 | 6 | {% block head_title %}Set Password{% endblock %} 7 | 8 | {% block panel_content %} 9 |
10 |
12 | {% csrf_token %} 13 | 14 | 15 | 16 | {{ form|as_bootstrap_horizontal:'col-md-4' }} 17 | 18 | 20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /open_humans/templates/account/signup-email.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% block head_title %}Create an Open Humans account{% endblock %} 4 | 5 | {% block panel_content %} 6 |
7 |
8 | {% include 'account/signup-form.html' %} 9 |
10 |
11 | 12 |
13 |
14 | 16 |
17 | 18 |
19 |
20 |
21 | 22 |
23 |

24 | Already have an account? 25 | 26 | 27 |

28 |
29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /open_humans/templates/account/signup-modal-bs4.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 44 | 45 | -------------------------------------------------------------------------------- /open_humans/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load static %} 4 | {% load utilities %} 5 | 6 | {% block head_title %}Create an Open Humans account{% endblock %} 7 | 8 | {% block panel_content %} 9 |
10 |
11 |
12 | 13 | {% include 'account/login-social.html' %} 14 | 15 |
16 |

or

17 |
18 | 19 | 26 | 27 |
28 |
29 | 30 |
31 |
32 |
33 | 34 |
35 |

36 | Already have an account? 37 | 38 | 39 |

40 |
41 |
42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /open_humans/templates/account/signup_closed.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% block head_title %}Open Humans is currently invite only{% endblock %} 4 | {% block head_title_suffix %}Open Humans is currently invite only{% endblock %} 5 | 6 | {% block panel_content %} 7 |
8 |
9 |

10 | Sorry, Open Humans is currently open by invitation only, ahead of our 11 | public launch. 12 |

13 |

14 | If we invited you, and you're having trouble creating an account, 15 | please let us know! 16 |

17 |
18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /open_humans/templates/email/activity-message.html: -------------------------------------------------------------------------------- 1 | {{ message|linebreaks }} 2 | 3 |
4 | 5 |
6 | 7 |

8 | This message was sent to the project "{{ project.name }}" by project member ID 9 | "{{ project_member_id }}". 10 |

11 | 12 |

13 | Please do not reply to this email! You can reply to this project member 14 | selecting "Message members" for this project in your Open Humans 15 | project 16 | management page. 17 |

18 | -------------------------------------------------------------------------------- /open_humans/templates/email/activity-message.txt: -------------------------------------------------------------------------------- 1 | {{ message }} 2 | 3 | ------------------------------------------------------------------------------- 4 | 5 | This message was sent to the project "{{ project.name }}" 6 | by project member ID "{{ project_member_id }}". 7 | 8 | Please do not reply to this email! You can reply to this project member 9 | selecting "Message members" for this project in your Open Humans project 10 | management page . 11 | -------------------------------------------------------------------------------- /open_humans/templates/email/user-message.html: -------------------------------------------------------------------------------- 1 | {{ message|linebreaks }} 2 | 3 |
4 | 5 |
6 | 7 |

8 | This email was sent by Open Humans member "{{ sender.username }}" to 9 | "{{ receiver.username }}". It has been automatically delivered. The 10 | sender has not been given your email address. You have no obligation to reply 11 | to this email or take any other action that might disclose your identity. 12 | Open Humans did not review nor retain a copy of this message’s contents and 13 | is not responsible for material sent by this member. You should retain a 14 | copy of this message if you may need it in the future. 15 |

16 | 17 |

18 | If you respond, the sender will know your email address. 19 | Your email will go directly to the sender’s email address, this communication 20 | is not managed by Open Humans. 21 |

22 | 23 |

24 | Receiving messages is optional. You may refuse messages by going to your account 26 | settings and unselecting “Allow other members to contact you?” Please 27 | report abuses of messaging to Open 28 | Humans support. 29 |

30 | -------------------------------------------------------------------------------- /open_humans/templates/email/user-message.txt: -------------------------------------------------------------------------------- 1 | {{ message }} 2 | 3 | ------------------------------------------------------------------------------- 4 | 5 | This email was sent by Open Humans member "{{ sender.username }}" to 6 | "{{ receiver.username }}". It has been automatically delivered. The sender has 7 | not been given your email address. You have no obligation to reply to this 8 | email or take any other action that might disclose your identity. Open Humans 9 | did not review nor retain a copy of this message’s contents and is not 10 | responsible for material sent by this member. You should retain a copy of this 11 | message if you may need it in the future. 12 | 13 | If you respond, the sender will know your email address. Your email will go 14 | directly to the sender’s email address, this communication is not managed by 15 | Open Humans. 16 | 17 | Receiving messages is optional. You may refuse messages going to your account 18 | settings and unselecting "Allow other members to contact you?" 19 | . Please report abuses 20 | of messaging to Open Humans support . 21 | -------------------------------------------------------------------------------- /open_humans/templates/member/member-detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block head_title %}{{ member.user.username }}{% endblock %} 6 | 7 | {% block main %} 8 |
9 |
10 | {% include 'member/member-detail-content.html' %} 11 |
12 |
13 | {% endblock main %} 14 | -------------------------------------------------------------------------------- /open_humans/templates/member/my-member-change-email.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load utilities %} 5 | 6 | {% block head_title %}Change Email{% endblock %} 7 | 8 | {% block panel_content %} 9 |
10 |

11 | Note: Email change only takes effect after your 12 | confirmation! Make sure to check your email after this. 13 |

14 |
15 |
17 | {% csrf_token %} 18 | 19 | 20 | 21 |
22 | 23 |
24 |

{{ user.email }}

25 |
26 |
27 | 28 | {{ form|as_bootstrap_horizontal:'col-md-4' }} 29 | 30 | 31 | A confirmation email will be sent to your new email address so we can 32 | confirm it is correct. 33 | 34 | 35 | 37 |
38 |
39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /open_humans/templates/member/my-member-change-name.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load utilities %} 5 | 6 | {% block head_title %}Change Name{% endblock %} 7 | 8 | {% block panel_content %} 9 |
10 |
12 | {% csrf_token %} 13 | 14 | 15 | 16 |
17 | 18 |
19 |

{{ user.member.name }}

20 |
21 |
22 | 23 | {{ form|as_bootstrap_horizontal:"col-md-4" }} 24 | 25 | 26 | Your name is publicly visible. You may use any name you wish, provided you follow our 27 | Naming Guidelines. 28 | 29 | 30 | 32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /open_humans/templates/member/my-member-change-username.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load utilities %} 5 | 6 | {% block head_title %}Change Username{% endblock %} 7 | 8 | {% block panel_content %} 9 |
10 |
12 | {% csrf_token %} 13 | 14 | 15 | 16 |
17 | 18 |
19 |

{{ user.username }}

20 |
21 |
22 | 23 | {{ form|as_bootstrap_horizontal:"col-md-4" }} 24 | 25 | 26 | Your username is publicly visible. You may use any username you wish, provided it's available and you follow our 27 | Naming Guidelines. 28 | 29 | 30 | 32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /open_humans/templates/member/my-member-connections-delete.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %}Confirm removal{% endblock %} 6 | 7 | {% block panel_content %} 8 |
9 |
10 |

Are you sure you want to remove the connection to 11 | {{ connection_name }}?

12 |
13 |
14 | 15 |
16 |
17 |
18 | {% csrf_token %} 19 | 20 |
21 | 26 |
27 | 28 |

29 | 30 |

31 |
32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /open_humans/templates/member/my-member-connections.html: -------------------------------------------------------------------------------- 1 | {% extends 'member/my-member-dashboard.html' %} 2 | 3 | {% block head_title %}Manage connections{% endblock %} 4 | 5 | {% block dashboard_main %} 6 |
7 |

Manage connections

8 | 9 | {% for label, connection in connections %} 10 |
11 | {{ connection.verbose_name }} 12 | 13 | Remove connection 15 |
16 | {% endfor %} 17 | 18 | {% for project_member in project_members %} 19 |
20 | {{ project_member.project.name }} 21 | 22 | Withdraw from project & revoke 24 | authorization 25 |
26 | {% endfor %} 27 |
28 | {% endblock dashboard_main %} 29 | -------------------------------------------------------------------------------- /open_humans/templates/member/my-member-source-data-files-delete.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %}Confirm deletion{% endblock %} 6 | 7 | {% block panel_content %} 8 |
9 |
10 |

Are you sure you want to remove all of the data files from 11 | {{ project.name }}?

12 |
13 |
14 | 15 |
16 |
17 |
18 | {% csrf_token %} 19 | 20 |

21 | 22 |

23 |
24 |
25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /open_humans/templates/member/my-member-study-grants-delete.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %}Confirm removal{% endblock %} 6 | 7 | {% block panel_content %} 8 |
9 |
10 |

Are you sure you want to remove the grant to 11 | {{ study_title }}?

12 |
13 |
14 | 15 |
16 |
17 |
18 | {% csrf_token %} 19 | 20 |

21 | 22 |

23 |
24 |
25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /open_humans/templates/pages/add-data.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | {% load utilities %} 5 | 6 | {% block head_title %}Home{% endblock %} 7 | 8 | {% block extra_js %} 9 | 10 | 11 | {% endblock %} 12 | 13 | {% block body_main %} 14 |
15 |

Add data

16 |

17 | Once you connect data sources, you can join projects that help 18 | you explore your data. You can also choose to donate your data 19 | to research and citizen science projects! 20 |

21 |
22 | 23 | {% for project in projects %} 24 | {% include 'partials/activity-panel.html' %} 25 | {% endfor %} 26 | 27 |
28 |
29 | 30 | {% endblock body_main %} 31 | -------------------------------------------------------------------------------- /open_humans/templates/pages/explore-share.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | {% load utilities %} 5 | 6 | {% block head_title %}Home{% endblock %} 7 | 8 | {% block extra_js %} 9 | 10 | 11 | {% endblock %} 12 | 13 | {% block body_main %} 14 |
15 |

Explore and share your data

16 |

17 | What can you do with your data? You can join these activities to 18 | explore your data or share it with research and citizen science. 19 |

20 |
21 | 22 | {% for project in projects %} 23 | 24 | {% include 'partials/activity-panel.html' %} 25 | 26 | {% endfor %} 27 | 28 |
29 |
30 | 31 | {% endblock body_main %} 32 | -------------------------------------------------------------------------------- /open_humans/templates/pages/grant_projects.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block head_title %}Grant Projects{% endblock %} 6 | 7 | {% block main %} 8 | 9 |

Grant Projects

10 |
11 | 12 |

This page shows all of the active project grants that were awarded. 13 | 14 | {% for project in grant_projects %} 15 | 16 |

{{project.name}}

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 | 48 |
Status{{project.status}}
Grant Date{{project.grant_date}}
Github URL{{project.github}}
Grantee Name{{project.grantee_name}}
Blog URL{{project.blog_url}}
Project Description{{project.project_desc}}
49 | 50 | {% endfor %} 51 | 52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /open_humans/templates/pages/self-research.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-bs4.html' %} 2 | 3 | {% block head_title %}Self Research{% endblock %} 4 | 5 | {% block main %} 6 |

This page contains extended information about the 7 | Keating 8 | Memorial Self Research activity run by Open Humans. 9 |

10 |
11 | 12 |
13 | {% endblock main %} -------------------------------------------------------------------------------- /open_humans/templates/panel.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load static %} 4 | 5 | {% block background_image %}background-color: #f8f8f8;{% endblock %} 6 | 7 | {# override messages_block #} 8 | {% block messages_block %}{% endblock %} 9 | 10 | {% block main %} 11 |
12 |
13 |
14 |
15 | {% if messages %} 16 | {% for message in messages %} 17 |
{{ message }}
19 | {% endfor %} 20 | {% endif %} 21 | 22 | {% block head_title %}{% endblock %} 23 |
24 | 25 | {% block panel_content %}{% endblock %} 26 |
27 |
28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /open_humans/templates/partials/activity-info-short-bs4.html: -------------------------------------------------------------------------------- 1 |
2 |
Managed by:
3 |
4 | {{ project.leader }} 5 | {% if project.organization %} 6 |
{{ project.organization}} 7 | {% endif %} 8 |
9 |
-------------------------------------------------------------------------------- /open_humans/templates/partials/activity-management-join-add-button.html: -------------------------------------------------------------------------------- 1 | 8 | {{ connect_prefix|add:activity.connect_verb|title }} {{ activity.verbose_name }} 9 | 10 | -------------------------------------------------------------------------------- /open_humans/templates/partials/activity-management-sharing-opportunities.html: -------------------------------------------------------------------------------- 1 | {% load utilities %} 2 | 3 |
4 | 5 |

Sharing Opportunities

6 | 7 |

8 | Data from this data source can be shared with the following projects: 9 |

10 | 11 |
    12 | {% for requesting_activity in requesting_activities %} 13 |
  • 14 | 15 | {{ requesting_activity.name }}: 16 | 17 | 18 | {{ requesting_activity.authorized_members }} 19 | member{{ requesting_activity.authorized_members|pluralize }} 20 | 21 | {% project_is_connected requesting_activity user as connected %} 22 | {% if connected %} 23 | (joined) 24 | {% endif %} 25 |
  • 26 | {% endfor %} 27 |
28 | -------------------------------------------------------------------------------- /open_humans/templates/partials/activity-panel-needs.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | {% load utilities %} 3 | {# the "needs" panel on an activity page #} 4 |
5 |
-------------------------------------------------------------------------------- /open_humans/templates/partials/activity-panel-news.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | {% load utilities %} 3 | {# the "news" panel on an activity page #} 4 |
5 |
-------------------------------------------------------------------------------- /open_humans/templates/partials/activity-permissions-bs4.html: -------------------------------------------------------------------------------- 1 | {% if requests_permissions %} 2 |
3 |
4 | Requested permissions: 5 |
6 |
7 | {% for source_project in project.requested_sources.all %} 8 | 9 | {{ source_project.name }}
10 | {% endfor %} 11 | {% if project.request_username_access %} 12 | Username 13 | {% endif %} 14 |
15 |
16 | {% endif %} 17 | {% if project.returned_data_description %} 18 |
19 |
20 | Uploaded data: 21 |
22 |
23 | {{ project.returned_data_description }} 24 |
25 |
26 | {% endif %} 27 | -------------------------------------------------------------------------------- /open_humans/templates/partials/connection-activity.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ source.verbose_name }} 4 |
5 | 6 |
7 |
8 | 9 |
10 | {% if not user_data.is_connected %} 11 |
12 |
13 | Are you a {{ source.verbose_name }} user? 14 |
15 | 16 | 21 |
22 | 23 | {% else %} 24 |
25 |
26 | Perform a new data import? 27 |
28 | 29 |
30 |
32 | {% csrf_token %} 33 | 34 | 36 |
37 |
38 |
39 | 40 | {% endif %} 41 |
42 |
43 | -------------------------------------------------------------------------------- /open_humans/templates/partials/my-member-data-source.html: -------------------------------------------------------------------------------- 1 | {% load data_import %} 2 | {% load utilities %} 3 | 4 | 5 | {% source_is_public project.id_label as is_public %} 6 | {% include 'partials/public-sharing-button.html' %} 7 |

8 | 9 | {{ project.name }} 10 | 11 |

12 | 13 | {% if data_files %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% for data_file in data_files|dictsort:'basename' %} 25 | 26 | 30 | 31 | 34 | 35 | 38 | 39 | 42 | 43 | {% endfor %} {# data_file in data_files #} 44 |
FileSizeDownloadsDescription
27 | {{ data_file.basename }} 28 | [Download] 29 | 32 | {{ data_file.size|filesizeformat }} 33 | 36 | {{ data_file.access_logs.count }} 37 | 40 | {{ data_file.description }} 41 |
45 | {% else %} 46 |

There are not currently any files associated with this source.

47 | {% endif %} 48 | -------------------------------------------------------------------------------- /open_humans/templates/partials/project-grants-blurb.html: -------------------------------------------------------------------------------- 1 |

Project Grants

2 | 3 |

4 | Do you have an idea for a project that will help grow the Open Humans 5 | ecosystem? 6 |

7 |

8 | You might qualify for a grant of up to $5000 supporting your work! 9 |

10 | Learn more about project grants 12 | 13 | -------------------------------------------------------------------------------- /open_humans/templates/partials/project-in-development.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /open_humans/templates/partials/source-in-development.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | This source is in development. Your data will help us develop 4 | data processing! After this is in place, we'll produce files from your data. 5 | Please email 6 | support@openhumans.org 7 | with suggestions or issues. 8 |

9 |
10 | -------------------------------------------------------------------------------- /open_humans/templates/partials/upload-activity.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ source.verbose_name }} 4 |
5 | 6 |
7 |
8 | 9 |
10 | {% if not user_data.is_connected %} 11 |
12 |
13 | Are you a {{ source.verbose_name }} user? 14 |
15 | 16 | 21 |
22 | 23 | {% else %} 24 |
25 |
26 | Update your {{ source.verbose_name }} data? 27 |
28 | 29 |
30 |
32 | {% csrf_token %} 33 | 34 | 36 |
37 | 39 | Upload {{ source.verbose_name }} data again 40 | 41 |
42 |
43 | 44 | {% endif %} 45 |
46 |
47 | -------------------------------------------------------------------------------- /open_humans/templates/partials/visible-public-sharing.html: -------------------------------------------------------------------------------- 1 |
2 | {% if show_toggle_visible_button %} 3 |
4 | {% csrf_token %} 5 | 6 | 7 | 8 | {% if is_visible %} 9 | 10 | {% else %} 11 | 12 | {% endif %} 13 | 14 | 21 |
22 | {% endif %} {# if show_toggle_visible_button #} 23 |
24 | -------------------------------------------------------------------------------- /open_humans/templates/scopes/scope-container.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/open_humans/templates/scopes/scope-container.html -------------------------------------------------------------------------------- /open_humans/templates/socialaccount/authentication_error.html: -------------------------------------------------------------------------------- 1 | {% extends "panel.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Social Network Login Failure" %}{% endblock %} 6 | 7 | {% block panel_content %} 8 |

{% trans "Social Network Login Failure" %}

9 | 10 |

{% trans "An error occurred while attempting to login via your social network account." %}

11 | 12 | {% comment %} 13 |

14 | Auth error: {{ auth_error }} 15 | Provider: {{ auth_error.provider }}
16 | Code: {{ auth_error.code }}
17 | Exception: {{ auth_error.exception }} 18 |

19 | {% endcomment %} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /open_humans/templates/socialaccount/login_cancelled.html: -------------------------------------------------------------------------------- 1 | {% extends "panel.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Login Cancelled" %}{% endblock %} 6 | 7 | {% block panel_content %} 8 | 9 |

{% trans "Login Cancelled" %}

10 | 11 | {% url 'account_login' as login_url %} 12 | 13 |

{% blocktrans %}You decided to cancel logging in to our site using one of your existing accounts. If this was a mistake, please proceed to sign in.{% endblocktrans %}

14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /open_humans/templates/socialaccount/messages/account_connected.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}The social account has been connected.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /open_humans/templates/socialaccount/messages/account_connected_other.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}The social account is already connected to a different account.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /open_humans/templates/socialaccount/messages/account_disconnected.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% blocktrans %}The social account has been disconnected.{% endblocktrans %} 3 | -------------------------------------------------------------------------------- /open_humans/templates/socialaccount/snippets/login_extra.html: -------------------------------------------------------------------------------- 1 | {% load socialaccount %} 2 | -------------------------------------------------------------------------------- /open_humans/templates/socialaccount/snippets/provider_list.html: -------------------------------------------------------------------------------- 1 | {% load socialaccount %} 2 | 3 | {% get_providers as socialaccount_providers %} 4 | 5 | {% for provider in socialaccount_providers %} 6 | {% if provider.id == "openid" %} 7 | {% for brand in provider.get_brands %} 8 |
  • 9 | {{brand.name}} 13 |
  • 14 | {% endfor %} 15 | {% endif %} 16 |
  • 17 | {{provider.name}} 19 |
  • 20 | {% endfor %} 21 | -------------------------------------------------------------------------------- /open_humans/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/open_humans/templatetags/__init__.py -------------------------------------------------------------------------------- /open_humans/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for open_humans project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/ 8 | """ 9 | 10 | import logging 11 | import os 12 | 13 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "open_humans.settings") 14 | 15 | # pylint: disable=wrong-import-position 16 | from django.conf import settings # noqa 17 | from django.core.cache.backends.memcached import BaseMemcachedCache # noqa 18 | from django.core.wsgi import get_wsgi_application # noqa 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | logger.info("WSGI application starting") 23 | 24 | logger.info("DEBUG: %s", settings.DEBUG) 25 | logger.info("OAUTH2_DEBUG: %s", settings.OAUTH2_DEBUG) 26 | logger.info("LOG_EVERYTHING: %s", settings.LOG_EVERYTHING) 27 | 28 | # Fix django closing connection to MemCachier after every request (#11331) 29 | BaseMemcachedCache.close = lambda self, **kwargs: None 30 | 31 | application = get_wsgi_application() 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-humans", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "description": "Open Humans", 6 | "main": "gulpfile.js", 7 | "author": "Personal Genomes Org", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/OpenHumans/open-humans.git" 11 | }, 12 | "engines": { 13 | "node": "6.x", 14 | "npm": "3.x" 15 | }, 16 | "dependencies": { 17 | "autoprefixer": "^6.4.1", 18 | "bootstrap": "^3.3.7", 19 | "browserify": "^13.1.0", 20 | "drmonty-garlicjs": "^1.2.3", 21 | "gulp": "^4.0.2", 22 | "gulp-cssnano": "^2.1.2", 23 | "gulp-eslint": "^3.0.1", 24 | "gulp-jscs": "^4.0.0", 25 | "gulp-load-plugins": "^1.3.0", 26 | "gulp-postcss": "^6.2.0", 27 | "gulp-shell": "^0.5.2", 28 | "gulp-sourcemaps": "^1.6.0", 29 | "gulp-uglify": "^2.0.0", 30 | "gulp-util": "^3.0.7", 31 | "js-cookie": "^2.1.3", 32 | "js-yaml": "^3.6.1", 33 | "lodash": "^4.16.0", 34 | "markdown": "^0.5.0", 35 | "postcss-color-function": "^2.0.1", 36 | "postcss-reporter": "^1.4.1", 37 | "precss": "^1.4.0", 38 | "pretty-hrtime": "^1.0.2", 39 | "rimraf": "^2.5.4", 40 | "select2": "^4.0.5", 41 | "through2": "^2.0.1", 42 | "vinyl-buffer": "^1.0.0", 43 | "watchify": "^3.7.0", 44 | "webshim": "^1.15.10" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /private_sharing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/private_sharing/__init__.py -------------------------------------------------------------------------------- /private_sharing/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from . import models 4 | 5 | 6 | class DataRequestProjectMemberAdmin(admin.ModelAdmin): 7 | """ 8 | Display and make the 'created' field read-only in the admin interface. 9 | """ 10 | 11 | readonly_fields = ("created",) 12 | search_fields = ("member__user__username", "project_member_id", "project__name") 13 | raw_id_fields = ("member",) 14 | 15 | 16 | class DataRequestProjectAdmin(admin.ModelAdmin): 17 | """ 18 | set the coordinator field to be raw_id 19 | """ 20 | 21 | raw_id_fields = ("coordinator",) 22 | 23 | 24 | admin.site.register(models.ProjectDataFile) 25 | admin.site.register(models.DataRequestProject, DataRequestProjectAdmin) 26 | admin.site.register(models.OAuth2DataRequestProject, DataRequestProjectAdmin) 27 | admin.site.register(models.OnSiteDataRequestProject, DataRequestProjectAdmin) 28 | admin.site.register(models.DataRequestProjectMember, DataRequestProjectMemberAdmin) 29 | admin.site.register(models.FeaturedProject) 30 | -------------------------------------------------------------------------------- /private_sharing/api_filter_backends.py: -------------------------------------------------------------------------------- 1 | from rest_framework.filters import BaseFilterBackend 2 | 3 | 4 | class ProjectFilterBackend(BaseFilterBackend): 5 | """ 6 | Filter that only allows users to see their own objects. 7 | """ 8 | 9 | def filter_queryset(self, request, queryset, view): 10 | return queryset.filter(project=request.auth) 11 | -------------------------------------------------------------------------------- /private_sharing/api_permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | 3 | 4 | class HasValidProjectToken(BasePermission): 5 | """ 6 | Return True if the request has a valid project token. 7 | """ 8 | 9 | def has_permission(self, request, view): 10 | return bool(request.auth) 11 | -------------------------------------------------------------------------------- /private_sharing/api_urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from rest_framework.urlpatterns import format_suffix_patterns 4 | 5 | from . import api_views 6 | 7 | urlpatterns = [ 8 | path("project/", api_views.ProjectDataView.as_view()), 9 | path("project/members/", api_views.ProjectMemberDataView.as_view()), 10 | path( 11 | "project/exchange-member/", 12 | api_views.ProjectMemberExchangeView.as_view(), 13 | name="exchange-member", 14 | ), 15 | path("project/message/", api_views.ProjectMessageView.as_view()), 16 | path("project/remove-members/", api_views.ProjectRemoveMemberView.as_view()), 17 | # Views for managing uploaded data files 18 | path("project/files/upload/", api_views.ProjectFileUploadView.as_view()), 19 | path("project/files/delete/", api_views.ProjectFileDeleteView.as_view()), 20 | path( 21 | "project/files/upload/direct/", api_views.ProjectFileDirectUploadView.as_view() 22 | ), 23 | path( 24 | "project/files/upload/complete/", 25 | api_views.ProjectFileDirectUploadCompletionView.as_view(), 26 | ), 27 | ] 28 | 29 | urlpatterns = format_suffix_patterns(urlpatterns) 30 | -------------------------------------------------------------------------------- /private_sharing/migrations/0002_add_project_data_file.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10 on 2016-08-01 23:15 3 | 4 | import django.contrib.postgres.fields 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('private_sharing', '0001_squashed_0034_auto_20160727_2138'), 15 | ('data_import', '0001_squashed_0020_auto_20160729_1632'), 16 | ] 17 | 18 | operations = [ 19 | 20 | migrations.CreateModel( 21 | name='ProjectDataFile', 22 | fields=[ 23 | ('parent', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name='parent_project_data_file', serialize=False, to='data_import.DataFile')), 24 | ('direct_sharing_project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='private_sharing.DataRequestProject')), 25 | ], 26 | bases=('data_import.datafile',), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /private_sharing/migrations/0004_projectdatafile_completed.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-09-26 17:16 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('private_sharing', '0003_auto_20160909_0427'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='projectdatafile', 16 | name='completed', 17 | field=models.BooleanField(default=False), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /private_sharing/migrations/0005_auto_20160926_1717.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-09-26 17:17 3 | 4 | from django.db import migrations 5 | 6 | 7 | def migrate_completed(apps, *args): 8 | ProjectDataFile = apps.get_model('private_sharing', 'ProjectDataFile') 9 | 10 | ProjectDataFile.objects.all().update(completed=True) 11 | 12 | 13 | class Migration(migrations.Migration): 14 | 15 | dependencies = [ 16 | ('private_sharing', '0004_projectdatafile_completed'), 17 | ] 18 | 19 | operations = [ 20 | migrations.RunPython(migrate_completed), 21 | ] 22 | -------------------------------------------------------------------------------- /private_sharing/migrations/0006_auto_20161102_1932.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-11-02 19:32 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('private_sharing', '0005_auto_20160926_1717'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='datarequestproject', 16 | name='info_url', 17 | field=models.URLField(blank=True, verbose_name='URL for general information about your project'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /private_sharing/migrations/0008_featuredproject.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2018-01-05 01:20 3 | 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('private_sharing', '0007_auto_20171220_2038'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='FeaturedProject', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('description', models.TextField(blank=True)), 20 | ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='private_sharing.DataRequestProject')), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /private_sharing/migrations/0009_auto_20180317_2209.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2018-03-17 22:09 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('private_sharing', '0008_featuredproject'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='datarequestproject', 16 | name='all_sources_access', 17 | field=models.BooleanField(default=False), 18 | ), 19 | migrations.AddField( 20 | model_name='datarequestprojectmember', 21 | name='all_sources_shared', 22 | field=models.BooleanField(default=False), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /private_sharing/migrations/0010_datarequestprojectmember_visible.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.7 on 2018-08-15 21:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('private_sharing', '0009_auto_20180317_2209'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='datarequestprojectmember', 15 | name='visible', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /private_sharing/migrations/0012_datarequestproject_approval_history.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.3 on 2019-01-11 20:38 2 | 3 | import django.contrib.postgres.fields 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('private_sharing', '0011_auto_20180912_2206'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='datarequestproject', 16 | name='approval_history', 17 | field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=32), size=2), default=list, editable=False, size=None), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /private_sharing/migrations/0013_auto_20190121_2142.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.3 on 2019-01-21 21:42 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('private_sharing', '0012_datarequestproject_approval_history'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='datarequestproject', 15 | name='add_data', 16 | field=models.BooleanField(default=False, help_text='If your project collects data, choose "Add data" here. If you choose "Add data", you will need to provide a "Returned data description" below.', verbose_name='Add data'), 17 | ), 18 | migrations.AddField( 19 | model_name='datarequestproject', 20 | name='explore_share', 21 | field=models.BooleanField(default=False, help_text='If your project performs analysis on data, choose "Explore & share".', verbose_name='Explore & share'), 22 | ), 23 | migrations.AlterField( 24 | model_name='datarequestproject', 25 | name='returned_data_description', 26 | field=models.CharField(blank=True, help_text='Leave this blank if your project doesn\'t plan to add or return new data for your members. If your project is set to be displayed under "Add data", then you must provide this information.', max_length=140, verbose_name='Description of data you plan to upload to member accounts (140 characters max)'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /private_sharing/migrations/0014_datarequestproject_no_public_data.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.3 on 2019-01-21 22:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('private_sharing', '0013_auto_20190121_2142'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='datarequestproject', 15 | name='no_public_data', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /private_sharing/migrations/0015_auto_20190124_1849.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-01-24 18:49 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('private_sharing', '0014_datarequestproject_no_public_data'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='datarequestproject', 15 | name='request_message_permission', 16 | ), 17 | migrations.RemoveField( 18 | model_name='datarequestprojectmember', 19 | name='message_permission', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /private_sharing/migrations/0016_auto_20190128_2111.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-01-28 21:11 2 | 3 | import django.contrib.postgres.fields 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('private_sharing', '0015_auto_20190124_1849'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='datarequestprojectmember', 16 | name='last_authorized', 17 | field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=32), size=2), default=list, editable=False, size=None), 18 | ), 19 | migrations.AddField( 20 | model_name='datarequestprojectmember', 21 | name='last_joined', 22 | field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=32), size=2), default=list, editable=False, size=None), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /private_sharing/migrations/0017_auto_20190212_0511.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-02-12 05:11 2 | 3 | import django.contrib.postgres.fields 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('private_sharing', '0016_auto_20190128_2111'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='datarequestproject', 16 | options={'ordering': ['name']}, 17 | ), 18 | migrations.AddField( 19 | model_name='datarequestproject', 20 | name='requested_sources', 21 | field=models.ManyToManyField(related_name='requesting_projects', to='private_sharing.DataRequestProject'), 22 | ), 23 | migrations.AddField( 24 | model_name='datarequestprojectmember', 25 | name='granted_sources', 26 | field=models.ManyToManyField(to='private_sharing.DataRequestProject'), 27 | ), 28 | migrations.AlterField( 29 | model_name='datarequestproject', 30 | name='request_sources_access', 31 | field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), blank=True, default=list, help_text='List of sources this project is requesting access to on Open Humans.', size=None), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /private_sharing/migrations/0018_oauth2datarequestproject_terms_url.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-02-12 21:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('private_sharing', '0017_auto_20190212_0511'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='oauth2datarequestproject', 15 | name='terms_url', 16 | field=models.URLField(default='', help_text='The URL for your "terms of use" policy.', verbose_name='Terms of Use URL'), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /private_sharing/migrations/0019_auto_20190214_1915.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-02-14 19:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('private_sharing', '0018_oauth2datarequestproject_terms_url'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='oauth2datarequestproject', 15 | name='deauth_webhook', 16 | field=models.URLField(blank=True, default='', help_text="The URL to send a POST to when a member\n requests data erasure. This request will be in the form\n of JSON,\n { 'project_member_id': '12345678', 'erasure_requested': True}", max_length=256, verbose_name='Deauthorization Webhook URL'), 17 | ), 18 | migrations.AlterField( 19 | model_name='oauth2datarequestproject', 20 | name='terms_url', 21 | field=models.URLField(help_text='The URL for your "terms of use" (or "terms of service").', verbose_name='Terms of Use URL'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /private_sharing/migrations/0020_auto_20190222_0036.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-02-22 00:36 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("private_sharing", "0019_auto_20190214_1915")] 9 | 10 | operations = [ 11 | migrations.RemoveField( 12 | model_name="datarequestproject", name="request_sources_access" 13 | ), 14 | migrations.RemoveField( 15 | model_name="datarequestprojectmember", name="sources_shared" 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /private_sharing/migrations/0021_auto_20190412_1908.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2019-04-12 19:08 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("data_import", "0019_datatype"), 10 | ("private_sharing", "0020_auto_20190222_0036"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="datarequestproject", 16 | name="auto_add_datatypes", 17 | field=models.BooleanField(default=False), 18 | ), 19 | migrations.AddField( 20 | model_name="datarequestproject", 21 | name="registered_datatypes", 22 | field=models.ManyToManyField(to="data_import.DataType"), 23 | ), 24 | migrations.AddField( 25 | model_name="projectdatafile", 26 | name="datatypes", 27 | field=models.ManyToManyField(to="data_import.DataType"), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /private_sharing/migrations/0022_auto_20190507_1843.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-07 18:43 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("private_sharing", "0021_auto_20190412_1908")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="projectdatafile", 14 | name="direct_sharing_project", 15 | field=models.ForeignKey( 16 | on_delete=django.db.models.deletion.PROTECT, 17 | to="private_sharing.DataRequestProject", 18 | ), 19 | ) 20 | ] 21 | -------------------------------------------------------------------------------- /private_sharing/migrations/0023_auto_20190528_1826.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-28 18:26 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("open_humans", "0014_member_password_reset_redirect"), 10 | ("private_sharing", "0022_auto_20190507_1843"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterUniqueTogether( 15 | name="datarequestprojectmember", unique_together={("member", "project")} 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /private_sharing/migrations/0024_auto_20190716_0504.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-07-16 05:04 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("private_sharing", "0023_auto_20190528_1826")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="datarequestproject", 13 | name="registered_datatypes", 14 | field=models.ManyToManyField( 15 | blank=True, related_name="source_projects", to="data_import.DataType" 16 | ), 17 | ), 18 | migrations.AlterField( 19 | model_name="datarequestproject", 20 | name="requested_sources", 21 | field=models.ManyToManyField( 22 | blank=True, 23 | related_name="requesting_projects", 24 | to="private_sharing.DataRequestProject", 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /private_sharing/migrations/0025_datarequestproject_any_datatypes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.3 on 2019-11-04 22:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("private_sharing", "0024_auto_20190716_0504")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="datarequestproject", 13 | name="any_datatypes", 14 | field=models.BooleanField(default=False), 15 | ) 16 | ] 17 | -------------------------------------------------------------------------------- /private_sharing/migrations/0026_auto_20191202_2105.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.7 on 2019-12-02 21:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("private_sharing", "0025_datarequestproject_any_datatypes")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="datarequestproject", 13 | name="review_url", 14 | field=models.URLField( 15 | blank=True, verbose_name="URL for project approval review" 16 | ), 17 | ), 18 | migrations.AlterField( 19 | model_name="datarequestprojectmember", 20 | name="granted_sources", 21 | field=models.ManyToManyField( 22 | related_name="granted_sources", to="private_sharing.DataRequestProject" 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /private_sharing/migrations/0027_oauth2datarequestproject_webhook_secret.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.10 on 2020-07-01 21:05 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("private_sharing", "0026_auto_20191202_2105")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="oauth2datarequestproject", 14 | name="webhook_secret", 15 | field=models.CharField( 16 | blank=True, 17 | help_text="If entered, this string will be used to provide a hash verifying Open Humans as the sender.", 18 | max_length=64, 19 | validators=[ 20 | django.core.validators.RegexValidator(regex="[\x00-\x7f]*"), 21 | django.core.validators.MinLengthValidator(16), 22 | ], 23 | ), 24 | ) 25 | ] 26 | -------------------------------------------------------------------------------- /private_sharing/migrations/0028_datarequestproject_jogl_page.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.10 on 2020-07-16 22:52 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [("private_sharing", "0027_oauth2datarequestproject_webhook_secret")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="datarequestproject", 13 | name="jogl_page", 14 | field=models.URLField( 15 | blank=True, help_text="JOGL project page URL (optional)" 16 | ), 17 | ) 18 | ] 19 | -------------------------------------------------------------------------------- /private_sharing/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/private_sharing/migrations/__init__.py -------------------------------------------------------------------------------- /private_sharing/static/images/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/private_sharing/static/images/badge.png -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/layout.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load static %} 4 | 5 | {% block head_title %}Direct sharing{% endblock %} 6 | 7 | {% block main %} 8 |
    9 |
    10 |
    11 | 14 |
    15 | 16 | 21 |
    22 | 23 |
    24 | {% block content %}{% endblock %} 25 |
    26 |
    27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/oauth2-data-access.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 |

    OAuth2 data access

    5 | 6 |

    7 | Once a member authorizes data sharing, you'll be able to immediately access 8 | that data via our API. 9 |

    10 | 11 |

    12 | Topics: 13 |

    14 | 25 | 26 | {% include 'direct-sharing/partials/data-access.html' with oauth2_project=True %} 27 | 28 | {% include 'direct-sharing/partials/project-info.html' with oauth2_project=True %} 29 | 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/oauth2-data-upload.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 |

    OAuth2 Data Upload

    5 | 6 | {% include 'direct-sharing/partials/data-upload.html' with oauth2_project=True %} 7 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/oauth2-features.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 |

    OAuth2 features

    5 | 6 |

    When is an "OAuth2" project appropriate?

    7 | 8 |

    9 | "OAuth2 authorization" may be appropriate if... 10 |

    11 | 12 | {% include 'direct-sharing/partials/oauth2-appropriate-if.html' %} 13 | 14 |

    Features

    15 |

    16 | Here's a summary of main features: 17 |

    18 | 19 | {% include 'direct-sharing/partials/features.html' with oauth2_project=True %} 20 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/oauth2-member-removal.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 | 5 |

    OAuth2 member removal

    6 | 7 |

    8 | Topics: 9 |

    10 | 27 | 28 | {% include 'direct-sharing/partials/member-removal.html' with oauth2_project=True %} 29 | 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/oauth2-messages.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 | 5 |

    OAuth2 messages

    6 | 7 |

    8 | Topics: 9 |

    10 | 27 | 28 | {% include 'direct-sharing/partials/messages.html' with oauth2_project=True %} 29 | 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/on-site-data-access.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 |

    On-site data access

    5 | 6 |

    7 | Once a member authorizes data sharing, you'll be able to immediately access 8 | that data via our API. 9 |

    10 | 11 |

    12 | Topics: 13 |

    14 | 28 | 29 | {% include 'direct-sharing/partials/data-access.html' with on_site_project=True %} 30 | 31 | {% include 'direct-sharing/partials/project-info.html' with on_site_project=True %} 32 | 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/on-site-data-upload.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 |

    On-site Data Upload

    5 | 6 | {% include 'direct-sharing/partials/data-upload.html' with on_site_project=True %} 7 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/on-site-features.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 |

    On-site features

    5 | 6 |

    When is an "on-site" project appropriate?

    7 | 8 |

    9 | "On-site authorization" may be appropriate if... 10 |

    11 |
      12 |
    • You just want something simple.
    • 13 |
    • You have limited technical resources.
    • 14 |
    • You are NOT running an accounts-based website or phone app.
    • 15 |
    16 | 17 |

    Features

    18 |

    19 | Here's a summary of main features: 20 |

    21 | 22 | {% include 'direct-sharing/partials/features.html' with on_site_project=True %} 23 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/on-site-member-removal.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 | 5 |

    On-site member removal

    6 | 7 |

    8 | Topics: 9 |

    10 | 24 | 25 | {% include 'direct-sharing/partials/member-removal.html' with on_site_project=True %} 26 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/on-site-messages.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 | 5 |

    On-site messages

    6 | 7 |

    8 | Topics: 9 |

    10 | 24 | 25 | {% include 'direct-sharing/partials/messages.html' with on_site_project=True %} 26 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/on-site-setup.html: -------------------------------------------------------------------------------- 1 | {% extends 'direct-sharing/layout.html' %} 2 | 3 | {% block content %} 4 |

    On-site setup

    5 | 6 |

    7 | Topics: 8 |

    9 | 20 | 21 | {% include 'direct-sharing/partials/setup.html' with on_site_project=True %} 22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/partials/about-master-token.html: -------------------------------------------------------------------------------- 1 |

    2 | Each project has a "master access token" used for API calls. 3 | This token is a password for your project. 4 |

    5 | 6 |

    7 | To find the token, your 8 | project management page and click on your project's name. The master 9 | access token should be listed in your project's details. 10 |

    11 | 12 |

    13 | Keep this token PRIVATE.
    14 |

    15 | 16 |
      17 |
    • Do NOT publicly share this token.
    • 18 |
    • Do NOT share this token in an unsecured manner.
    • 19 |
    • NEVER put this token into a git repository.
    • 20 |
    21 | 22 |

    23 | This token is used to authorize the following: 24 |

    25 |
      26 |
    • API access to any private data shared with your project
    • 27 |
    • sending messages to project members via API
    • 28 |
    • uploading data for project members via API
    • 29 |
    30 | 31 |

    32 | If you ever believe the security of this token may have been compromised, 33 | contact us at support@openhumans.org and we'll reset it to a new 34 | value. 35 |

    36 | 37 |

    38 | When using this token in programs, we recommend you do NOT store it. 39 | You should enter the token each time you run your software. 40 |

    41 | 42 |

    43 | If you want to have fully automated API transactions with Open Humans, you 44 | should use OAuth2 endpoints with user-specific access tokens. 45 |

    46 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/partials/oauth2-appropriate-if.html: -------------------------------------------------------------------------------- 1 |
      2 |
    • You're running an accounts-based website or phone app.
    • 3 |
    • You have a pre-existing project.
    • 4 |
    • You're technically inclined.
    • 5 |
    6 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/partials/on-site-appropriate-if.html: -------------------------------------------------------------------------------- 1 |
      2 |
    • You just want something simple.
    • 3 |
    • You have limited technical resources.
    • 4 |
    • You're NOT running a phone app or website with accounts.
    • 5 |
    6 | -------------------------------------------------------------------------------- /private_sharing/templates/direct-sharing/partials/project-info.html: -------------------------------------------------------------------------------- 1 |

    API data access: Project information

    2 | 3 |

    4 | {% if oauth2_project %}OAuth2 and master access tokens can also both{% else %} 5 | Master access tokens can also{% endif %} be used to retrieve information about 6 | the project they are related to. 7 |

    8 | 9 |

    10 | To use this endpoint, send a secure GET request (using 'https') to the following 11 | URL with the 'access_token' parameter set to access token: 12 |

    13 |

    14 | https://www.openhumans.org/api/direct-sharing/project/?access_token=<ACCESS_TOKEN> 15 |

    16 | 17 |

    18 | This returns JSON-formatted data related to the project itself. 19 |

    20 | -------------------------------------------------------------------------------- /private_sharing/templates/email/notify-withdrawal.html: -------------------------------------------------------------------------------- 1 |

    2 | Hello, 3 |

    4 | 5 |

    6 | This email is to inform you that a member, {{ project.project_member.project_member_id }}, 7 | has withdrawn their participation from your project. 8 |

    9 |

    10 | For your convenience, we maintain a list of project members that have requested 11 | withdrawal. It can be found at {{ withdrawn_url }}. 12 |

    13 | {% if erasure_requested %} 14 |

    15 | In addition, they have requested erasure of their data. We have 16 | deleted the data on our end, and hereby request that you do the same. 17 |

    18 | {% endif %} 19 |

    20 | Questions? Email us at 21 | support@openhumans.org. 22 |

    23 |

    24 | Thank you for being a member of our community! 25 |

    26 | 27 |

    28 | The Open Humans Team 29 |

    30 | 31 |

    32 | (0) {{ withdrawn_data }} 33 |

    34 | -------------------------------------------------------------------------------- /private_sharing/templates/email/notify-withdrawal.txt: -------------------------------------------------------------------------------- 1 | Hello, 2 | 3 | This email is to inform you that a member, {{ project.project_member.project_member_id }}, 4 | has withdrawn their participation from your project. 5 | 6 | For your convenience, we maintain a list of project members that have requested 7 | withdrawal. It can be found at {{ withdrawn_url }}. 8 | 9 | {% if erasure_requested %} 10 | In addition, they have requested erasure of their data. We have 11 | deleted the data on our end, and hereby request that you do the same. 12 | 13 | {% endif %} 14 | Questions? Email us at support@openhumans.org 15 | 16 | Thank you for being a member of our community! 17 | 18 | The Open Humans Team 19 | -------------------------------------------------------------------------------- /private_sharing/templates/email/project-message.txt: -------------------------------------------------------------------------------- 1 | {{ message }} 2 | 3 | ------------------------------------------------------------------------------- 4 | 5 | This email was sent by Open Humans project "{{ project }}" to "{{ username }}". 6 | It has been automatically delivered. The project has not been given your email 7 | address. You have no obligation to reply to this email or take any other action 8 | that might disclose your identity. Open Humans did not review nor retain a copy 9 | of this message’s contents and is not responsible for material sent by this 10 | project. You should retain a copy of this message if you may need it in the 11 | future. 12 | 13 | If you wish to respond using your Project Member ID (i.e. not reveal your 14 | personal email) you can use this form: 15 | <{{ project_message_form }}> 16 | 17 | If you respond directly, the project will know your email address. Your email 18 | will go directly to the project's email address, this communication is not 19 | managed by Open Humans. 20 | 21 | When you authorized this project, you granted permission for it to send you 22 | messages. You may deauthorize this project by visiting the project management 23 | page: <{{ activity_management_url }}>. 24 | 25 | Please report abuses of messaging to Open Humans at . 26 | -------------------------------------------------------------------------------- /private_sharing/templates/private_sharing/authorize-inactive.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% block head_title %} 4 | Authorization not possible 5 | {% endblock %} 6 | 7 | {% block panel_content %} 8 | 9 | Unfortunately, this project is not currently active. 10 |

    11 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /private_sharing/templates/private_sharing/authorize-oauth2.html: -------------------------------------------------------------------------------- 1 | {% extends 'private_sharing/authorize-base.html' %} 2 | 3 | {% block form_content %} 4 |
    5 | {% csrf_token %} 6 | 7 | {% for field in form %} 8 | {% if field.is_hidden %} 9 | {{ field }} 10 | {% endif %} 11 | {% endfor %} 12 | 13 | {{ form.errors }} 14 | {{ form.non_field_errors }} 15 | 16 |

    17 | 21 | 22 | If hidden, the project's badge will not appear on your public 23 | profile. 24 | 25 |

    26 | 27 |
    28 | 29 |

    30 | 32 | 33 | 35 |

    36 |
    37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /private_sharing/templates/private_sharing/authorize-on-site.html: -------------------------------------------------------------------------------- 1 | {% extends 'private_sharing/authorize-base.html' %} 2 | 3 | {% block form_content %} 4 |
    6 | {% csrf_token %} 7 |
    8 | 9 |
    11 | 12 | 13 | {% csrf_token %} 14 |
    15 | 16 |

    17 | 21 | 22 | If hidden, the project's badge will not appear on your public 23 | profile. 24 | 25 |

    26 | 27 |
    28 | 29 |

    30 | 33 | 34 | 37 |

    38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /private_sharing/templates/private_sharing/create-project.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load static %} 5 | 6 | {% block head_title %}Create a project{% endblock %} 7 | 8 | {% block panel_content %} 9 |

    10 | The information you enter below initializes your project. It can all be 11 | edited later! Once initialized, you can start testing and improving your 12 | project. 13 |

    14 |
    15 |
    18 | {% csrf_token %} 19 | 20 | {% if form.non_field_errors %} 21 |
    22 |

    23 | {% for error in form.non_field_errors%} 24 | Error: {{ error }} 25 | {% endfor %} 26 |

    27 |
    28 | {% endif %} 29 | 30 | {{ form|as_bootstrap }} 31 | 32 | 33 | 36 |
    37 |
    38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /private_sharing/templates/private_sharing/join-on-site.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %}Join '{{ object.name }}'{% endblock %} 6 | 7 | {% block panel_content %} 8 | {% if not object.approved %} 9 | {% include 'partials/project-in-development.html' %} 10 | {% endif %} 11 | {% if object.project_type == 'activity' %} 12 |
    13 | Not a research study. This project is an activity, not a 14 | research study. As such, it has not been through the ethical review process a 15 | human subjects research study would normally undergo. The text below should 16 | describe how this activity plans to interact with you and/or your data. 17 |
    18 | {% endif %} 19 | 20 |
    21 | 24 |
    25 | 26 |
    28 | 29 | {% csrf_token %} 30 | 31 |

    32 | 35 | 36 | Decline 37 |

    38 |
    39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /private_sharing/templates/private_sharing/leave-project.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load utilities %} 4 | {% load private_sharing %} 5 | 6 | {% block head_title %}Confirm removal{% endblock %} 7 | 8 | {% block panel_content %} 9 |
    10 |
    11 |

    Are you sure you want to leave the project 12 | '{{ object.project.name }}'?

    13 |
    14 |
    15 | 16 |
    17 |
    18 |
    19 | {% csrf_token %} 20 | 21 |
    22 | 27 |
    28 | 29 | {% erasure_requested_checkbox object %} 30 | 31 |

    32 | 33 |

    34 |
    35 |
    36 |
    37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /private_sharing/templates/private_sharing/message-project-members.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load static %} 5 | 6 | {% block head_title %}Message project members{% endblock %} 7 | 8 | {% block panel_content %} 9 | 10 |

    Project: {{object.name}}

    11 | 12 |

    13 | Use the form below to message members of your project. 14 |

    15 | 16 |
    17 |
    19 | 20 | {% csrf_token %} 21 | 22 | {{ form|as_bootstrap }} 23 | 24 | 27 |
    28 |
    29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /private_sharing/templates/private_sharing/project-withdrawn-members-view.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load utilities %} 4 | 5 | {% block head_title %}Withdrawn project members{% endblock %} 6 | 7 | {% block main %} 8 | 9 |

    Members project members

    10 | 11 | Below you can find a listing of the project members that have left the 12 | project, and whether they have requested erasure of their data from 13 | "{{ object.name }}". 14 | 15 |
    16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% for project_member in object_list %} 28 | 29 | 30 | 33 | 36 | 37 | 38 | {% endfor %} 39 | 40 |
    Project Member IDErasure Requested
    31 | {{ project_member.project_member_id }} 32 | 34 | {% template_bool project_member.erasure_requested %} 35 |
    41 |

    42 | 43 | 44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /private_sharing/templates/private_sharing/remove-project-members.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load static %} 5 | 6 | {% block head_title %}{{ aoeueoa }}Remove project members{% endblock %} 7 | 8 | {% block panel_content %} 9 | 10 |

    Project: {{object.name}}

    11 | 12 |

    13 | CAUTION: this action cannot be reversed by you!
    14 | You can use the form below to remove members from your project. 15 |

    16 | 17 |
    18 |
    20 | 21 | {% csrf_token %} 22 | 23 | {{ form|as_bootstrap }} 24 | 25 | 28 |
    29 |
    30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /private_sharing/templates/private_sharing/update-project.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel.html' %} 2 | 3 | {% load bootstrap_tags %} 4 | {% load static %} 5 | 6 | {% block head_title %}Update a project{% endblock %} 7 | 8 | {% block panel_content %} 9 |

    10 | Use the form below to edit the information about your project. 11 |

    12 | 13 |
    14 |
    16 | {% csrf_token %} 17 | 18 | {{ form|as_bootstrap }} 19 | 20 | 23 |
    24 |
    25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /private_sharing/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/private_sharing/templatetags/__init__.py -------------------------------------------------------------------------------- /private_sharing/templatetags/private_sharing.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.utils.safestring import mark_safe 3 | 4 | from private_sharing.models import DataRequestProject 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.simple_tag() 10 | def erasure_requested_checkbox(object): 11 | """ 12 | If a Data Request Project supports member data erasure, then return the 13 | html to produce a checkbox to request this. 14 | """ 15 | html = """ 16 |
    17 | 22 |
    23 | """ 24 | erasure_supported = object.project.erasure_supported 25 | if erasure_supported == True: 26 | return mark_safe(str(html).format(object.project.name)) 27 | else: 28 | return "" 29 | -------------------------------------------------------------------------------- /public_data/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "public_data.apps.PublicDataConfig" 2 | -------------------------------------------------------------------------------- /public_data/admin.py: -------------------------------------------------------------------------------- 1 | # from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /public_data/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PublicDataConfig(AppConfig): 5 | """ 6 | Configure the public data application. 7 | """ 8 | 9 | name = "public_data" 10 | verbose_name = "Public Data" 11 | 12 | def ready(self): 13 | # Make sure our signal handlers get hooked up 14 | 15 | # pylint: disable=unused-variable 16 | import public_data.signals # noqa 17 | -------------------------------------------------------------------------------- /public_data/migrations/0002_auto_20171213_1947.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2017-12-13 19:47 3 | 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('public_data', '0001_squashed_0004_auto_20151230_0050'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='participant', 16 | name='enrollment_date', 17 | ), 18 | migrations.RemoveField( 19 | model_name='participant', 20 | name='signature', 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /public_data/migrations/0003_auto_20190508_2341.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-08 23:41 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("private_sharing", "0022_auto_20190507_1843"), 11 | ("public_data", "0002_auto_20171213_1947"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="publicdataaccess", 17 | name="project_membership", 18 | field=models.OneToOneField( 19 | null=True, 20 | on_delete=django.db.models.deletion.CASCADE, 21 | to="private_sharing.DataRequestProjectMember", 22 | ), 23 | ), 24 | migrations.AlterField( 25 | model_name="publicdataaccess", 26 | name="data_source", 27 | field=models.CharField(max_length=100, null=True), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /public_data/migrations/0005_auto_20190508_2342.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.1 on 2019-05-08 23:42 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [("public_data", "0004_migrate_data_20190508")] 10 | 11 | operations = [ 12 | migrations.RemoveField(model_name="publicdataaccess", name="data_source"), 13 | migrations.AlterField( 14 | model_name="publicdataaccess", 15 | name="project_membership", 16 | field=models.OneToOneField( 17 | on_delete=django.db.models.deletion.CASCADE, 18 | to="private_sharing.DataRequestProjectMember", 19 | ), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /public_data/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/public_data/migrations/__init__.py -------------------------------------------------------------------------------- /public_data/signals.py: -------------------------------------------------------------------------------- 1 | from django.db.models.signals import post_save 2 | from django.dispatch import receiver 3 | 4 | from .models import Participant 5 | 6 | 7 | @receiver(post_save, sender=Participant) 8 | def post_save_cb(sender, instance, created, raw, update_fields, **kwargs): 9 | """ 10 | Set all PublicDataAccess objects' is_public to false when a user leaves the 11 | public sharing study. 12 | """ 13 | # If the model was updated but not created or udpated as part of a fixture 14 | if raw or created: 15 | return 16 | 17 | if not instance.enrolled: 18 | for public_data_access in instance.publicdataaccess_set.all(): 19 | public_data_access.is_public = False 20 | public_data_access.save(update_fields=["is_public"]) 21 | -------------------------------------------------------------------------------- /public_data/static/docs/Consent_Document_20141212_(stamped).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/public_data/static/docs/Consent_Document_20141212_(stamped).pdf -------------------------------------------------------------------------------- /public_data/static/docs/Consent_Document_20160128_(stamped).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/public_data/static/docs/Consent_Document_20160128_(stamped).pdf -------------------------------------------------------------------------------- /public_data/static/docs/Research_Protocol_20141212.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/public_data/static/docs/Research_Protocol_20141212.pdf -------------------------------------------------------------------------------- /public_data/static/docs/Research_Protocol_20160128.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/public_data/static/docs/Research_Protocol_20160128.pdf -------------------------------------------------------------------------------- /public_data/static/images/public-data-sharing-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/public_data/static/images/public-data-sharing-badge.png -------------------------------------------------------------------------------- /public_data/templates/public_data/withdraw.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block main %} 4 |

    5 | By deactivating the "public data sharing" feature you will also mark all of 6 | your data files as private. 7 |

    8 | 9 |
    10 | {% csrf_token %} 11 | 12 | 13 |
    14 | {% endblock main %} 15 | -------------------------------------------------------------------------------- /public_data/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.decorators.http import require_POST 3 | 4 | from .views import ( 5 | ActivateOverviewView, 6 | ConsentView, 7 | HomeView, 8 | QuizView, 9 | ToggleSharingView, 10 | WithdrawView, 11 | ) 12 | 13 | app_name = "public-data" 14 | 15 | urlpatterns = [ 16 | path("", HomeView.as_view(), name="home"), 17 | # Enrollment process pages. User must be logged in to access. 18 | path( 19 | "activate-1-overview/", ActivateOverviewView.as_view(), name="enroll-overview" 20 | ), 21 | path("activate-2-information/", ConsentView.as_view(), name="enroll-information"), 22 | path("activate-3-quiz/", QuizView.as_view(), name="enroll-quiz"), 23 | path( 24 | "activate-4-complete/", 25 | require_POST(ConsentView.as_view()), 26 | name="enroll-signature", 27 | ), 28 | # Withdraw from the public data study 29 | path("deactivate/", WithdrawView.as_view(), name="deactivate"), 30 | # Data management 31 | path("toggle-sharing/", ToggleSharingView.as_view(), name="toggle-sharing"), 32 | ] 33 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | -e git+https://github.com/beaugunderson/django-storages-s3upload.git#egg=django-storages-s3upload 2 | 3 | # 20200507 Added to enable Apple OAuth from a feature PR in progress. 4 | -e git+https://github.com/gedankenstuecke/django-allauth.git@c9aa44fb22771b00f70ed68d1030aad44bbe9ae2#egg=django-allauth 5 | 6 | ansicolors 7 | arrow 8 | beautifulsoup4 9 | bleach 10 | boto3==1.12 11 | brotlipy 12 | celery==5.4.0 13 | cffi==1.16.0 14 | dj-database-url 15 | Django==3.2 16 | #django-allauth 17 | django-appconf 18 | django-autoslug 19 | django-bmemcached 20 | django-bootstrap-pagination 21 | django-cors-headers 22 | django-debug-toolbar 23 | django-extensions 24 | django-filter 25 | django-forms-bootstrap 26 | django-heroku 27 | django-ipware 28 | django-oauth-toolkit 29 | django-recaptcha 30 | django-storages==1.9.1 31 | django-waffle==0.20.0 32 | django_coverage_plugin 33 | djangorestframework 34 | factory-boy 35 | feedparser 36 | gevent 37 | gunicorn 38 | Jinja2 39 | jsonfield # can't remove this because migrations depend on it presently 40 | mailchimp 41 | Markdown 42 | mock 43 | Pillow # for sorl-thumbnail 44 | psycopg2==2.9.9 45 | pyparsing 46 | PyJWT 47 | raven 48 | redis 49 | requests 50 | selenium 51 | sorl-thumbnail 52 | termcolor 53 | 54 | # modules we control 55 | django-gulp 56 | django-hash-filter2 57 | env-tools 58 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.10.14 2 | -------------------------------------------------------------------------------- /scripts/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ENV="staging" 4 | # ENV="www" 5 | 6 | ab -c 10 -t 300 -s 30 "https://$ENV.openhumans.org/member/beau/" 7 | 8 | echo "https://$ENV.openhumans.org/members/" > urls.txt 9 | echo "https://$ENV.openhumans.org/member/beau/" >> urls.txt 10 | echo "https://$ENV.openhumans.org/" >> urls.txt 11 | 12 | siege --file urls.txt --quiet --internet --time 5m 13 | -------------------------------------------------------------------------------- /scripts/dump-production.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | COMMAND="./manage.py dumpdata --natural-foreign --natural-primary -e admin -e contenttypes -e auth.Permission -e auth.Group -e sessions --indent=2" 4 | 5 | production run "$COMMAND" 6 | -------------------------------------------------------------------------------- /scripts/migrate_s3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # pylint: disable=wrong-import-order 3 | 4 | import boto3 5 | import os 6 | import re 7 | 8 | from env_tools import apply_env 9 | 10 | apply_env() 11 | 12 | import django # noqa 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "open_humans.settings") 15 | 16 | django.setup() 17 | 18 | from data_import.models import DataFile # noqa 19 | from data_import.utils import get_upload_path # noqa 20 | 21 | BUCKET_NAME = "open-humans-production" 22 | 23 | s3 = boto3.resource("s3") 24 | 25 | bucket = s3.Bucket(BUCKET_NAME) 26 | 27 | for key in bucket.objects.all(): 28 | if not re.match(r"^member/", key.key): 29 | continue 30 | 31 | if "profile-images" in key.key: 32 | continue 33 | 34 | try: 35 | data_file = DataFile.objects.get(file=key.key) 36 | except DataFile.DoesNotExist: 37 | print("Does not exist: {}".format(key.key)) 38 | 39 | continue 40 | except DataFile.MultipleObjectsReturned: 41 | print("Multiple objects: {}".format(key.key)) 42 | 43 | continue 44 | 45 | file_name = os.path.basename(key.key) 46 | 47 | new_key = get_upload_path(data_file, file_name) 48 | 49 | print(key.key) 50 | print(" {}".format(file_name)) 51 | print(" {}".format(new_key)) 52 | print("") 53 | 54 | s3.Object(BUCKET_NAME, new_key).copy_from( 55 | CopySource="{0}/{1}".format(BUCKET_NAME, key.key) 56 | ) 57 | 58 | data_file.file = new_key 59 | data_file.save() 60 | 61 | key.delete() 62 | -------------------------------------------------------------------------------- /static/css/_footer.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer styles 2 | -------------------------------------------------- */ 3 | $footer-height: 60px; 4 | $footer-border-color: $navbar-border-color; 5 | $footer-border-thickness: $navbar-border-thickness; 6 | 7 | html { 8 | position: relative; 9 | min-height: 100%; 10 | } 11 | 12 | body { 13 | /* Margin bottom by footer height */ 14 | margin-bottom: $footer-height; 15 | } 16 | 17 | .footer { 18 | position: absolute; 19 | bottom: 0; 20 | width: 100%; 21 | /* Set the fixed height of the footer here */ 22 | height: $footer-height; 23 | background-color: #ffffff; 24 | border-color: $footer-border-color; 25 | border-width: $footer-border-thickness 0 0; 26 | border-style: solid; 27 | } 28 | 29 | .footer > .container-fluid, 30 | .footer > .container { 31 | padding: 15px; 32 | } 33 | -------------------------------------------------------------------------------- /static/css/_global-variables.css: -------------------------------------------------------------------------------- 1 | /* Colors 2 | -------------------------------------------------- */ 3 | $oh-teal: #4ac1c8; 4 | $oh-teal-dark: #009fa8; 5 | $oh-teal-verydark: #008c94; 6 | $oh-teal-verylight: #e6f9fa; 7 | 8 | $oh-orange: #ff9161; 9 | $oh-orange-verylight: #fff1eb; 10 | $oh-orange-dark: #f7763e; 11 | 12 | $oh-gray-verydark: #4d4d4d; 13 | 14 | $oh-background-image: '/static/images/get2014_background.jpg'; 15 | 16 | /* Navbar styles 17 | -------------------------------------------------- */ 18 | $navbar-height: 50px; 19 | $navbar-border-thickness: 0px; 20 | $navbar-background-color: #ffffff; 21 | $navbar-background-color-active: $oh-teal-verylight; 22 | $navbar-border-color: $fff; 23 | $navbar-color: $oh-teal-dark; 24 | $navbar-color-focus-hover: $oh-teal-verydark; 25 | $navbar-color-active: $oh-teal-verydark; 26 | 27 | 28 | /* Fonts 29 | -------------------------------------------------- */ 30 | $user-input-font: 'Merriweather', 'Palatino Linotype', 'Book Antiqua', Palatino, serif; 31 | $heading-font: 'Montserrat', sans-serif; 32 | $main-font: 'Montserrat', sans-serif; 33 | -------------------------------------------------------------------------------- /static/css/_pages-members.css: -------------------------------------------------------------------------------- 1 | .member-profile-img { 2 | width: 30px; 3 | height: 30px; 4 | } 5 | -------------------------------------------------------------------------------- /static/css/_text.css: -------------------------------------------------------------------------------- 1 | /* Text, links, and header styles 2 | -------------------------------------------------- */ 3 | h1, h2, h3, h4, h5, h6, 4 | .h1, .h2, .h3, .h4, .h5, .h6 { 5 | font-family: $heading-font; 6 | font-weight: 300; 7 | color: $oh-gray-verydark; 8 | } 9 | 10 | body { 11 | font-family: $main-font; 12 | } 13 | 14 | a { 15 | color: $oh-teal-dark; 16 | 17 | &:hover, &:focus { 18 | color: $oh-teal-verydark; 19 | } 20 | } 21 | 22 | h6.section-header { 23 | margin-top: 15px; 24 | margin-bottom: 0; 25 | } 26 | 27 | hr.section-header { 28 | margin-top: 0; 29 | margin-bottom: 10px; 30 | } 31 | 32 | .profile-text { 33 | font-family: $user-input-font; 34 | 35 | h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 { 36 | font-family: $user-input-font; 37 | } 38 | 39 | h1, .h1, h2, .h2, h3, .h3 { 40 | margin-top: 10px; 41 | margin-bottom: 5px; 42 | font-weight: 700; 43 | } 44 | 45 | h1, .h1 { 46 | font-size: 24px; 47 | } 48 | 49 | h2, .h2 { 50 | font-size: 21px; 51 | } 52 | 53 | h3, .h3 { 54 | font-size: 18px; 55 | } 56 | 57 | h4, .h5 { 58 | font-size: 15px; 59 | } 60 | 61 | h5, .h5 { 62 | font-size: 12px; 63 | } 64 | 65 | h6, .h6 { 66 | font-size: 9px; 67 | } 68 | } 69 | 70 | .bigger-text { 71 | font-size: 18px; 72 | } 73 | -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | @import '_global-variables.css'; 2 | @import '_navbar.css'; 3 | @import '_buttons.css'; 4 | @import '_text.css'; 5 | @import '_footer.css'; 6 | @import '_divs.css'; 7 | @import '_pages.css'; 8 | 9 | /* The blurred background image shown on every page */ 10 | #blurred-people-background { 11 | width: 100%; 12 | height: 100%; 13 | z-index: -1; 14 | position: absolute; 15 | background-size: cover; 16 | background-position: center; 17 | /* The actual image URL is specified in a template block */ 18 | } 19 | 20 | /* TODO: Move somewhere appropriate */ 21 | .profile-image { 22 | width: 30%; 23 | margin-left: 2%; 24 | float: right; 25 | max-width: 250px; 26 | } 27 | 28 | /* Messages 29 | -------------------------------------------------- */ 30 | .message { 31 | margin: 15px 0; 32 | padding: 15px; 33 | background-color: #ddf9ff; 34 | border-style: solid; 35 | border-color: $oh-indigo-muted; 36 | border-width: 2px; 37 | border-radius: 6px; 38 | 39 | &.error { 40 | background-color: #ffeeee; 41 | border-color: #cc0000; 42 | font-weight: 800; 43 | } 44 | 45 | &.success { 46 | background-color: #d9ffee; 47 | border-color: #00aa22; 48 | } 49 | } 50 | 51 | /* Site-wide modals 52 | -------------------------------------------------- */ 53 | #signup-modal { 54 | overflow: scroll; 55 | } 56 | -------------------------------------------------------------------------------- /static/images/FB-F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/FB-F.png -------------------------------------------------------------------------------- /static/images/FB-f-Logo__blue_29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/FB-f-Logo__blue_29.png -------------------------------------------------------------------------------- /static/images/Mad-Ball.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/Mad-Ball.jpg -------------------------------------------------------------------------------- /static/images/apple-logo-white-large-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/apple-logo-white-large-3x.png -------------------------------------------------------------------------------- /static/images/bastian-greshake-tzovaras.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/bastian-greshake-tzovaras.jpg -------------------------------------------------------------------------------- /static/images/connect-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/connect-data.png -------------------------------------------------------------------------------- /static/images/default-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/default-badge.png -------------------------------------------------------------------------------- /static/images/email-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/email-icon.png -------------------------------------------------------------------------------- /static/images/get2014_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/get2014_background.jpg -------------------------------------------------------------------------------- /static/images/google-logo-g.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/google-logo-g.png -------------------------------------------------------------------------------- /static/images/knight-logo-300.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/knight-logo-300.jpg -------------------------------------------------------------------------------- /static/images/mdulaney.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/mdulaney.jpg -------------------------------------------------------------------------------- /static/images/oh-connect-study.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/oh-connect-study.png -------------------------------------------------------------------------------- /static/images/oh-public-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/oh-public-data.png -------------------------------------------------------------------------------- /static/images/oh-research-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/oh-research-help.png -------------------------------------------------------------------------------- /static/images/oh-research-partner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/oh-research-partner.png -------------------------------------------------------------------------------- /static/images/open-humans-logo-horizontal-80px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/open-humans-logo-horizontal-80px.png -------------------------------------------------------------------------------- /static/images/open_humans_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/open_humans_favicon.png -------------------------------------------------------------------------------- /static/images/open_humans_logo_only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/open_humans_logo_only.png -------------------------------------------------------------------------------- /static/images/profile-placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/profile-placeholder.png -------------------------------------------------------------------------------- /static/images/project-1-draft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/project-1-draft.png -------------------------------------------------------------------------------- /static/images/project-2-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/project-2-test.png -------------------------------------------------------------------------------- /static/images/project-3-try.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/project-3-try.png -------------------------------------------------------------------------------- /static/images/rwjf_logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/rwjf_logo1.png -------------------------------------------------------------------------------- /static/images/share-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/share-data.png -------------------------------------------------------------------------------- /static/images/twitter-xs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/twitter-xs-logo.png -------------------------------------------------------------------------------- /static/images/your-data-plus-you-sketch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHumans/open-humans/51c6bdb666d6d8ee0cbe227c606584d3f1556969/static/images/your-data-plus-you-sketch.jpg -------------------------------------------------------------------------------- /static/js/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var navPills = require('./lib/nav-pills.js'); 4 | 5 | navPills(); 6 | -------------------------------------------------------------------------------- /static/js/account-password-reset-token.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function () { 4 | $('#id_password').attr('required', ''); 5 | $('#id_password_confirm').attr('required', ''); 6 | 7 | $('#id_password').attr('minlength', '8'); 8 | $('#id_password_confirm').attr('minlength', '8'); 9 | 10 | $('#id_password_confirm').attr('data-parsley-equalto', '#id_password'); 11 | }); 12 | -------------------------------------------------------------------------------- /static/js/account-password.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function () { 4 | $('#id_password_current').attr('required', ''); 5 | 6 | $('#id_password_new').attr('required', ''); 7 | $('#id_password_new_confirm').attr('required', ''); 8 | 9 | $('#id_password_new').attr('minlength', '8'); 10 | $('#id_password_new_confirm').attr('minlength', '8'); 11 | 12 | $('#id_password_new_confirm').attr('data-parsley-equalto', '#id_password_new'); 13 | }); 14 | -------------------------------------------------------------------------------- /static/js/activity-management.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var publicSharingToggle = require('./lib/public-sharing-toggle.js'); 4 | var publicVisibleToggle = require('./lib/public-visible-toggle.js'); 5 | 6 | $(function () { 7 | $('[data-toggle="popover"]').popover({html: true, trigger: 'focus'}); 8 | 9 | publicSharingToggle(); 10 | 11 | $('form.delete-selfie-file').on('click', 'input[type=submit]', function (e) { 12 | e.preventDefault(); 13 | 14 | var $form = $(this).parent(); 15 | var formUrl = $form.attr('action'); 16 | 17 | var self = this; 18 | 19 | $.post(formUrl, $form.serialize(), function () { 20 | var $tr = $(self).parents('tr').eq(0); 21 | 22 | $tr.hide('slow', function () { 23 | $tr.remove(); 24 | }); 25 | }).fail(function () { 26 | // fall back to a regular form submission if AJAX doesn't work 27 | $form.submit(); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /static/js/add-data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function () { 4 | $('[data-toggle="popover"]').popover({html: true, trigger: 'focus'}); 5 | 6 | var $grid = $('.grid').isotope({ 7 | itemSelector: '.item', 8 | layoutMode: 'masonry', 9 | masonry: { 10 | columnWidth: '.col-md-4', 11 | percentPosition: true 12 | } 13 | }); 14 | 15 | $('.grid').imagesLoaded(function () { 16 | $('.grid').isotope('layout'); 17 | }); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /static/js/community-guidelines.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var navPills = require('./lib/nav-pills.js'); 4 | 5 | navPills(); 6 | -------------------------------------------------------------------------------- /static/js/explore-share.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function () { 4 | $('[data-toggle="popover"]').popover({html: true, trigger: 'focus'}); 5 | 6 | var $grid = $('.grid').isotope({ 7 | itemSelector: '.item', 8 | layoutMode: 'masonry', 9 | masonry: { 10 | columnWidth: '.col-md-4', 11 | percentPosition: true 12 | } 13 | }); 14 | 15 | $('.grid').imagesLoaded(function () { 16 | $('.grid').isotope('layout'); 17 | }); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /static/js/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function getHashFilter() { 4 | var matches = location.hash.match(/filter=([^&]+)/i); 5 | var filter = matches && matches[1]; 6 | 7 | return (filter && decodeURIComponent(filter)) || '*'; 8 | } 9 | 10 | $(function () { 11 | $('[data-toggle="popover"]').popover({html: true, trigger: 'focus'}); 12 | 13 | var $grid = $('.grid').isotope({ 14 | itemSelector: '.item', 15 | layoutMode: 'masonry', 16 | masonry: { 17 | columnWidth: '.col-md-4', 18 | percentPosition: true 19 | } 20 | }); 21 | 22 | $('.grid').imagesLoaded(function () { 23 | $('.grid').isotope('layout'); 24 | }); 25 | 26 | function onHashchange() { 27 | var filter = getHashFilter(); 28 | 29 | $('.filters button').removeClass('selected'); 30 | $('.filters button[data-filter="' + filter + '"]').addClass('selected'); 31 | 32 | $('.filter-empty').hide(); 33 | 34 | $('.filter-description').hide(); 35 | $('.filter-description[data-filter="' + filter + '"]').show(); 36 | 37 | $grid.isotope({filter: filter}); 38 | 39 | var filteredItems = $('.grid ' + filter); 40 | 41 | if (!filteredItems.length) { 42 | $('.filter-description').hide(); 43 | $('.filter-empty[data-filter="' + getHashFilter() + '"]').show(); 44 | } 45 | } 46 | 47 | $('.filters button').click(function () { 48 | location.hash = 'filter=' + 49 | encodeURIComponent($(this).attr('data-filter')); 50 | }); 51 | 52 | $(window).on('hashchange', onHashchange); 53 | 54 | onHashchange(); 55 | }); 56 | -------------------------------------------------------------------------------- /static/js/lib/enable-csrf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Cookies = require('js-cookie'); 4 | 5 | function csrfSafeMethod(method) { 6 | // These HTTP methods do not require CSRF protection 7 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 8 | } 9 | 10 | module.exports = function ($) { 11 | var csrfToken = Cookies.get('csrftoken'); 12 | 13 | $.ajaxSetup({ 14 | beforeSend: function (xhr, settings) { 15 | if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 16 | xhr.setRequestHeader('X-CSRFToken', csrfToken); 17 | } 18 | } 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /static/js/lib/nav-pills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function updatePill() { 4 | var url = document.location.toString(); 5 | 6 | if (url.match('#')) { 7 | $('.nav-pills a[href="#' + url.split('#')[1] + '"]').tab('show'); 8 | } 9 | } 10 | 11 | module.exports = function () { 12 | $(function () { 13 | // Change the hash when a user clicks on a nav-pill link 14 | $('.nav-pills a, .table-of-contents a').on('click', function (e) { 15 | e.preventDefault(); 16 | 17 | history.pushState({}, '', e.target.hash); 18 | 19 | updatePill(); 20 | }); 21 | 22 | // Show the correct page when the hash changes 23 | $(window).on('popstate', function () { 24 | updatePill(); 25 | }); 26 | 27 | updatePill(); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /static/js/lib/public-sharing-toggle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function () { 4 | // AJAX toggling for public data sharing 5 | $('form.toggle-sharing').on('click', 'button[type=submit]', function (e) { 6 | e.preventDefault(); 7 | 8 | var self = this; 9 | $(self).html("Updating..."); 10 | $(self).prop("disabled", true); 11 | 12 | var $form = $(this).parent(); 13 | var formUrl = $form.attr('action'); 14 | 15 | var isPublic = $(this).siblings('input[name=public]').val() === 'True'; 16 | 17 | var newState = isPublic ? 'False' : 'True'; 18 | var newValue = isPublic ? 'Stop public sharing' : 'Share publicly'; 19 | 20 | $.post(formUrl, $form.serialize(), function () { 21 | $(self).html(newValue); 22 | $(self).removeAttr('disabled') 23 | $(self).siblings('input[name=public]').val(newState); 24 | }).fail(function () { 25 | // fall back to a regular form submission if AJAX doesn't work 26 | $form.submit(); 27 | }); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /static/js/lib/public-visible-toggle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function () { 4 | // AJAX toggling for public data visibility 5 | $('form.toggle-visibility').on('click', 'button[type=submit]', function (e) { 6 | e.preventDefault(); 7 | 8 | var self = this; 9 | $(self).html("Updating..."); 10 | $(self).prop("disabled", true); 11 | 12 | var $form = $(this).parent(); 13 | var formUrl = $form.attr('action'); 14 | 15 | var isVisible = $(this).siblings('input[name=visible]').val() === 'True'; 16 | 17 | var newState = isVisible ? 'False' : 'True'; 18 | var newValue = isVisible ? 'Visible' : 'Hidden'; 19 | 20 | $.post(formUrl, $form.serialize(), function () { 21 | $(self).html(newValue); 22 | $(self).removeAttr('disabled') 23 | $(self).siblings('input[name=visible]').val(newState); 24 | }).fail(function () { 25 | // fall back to a regular form submission if AJAX doesn't work 26 | $form.submit(); 27 | }); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /static/js/member-detail.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function () { 4 | $('[data-toggle="popover"]').popover({html: true, trigger: 'focus'}); 5 | }); 6 | -------------------------------------------------------------------------------- /static/js/member-me-research-data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var publicSharingToggle = require('./lib/public-sharing-toggle.js'); 4 | 5 | $(function () { 6 | $('[data-toggle="popover"]').popover({html: true, trigger: 'focus'}); 7 | 8 | publicSharingToggle(); 9 | 10 | $('form.delete-selfie-file').on('click', 'input[type=submit]', function (e) { 11 | e.preventDefault(); 12 | 13 | var $form = $(this).parent(); 14 | var formUrl = $form.attr('action'); 15 | 16 | var self = this; 17 | 18 | $.post(formUrl, $form.serialize(), function () { 19 | var $tr = $(self).parents('tr').eq(0); 20 | 21 | $tr.hide('slow', function () { 22 | $tr.remove(); 23 | }); 24 | }).fail(function () { 25 | // fall back to a regular form submission if AJAX doesn't work 26 | $form.submit(); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /static/js/my-member-data-selfie.js: -------------------------------------------------------------------------------- 1 | /*global SELFIE_ACKNOWLEDGE_URL:true, SELFIE_SHOW_MODAL:true*/ 2 | 3 | 'use strict'; 4 | 5 | var enableCsrf = require('./lib/enable-csrf.js'); 6 | var publicSharingToggle = require('./lib/public-sharing-toggle.js'); 7 | 8 | enableCsrf($); 9 | 10 | $(function () { 11 | $('[data-toggle="popover"]').popover({html: true, trigger: 'focus'}); 12 | 13 | publicSharingToggle(); 14 | 15 | $('.delete-button').click(function (e) { 16 | e.preventDefault(); 17 | }); 18 | 19 | if (!SELFIE_SHOW_MODAL) { 20 | return; 21 | } 22 | 23 | $('#data-selfie-modal').modal({ 24 | keyboard: false, 25 | backdrop: 'static' 26 | }); 27 | 28 | $('#continue').click(function (e) { 29 | e.preventDefault(); 30 | 31 | $('#data-selfie-modal').modal('hide'); 32 | 33 | $.post(SELFIE_ACKNOWLEDGE_URL); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /static/js/public-data-api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function () { 4 | var $sourceSearch = $('#source-search').select2({ 5 | placeholder: 'Select an activity', 6 | theme: 'bootstrap' 7 | }); 8 | 9 | $sourceSearch.on('select2:select', function () { 10 | var url = 'https://www.openhumans.org/api/public-data/?source=' + 11 | $sourceSearch.val(); 12 | 13 | $('#source-url').html('' + url + ''); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /static/js/studies-connect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function () { 4 | $('[data-toggle="popover"]').popover({html: true}); 5 | }); 6 | --------------------------------------------------------------------------------