diff --git a/.env.sample b/.env.sample index 9ecf7e9..4279947 100644 --- a/.env.sample +++ b/.env.sample @@ -1,4 +1,23 @@ -DATABASE_URL=postgres:///myurl +################################ +# # +# THIS IS A SECRET FILE!!! # +# # +# Do not add your .env to git! # +# # +################################ + +# SECRET_KEY encrypts things. In production it should be a random string. SECRET_KEY=mysecretshouldnotbeongit -OH_CLIENT_ID=myclientidshouldnotbeongit -OH_CLIENT_SECRET=myclientsecretshouldnotbeongit + +# ADMIN_PASSWORD is how you log in to manage this app and configuration. +ADMIN_PASSWORD='' + +# Optional, but you probably want this for logging and debugging. +PYTHONUNBUFFERED='true' + +# Set DATABASE_URL to use something other than the default SQLite. +# DATABASE_URL=postgres:///myurl + +# Set app base URL. If unset, defaults to http://127.0.0.1:5000 local, +# and herokuapp.com URL on Heroku. +# APP_BASE_URL='http://127.0.0.1:5000' diff --git a/LICENSE b/LICENSE index 0d8f1c3..8a0d8f9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Bastian Greshake Tzovaras +Copyright (c) 2018 Bastian Greshake Tzovaras, Mad Price Ball Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Procfile b/Procfile index 0bc58d7..063f11a 100644 --- a/Procfile +++ b/Procfile @@ -1 +1,2 @@ +release: python manage.py migrate web: gunicorn oh_data_uploader.wsgi --log-file=- diff --git a/README.md b/README.md index 21ac247..2658bc3 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ > *it's like Jekyll for Open Humans* or something along the lines. +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) + Ultimately this should be an easy to deploy Django project that functions as a file uploader for individual *Open Humans* projects. It should be easy deploy to *heroku* and the configuration/styling of the project website should be done more @@ -24,17 +26,23 @@ conda create -n oh_uploader python=3.6 pip install -r requirements.txt ``` -*Step 2: Install heroku-CLI & PostgreSQL* +*Step 2: Install Heroku Command Line Interface (CLI)* + +You should install the Heroku CLI to run this app. Heroku has [installation instructions for MacOS, Windows, and Linux](https://devcenter.heroku.com/articles/heroku-cli#download-and-install). -If you are running MacOS the easiest way to do this is using [Homebrew](https://brew.sh/). If you are on a Linux machine you [should be able to do the same things using Linuxbrew](https://virtualenv.pypa.io/en/stable/). After installing Homebrew/Linuxbrew -you can do: +If you are running MacOS the easiest way to do this is using [Homebrew](https://brew.sh/). After installing Homebrew you can do: ``` -brew install postgres brew install heroku/brew/heroku ``` -Once this is done you can start the setup of your *Open Humans uploader* by copying the example `.env` file (`cp .env.sample .env`) and entering at least your database URL. Once this is done you can migrate your database using `heroku local:run python manage.py migrate`. Afterwards you can start the webserver of your local heroku environment using `heroku local`. +Once this is done you can complete minimal setup by: +* Create an `.env` file from the example: `cp .env.sample .env`) +* Edit `.env` to set a random string for `DJANGO_SECRET` +* Migrate your database using `heroku local:run python manage.py migrate` +* Initialize config with `heroku local:run python manage.py init_proj_config` + +Now you can run the webserver of your local heroku environment using `heroku local`. To fully set up your uploader you will have to modify some files, as described below. @@ -97,7 +105,7 @@ prominently on the front page. ``` # Give the path to the logo of your project. -logo: ' static/example_logo.png' +logo: ' static/default_logo.png' # Is there a larger project website where more info might be located? more_info_link: http://www.github.com/gedankenstuecke/oh_data_uploader diff --git a/_descriptions/index.md b/_descriptions/index.md index 5bde8b5..05b99df 100644 --- a/_descriptions/index.md +++ b/_descriptions/index.md @@ -1,16 +1,17 @@ # What does my project do? This tool allows you to easily setup your own *Open Humans* project that wants -to collect data. You just need to set up some configuration files before you -can deploy it *heroku*. +to collect data. -The configuration files are: +The configuration for this project is in: -- `config.yaml` in the main directory of this git repository. This contains details -on your project that aren't secret like the title, description, where the logo can be found etc. -- The `.env` contains the secret details that shouldn't be shared. E.g. your database setup, -your *Open Humans* API keys etc. -- The texts that should be displayed on this project website. In `_descriptions` you can find the -markdown files needed to customize this template. E.g. this page is written in `_descriptions/index.md`. +- **Environment variables:** If you deploy to Heroku, you'll probably be +prompted to provide these, and they can be modified in the the app. Locally, +your `.env` file defines these. These are secret details that should not be +shared. (Be sure not to add this to your git repository!) +- **Project configuration:** The [Project Admin page](/project-admin) can be +used to configure other aspects of your site. Log in with the `ADMIN_PASSWORD` +you set in environment variables. -Use this page to inform your users about what your project is about and what it tries to do. It's the first -page they will see, so be verbose! +As you configure the project, replace this "front page" text to inform +users what your project is about and what it tries to do. It's the first page +they will see, so be verbose! diff --git a/app.json b/app.json new file mode 100644 index 0000000..971805c --- /dev/null +++ b/app.json @@ -0,0 +1,23 @@ +{ + "name": "Open Humans Data Uploader", + "description": "A web app to support data uploads to an Open Humans project", + "repository": "https://github.com/madprime/oh_data_uploader", + "keywords": ["django"], + "scripts": { + "postdeploy": "python manage.py init_proj_config" + }, + "env": { + "HEROKUCONFIG_APP_NAME": { + "description": "Heroku \"App name\" (copy/paste what you set above)", + "required": true + }, + "ADMIN_PASSWORD": { + "description": "You'll use this password to manage configuring this app.", + "required": true + }, + "SECRET_KEY": { + "description": "This is set for you and is used to encrypt data.", + "generator": "secret" + } + } +} diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 8809993..0000000 --- a/config.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# REQUIRED: What's the name of your project? -project_title: My Open Humans Project - -# REQUIRED: Will be displayed on the front page of the uploader -project_description: This template demonstrates how you can run your own Open Humans data upload project. - -# REQUIRED: Where can we find your project on Open Humans -oh_activity_page: https://www.openhumans.org/activity/your-project-url/ - -# REQUIRED: which URL will lead to your Open Humans uploader -app_base_url: http://127.0.0.1:5000 - -# REQUIRED: Tell Open Humans what kind of data is being uploaded -file_description: This is an example file that doesnt have any meaning. - -# REQUIRED: Tags to add to your file uploads -file_tags: -- tags -- 'are a good way to' -- 'describe the files you are uploading' - -# Give the path to the logo of your project. -logo: 'static/example_logo.png' - -# Is there a larger project website where more info might be located? -more_info_link: http://www.github.com/gedankenstuecke/oh_data_uploader diff --git a/oh_connection/context_processors.py b/oh_connection/context_processors.py index 0e27dae..d0d4326 100644 --- a/oh_connection/context_processors.py +++ b/oh_connection/context_processors.py @@ -1,7 +1,9 @@ -from django.conf import settings +from project_admin.models import ProjectConfiguration def read_config(request): - context = {'yaml_config': settings.YAML_CONFIG, - 'oh_proj_page': settings.YAML_CONFIG['oh_activity_page']} + config = ProjectConfiguration.objects.get(id=1) + context = {'config': config, + 'is_admin': request.user.username == 'admin', + 'oh_proj_page': config.oh_activity_page} return context diff --git a/oh_connection/templates/oh_connection/application.html b/oh_connection/templates/oh_connection/application.html index b01996a..9b41f82 100644 --- a/oh_connection/templates/oh_connection/application.html +++ b/oh_connection/templates/oh_connection/application.html @@ -5,7 +5,7 @@ - {{yaml_config.project_title}} + {{config.project_title}} @@ -73,18 +73,28 @@ - {{yaml_config.project_title}} + {{config.project_title}} diff --git a/oh_connection/templates/oh_connection/complete.html b/oh_connection/templates/oh_connection/complete.html index 403887a..c6a6593 100644 --- a/oh_connection/templates/oh_connection/complete.html +++ b/oh_connection/templates/oh_connection/complete.html @@ -2,7 +2,7 @@ {% block content %} -

One last step: Uploading your data to {{yaml_config.project_title}}

+

One last step: Uploading your data to {{config.project_title}}

Thank you! We got your authorization to connect to Open Humans.

diff --git a/oh_connection/templates/oh_connection/index.html b/oh_connection/templates/oh_connection/index.html index f908682..403219b 100644 --- a/oh_connection/templates/oh_connection/index.html +++ b/oh_connection/templates/oh_connection/index.html @@ -3,10 +3,10 @@ {% block content %}
- Brand -

Welcome to {{yaml_config.project_title}}

+ Brand +

Welcome to {{config.project_title}}

- {{yaml_config.project_description}} + {{config.project_description}}

@@ -19,13 +19,13 @@

Welcome to {{yaml_config.project_title}}

How it works

  1. Log in or create an Open Humans account
    - You can upload your data for {{yaml_config.project_title}} + You can upload your data for {{config.project_title}} once you have done this.
  2. -
  3. Authorize {{yaml_config.project_title}} on Open Humans
    +
  4. Authorize {{config.project_title}} on Open Humans
    This authorizes us to deposit your archive into your Open Humans account.
  5. Upload your data.
    - You will be redirected back to {{yaml_config.project_title}} and can then + You will be redirected back to {{config.project_title}} and can then upload your data. You'll be able to access & manage your data on diff --git a/oh_connection/templates/oh_connection/overview.html b/oh_connection/templates/oh_connection/overview.html index 3a613a0..a763bd5 100644 --- a/oh_connection/templates/oh_connection/overview.html +++ b/oh_connection/templates/oh_connection/overview.html @@ -2,7 +2,7 @@ {% block content %} -

    Manage your {{yaml_config.project_title}} data.

    +

    Manage your {{config.project_title}} data.

    Thank you! We logged you in through Open Humans.

    diff --git a/oh_connection/templates/oh_connection/partials/upload_form.html b/oh_connection/templates/oh_connection/partials/upload_form.html index 69e4e2b..1bc6139 100644 --- a/oh_connection/templates/oh_connection/partials/upload_form.html +++ b/oh_connection/templates/oh_connection/partials/upload_form.html @@ -16,7 +16,7 @@ replacement = '

    Your Upload started

    ' $("#upload_form").replaceWith('
    '+replacement+'
    '); var xhr = new XMLHttpRequest(); - var metadata = JSON.stringify({"tags":{{yaml_config.file_tags|safe}},"description" : '{{yaml_config.file_description}}'}); + var metadata = JSON.stringify({"tags":{{config.file_tags|safe}},"description" : '{{config.file_description}}'}); xhr.open('POST', "https://www.openhumans.org/api/direct-sharing/project/files/upload/direct/?access_token={{oh_member.access_token}}", true); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.send('project_member_id={{oh_member.oh_id}}&filename='+file['name']+'&metadata='+metadata); diff --git a/oh_connection/urls.py b/oh_connection/urls.py index f668a32..4146075 100644 --- a/oh_connection/urls.py +++ b/oh_connection/urls.py @@ -5,6 +5,7 @@ urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^complete/?$', views.complete, name='complete'), + url(r'^logout/?$', views.logout_user, name='logout'), url(r'^overview/?$', views.overview, name='overview'), url(r'^upload_simple/?$', views.upload_old, name='upload_old'), url(r'^about/?$', views.about, name='about') diff --git a/oh_connection/views.py b/oh_connection/views.py index 542280e..606af50 100644 --- a/oh_connection/views.py +++ b/oh_connection/views.py @@ -6,10 +6,12 @@ from urllib.error import HTTPError from django.conf import settings -from django.contrib.auth import login +from django.contrib.auth import login, logout from django.shortcuts import redirect, render import requests +from project_admin.models import ProjectConfiguration + from .models import OpenHumansMember from .forms import UploadFileForm logger = logging.getLogger(__name__) @@ -41,7 +43,8 @@ def oh_code_to_member(code): Exchange code for token, use this to create and return OpenHumansMember. If a matching OpenHumansMember already exists in db, update and return it. """ - if settings.OH_CLIENT_SECRET and settings.OH_CLIENT_ID and code: + proj_config = ProjectConfiguration.objects.get(id=1) + if proj_config.oh_client_secret and proj_config.oh_client_id and code: print('{}/complete'.format(APP_BASE_URL)) data = { 'grant_type': 'authorization_code', @@ -52,8 +55,8 @@ def oh_code_to_member(code): '{}/oauth2/token/'.format(OH_BASE_URL), data=data, auth=requests.auth.HTTPBasicAuth( - settings.OH_CLIENT_ID, - settings.OH_CLIENT_SECRET + proj_config.oh_client_id, + proj_config.oh_client_secret )) data = req.json() print("Data: {}".format(str(data))) @@ -143,25 +146,23 @@ def index(request): """ Starting page for app. """ - index_text = open("_descriptions/index.md", 'r').readlines() - index_text = "".join(index_text) - context = {'client_id': settings.OH_CLIENT_ID, + proj_config = ProjectConfiguration.objects.get(id=1) + context = {'client_id': proj_config.oh_client_id, 'redirect_uri': '{}/complete'.format(APP_BASE_URL), - 'index_page': index_text, - 'config': settings.YAML_CONFIG} - if request.user.is_authenticated: + 'index_page': "".join(proj_config.homepage_text)} + if request.user.is_authenticated and request.user.username != 'admin': return redirect('overview') return render(request, 'oh_connection/index.html', context=context) def overview(request): - if request.user.is_authenticated: + if request.user.is_authenticated and request.user.username != 'admin': oh_member = request.user.openhumansmember - overview = open("_descriptions/overview.md", 'r').readlines() - overview = "".join(overview) + proj_config = ProjectConfiguration.objects.get(id=1) context = {'oh_id': oh_member.oh_id, 'oh_member': oh_member, - "overview": overview} + 'access_token': oh_member.get_access_token(), + "overview": "".join(proj_config.overview)} return render(request, 'oh_connection/overview.html', context=context) return redirect('index') @@ -173,6 +174,7 @@ def complete(request): logger.debug("Received user returning from Open Humans.") form = None + proj_config = ProjectConfiguration.objects.get(id=1) if request.method == 'GET': # Exchange code for token. @@ -192,22 +194,18 @@ def complete(request): oh_member = request.user.openhumansmember form = UploadFileForm() - upload = open("_descriptions/upload_description.md", 'r').readlines() - upload = "".join(upload) context = {'oh_id': oh_member.oh_id, 'oh_member': oh_member, 'form': form, - 'upload_description': upload} + 'upload_description': proj_config.upload_description} return render(request, 'oh_connection/complete.html', context=context) elif request.method == 'POST': form = UploadFileForm(request.POST, request.FILES) if form.is_valid(): - metadata = {'tags': - settings.YAML_CONFIG['file_tags'], - 'description': - settings.YAML_CONFIG['file_description']} + metadata = {'tags': json.loads(proj_config.file_tags), + 'description': proj_config.file_description} upload_file_to_oh( request.user.openhumansmember, request.FILES['file'], @@ -217,22 +215,25 @@ def complete(request): return redirect('index') +def logout_user(request): + if request.method == 'POST': + logout(request) + return redirect('index') + + def upload_old(request): + proj_config = ProjectConfiguration.objects.get(id=1) + if request.user.is_authenticated: - upload = open("_descriptions/upload_description.md", 'r').readlines() - upload = "".join(upload) - context = {'upload_description': upload} + context = {'upload_description': proj_config.upload_description} return render(request, 'oh_connection/upload_old.html', context=context) return redirect('index') def about(request): - about = open("_descriptions/about.md", 'r').readlines() - about = "".join(about) - faq = open("_descriptions/faq.md", 'r').readlines() - faq = "".join(faq) - context = {'about': about, - 'faq': faq} + proj_config = ProjectConfiguration.objects.get(id=1) + context = {'about': proj_config.about, + 'faq': proj_config.faq} return render(request, 'oh_connection/about.html', context=context) diff --git a/oh_data_uploader/settings.py b/oh_data_uploader/settings.py index 445a52a..0579195 100644 --- a/oh_data_uploader/settings.py +++ b/oh_data_uploader/settings.py @@ -12,7 +12,6 @@ import os import dj_database_url -import yaml from env_tools import apply_env apply_env() @@ -30,7 +29,14 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False if os.getenv('DEBUG', '').lower() == 'false' else True -ALLOWED_HOSTS = ['*'] +# Infer if this is running on Heroku based on this being set. +HEROKUCONFIG_APP_NAME = os.getenv('HEROKUCONFIG_APP_NAME', '') +ON_HEROKU = bool(HEROKUCONFIG_APP_NAME) + +if ON_HEROKU: + ALLOWED_HOSTS = ['*'] +else: + ALLOWED_HOSTS = [] # Read OH settings from .env/environment variables @@ -38,15 +44,15 @@ OH_CLIENT_ID = os.getenv('OH_CLIENT_ID') OH_CLIENT_SECRET = os.getenv('OH_CLIENT_SECRET') -# Read config from config.yaml -yaml_content = open('config.yaml').readlines() -YAML_CONFIG = yaml.load(''.join(yaml_content)) -YAML_CONFIG['file_tags_string'] = str(YAML_CONFIG['file_tags']) - -APP_BASE_URL = YAML_CONFIG['app_base_url'] +# Set up base URL. +DEFAULT_BASE_URL = ('https://{}.herokuapp.com'.format(HEROKUCONFIG_APP_NAME) if + ON_HEROKU else 'http://127.0.0.1:5000') +APP_BASE_URL = os.getenv('APP_BASE_URL', DEFAULT_BASE_URL) if APP_BASE_URL[-1] == "/": APP_BASE_URL = APP_BASE_URL[:-1] +# Admin account password for configuration. +ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD', '') # Application definition @@ -57,7 +63,8 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'oh_connection.apps.OhConnectionConfig' + 'oh_connection.apps.OhConnectionConfig', + 'project_admin.apps.ProjectAdminConfig', ] MIDDLEWARE = [ @@ -96,9 +103,16 @@ # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases -db_from_env = dj_database_url.config(conn_max_age=500) +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} -DATABASES = {'default': db_from_env} +if ON_HEROKU: + db_from_env = dj_database_url.config(conn_max_age=500) + DATABASES = {'default': db_from_env} # Password validation diff --git a/oh_data_uploader/urls.py b/oh_data_uploader/urls.py index 2e7d047..8a076b3 100644 --- a/oh_data_uploader/urls.py +++ b/oh_data_uploader/urls.py @@ -13,10 +13,11 @@ 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ -from django.conf.urls import url,include +from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ + url(r'^project-admin/', include('project_admin.urls')), url(r'^', include('oh_connection.urls')), url(r'^admin/', admin.site.urls), ] diff --git a/project_admin/__init__.py b/project_admin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project_admin/admin.py b/project_admin/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/project_admin/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/project_admin/apps.py b/project_admin/apps.py new file mode 100644 index 0000000..13e6ef2 --- /dev/null +++ b/project_admin/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ProjectAdminConfig(AppConfig): + name = 'project_admin' diff --git a/project_admin/management/__init__.py b/project_admin/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project_admin/management/commands/__init__.py b/project_admin/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project_admin/management/commands/init_proj_config.py b/project_admin/management/commands/init_proj_config.py new file mode 100644 index 0000000..507c156 --- /dev/null +++ b/project_admin/management/commands/init_proj_config.py @@ -0,0 +1,27 @@ +from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand, CommandError + +from project_admin.models import ProjectConfiguration + +User = get_user_model() + + +class Command(BaseCommand): + help = 'Initializes or re-initializes data in ProjectConfiguration.' + + def load_md_text(self, filename): + return ''.join( + open('_descriptions/{}'.format(filename), 'r').readlines()) + + def handle(self, *args, **options): + config, _ = ProjectConfiguration.objects.get_or_create(id=1) + + config.about = self.load_md_text('about.md') + config.homepage_text = self.load_md_text('index.md') + config.faq = self.load_md_text('faq.md') + config.overview = self.load_md_text('overview.md') + config.upload_description = self.load_md_text('upload_description.md') + + config.save() + + User.objects.get_or_create(username='admin') diff --git a/project_admin/migrations/0001_initial.py b/project_admin/migrations/0001_initial.py new file mode 100644 index 0000000..c9bf2cc --- /dev/null +++ b/project_admin/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 2.0.1 on 2018-01-30 11:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='ProjectConfiguration', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('project_title', models.CharField(default='My Open Humans Project', max_length=50)), + ('oh_client_id', models.CharField(default='replace-with-Open-Humans-Client-ID', max_length=40)), + ('oh_client_secret', models.CharField(default='replace-with-Open-Humans-Client-secret', max_length=128)), + ('project_description', models.TextField(default='This template demonstrates how you can run your own Open Humans data upload project.', help_text='Project description, displayed on the front page.')), + ('oh_activity_page', models.TextField(default='https://www.openhumans.org/activity/your-project', help_text='The URL where we can find your project in Open Humans.')), + ('file_description', models.TextField(default="This is an example file that doesn't have any meaning.", help_text='Description of the type of data being uploaded.')), + ('file_tags', models.TextField(default='["tags", "are a good way to", "describe the files you are uploading"]', help_text='List of tags that describe file uploads, stored as a JSON-formatted array')), + ('logo_url', models.TextField(blank=True, help_text='URL with logo of your project. If left blank, /static/default_logo.png will be used.')), + ('more_info_url', models.TextField(blank=True, help_text='URL to find more information about your project.')), + ('about', models.TextField(blank=True)), + ('faq', models.TextField(blank=True)), + ('homepage_text', models.TextField(blank=True)), + ('overview', models.TextField(blank=True)), + ('upload_description', models.TextField(blank=True)), + ], + ), + ] diff --git a/project_admin/migrations/__init__.py b/project_admin/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project_admin/models.py b/project_admin/models.py new file mode 100644 index 0000000..31dfb11 --- /dev/null +++ b/project_admin/models.py @@ -0,0 +1,53 @@ +import json + +from django.core.exceptions import ValidationError +from django.db import models + + +class ProjectConfiguration(models.Model): + """ + Store project configuration. + """ + project_title = models.CharField( + max_length=50, + default='My Open Humans Project') + oh_client_id = models.CharField( + max_length=40, + default='replace-with-Open-Humans-Client-ID') + oh_client_secret = models.CharField( + max_length=128, + default='replace-with-Open-Humans-Client-secret') + + project_description = models.TextField( + default='This template demonstrates how you can run your own Open ' + 'Humans data upload project.', + help_text='Project description, displayed on the front page.') + oh_activity_page = models.TextField( + help_text='The URL where we can find your project in Open Humans.', + default='https://www.openhumans.org/activity/your-project') + file_description = models.TextField( + help_text='Description of the type of data being uploaded.', + default="This is an example file that does not have any meaning.") + file_tags = models.TextField( + help_text='List of tags that describe file uploads, stored as a ' + 'JSON-formatted array', + default=json.dumps(['tags', 'are a good way to', + 'describe the files you are uploading'])) + logo_url = models.TextField( + blank=True, + help_text='URL with logo of your project. If left blank, ' + '/static/default_logo.png will be used.') + more_info_url = models.TextField( + blank=True, + help_text='URL to find more information about your project.') + + about = models.TextField(blank=True) + faq = models.TextField(blank=True) + homepage_text = models.TextField(blank=True) + overview = models.TextField(blank=True) + upload_description = models.TextField(blank=True) + + def save(self, *args, **kwargs): + if ProjectConfiguration.objects.exists() and not self.pk: + raise ValidationError('Only one ProjectConfiguration allowed') + return super(ProjectConfiguration, self).save(*args, **kwargs) diff --git a/project_admin/templates/project_admin/config-general-settings.html b/project_admin/templates/project_admin/config-general-settings.html new file mode 100644 index 0000000..83ca332 --- /dev/null +++ b/project_admin/templates/project_admin/config-general-settings.html @@ -0,0 +1,23 @@ +{% extends 'oh_connection/application.html' %} + +{% block content %} +{% load utilities %} + +
    + {% csrf_token %} +

    + Project title: +

    +

    + Project description: +

    +

    + More info URL (optional): +

    +

    + Logo URL (optional): +

    + +
    + +{% endblock %} diff --git a/project_admin/templates/project_admin/config-homepage-text.html b/project_admin/templates/project_admin/config-homepage-text.html new file mode 100644 index 0000000..738800f --- /dev/null +++ b/project_admin/templates/project_admin/config-homepage-text.html @@ -0,0 +1,17 @@ +{% extends 'oh_connection/application.html' %} + +{% block content %} +{% load utilities %} + +
    + {% csrf_token %} +

    + Homepage text: +

    +

    + +

    + +
    + +{% endblock %} diff --git a/project_admin/templates/project_admin/config-oh-settings.html b/project_admin/templates/project_admin/config-oh-settings.html new file mode 100644 index 0000000..e2b2352 --- /dev/null +++ b/project_admin/templates/project_admin/config-oh-settings.html @@ -0,0 +1,20 @@ +{% extends 'oh_connection/application.html' %} + +{% block content %} +{% load utilities %} + +
    + {% csrf_token %} +

    + Client ID: +

    +

    + Client secret: +

    +

    + Activity page URL: +

    + +
    + +{% endblock %} diff --git a/project_admin/templates/project_admin/home.html b/project_admin/templates/project_admin/home.html new file mode 100644 index 0000000..76ff85e --- /dev/null +++ b/project_admin/templates/project_admin/home.html @@ -0,0 +1,125 @@ +{% extends 'oh_connection/application.html' %} + +{% block content %} +{% load utilities %} + +

    Current Project Configuration

    + +

    General information

    + + + + + + + + + + + + + + + + + + + + + + + + +
    Project title{{ config.project_title }}
    Project description{{ config.project_description }}
    More info URL (optional) + {% if config.more_info_url %} + {{ config.more_info_url }} + {% else %} + Not defined. + {% endif %} +
    Project logo URL + {% if config.logo_url %} + {{ config.logo_url }} + {% else %} + Not defined. Logo defaults to: + /static/default_logo.png + {% endif %} +
    Project logo + Brand +
    + +

    + Configure general settings +

    + +
    +

    Open Humans configurations

    + + + + + + + + + + + + + + + + +
    Open Humans client ID{{ config.oh_client_id }}
    Open Humans client secret{{ config.oh_client_secret }}
    Open Humans activity page URL{{ config.oh_activity_page }}
    + +

    + Configure Open Humans settings +

    + +
    +

    File information configurations

    + + + + + + + + + + + + +
    File description{{ config.file_description }}
    File tags{{ config.file_tags }}
    + +

    Webpage content

    + +

    About

    +
    + {{ config.about|markdown }} +
    + +

    FAQ

    +
    + {{ config.faq|markdown }} +
    + +

    Homepage text

    +
    + {{ config.homepage_text|markdown }} +
    + +

    + Configure homepage text +

    + +

    Overview

    +
    + {{ config.overview|markdown }} +
    + +

    Upload Description

    +
    + {{ config.upload_description|markdown }} +
    + +{% endblock %} diff --git a/project_admin/templates/project_admin/login.html b/project_admin/templates/project_admin/login.html new file mode 100644 index 0000000..c92bd72 --- /dev/null +++ b/project_admin/templates/project_admin/login.html @@ -0,0 +1,19 @@ +{% extends 'oh_connection/application.html' %} + +{% block content %} + +{% if error %} + +{% endif %} + +
    + {% csrf_token %} + Admin password: + +
    + +{% endblock %} diff --git a/project_admin/tests.py b/project_admin/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/project_admin/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/project_admin/urls.py b/project_admin/urls.py new file mode 100644 index 0000000..f7a1e46 --- /dev/null +++ b/project_admin/urls.py @@ -0,0 +1,16 @@ +from django.conf.urls import url + +from . import views + +app_name = 'project-admin' + +urlpatterns = [ + url(r'^$', views.home, name='home'), + url(r'^config-general-settings/?$', + views.config_general_settings, name='config-general-settings'), + url(r'^config-oh-settings/?$', + views.config_oh_settings, name='config-oh-settings'), + url(r'^config-homepage-text/?$', + views.config_homepage_text, name='config-homepage-text'), + url(r'^login/?$', views.admin_login, name='login'), +] diff --git a/project_admin/views.py b/project_admin/views.py new file mode 100644 index 0000000..d9ac6de --- /dev/null +++ b/project_admin/views.py @@ -0,0 +1,102 @@ +from django.conf import settings +from django.contrib.auth import get_user_model, login +from django.shortcuts import redirect, render + +from .models import ProjectConfiguration + +User = get_user_model() + + +def home(request): + """ + Main page for project config. + """ + if request.user.username == 'admin': + project_config = ProjectConfiguration.objects.get(id=1) + context = {'project_config': project_config} + return render(request, 'project_admin/home.html', context=context) + else: + return redirect('project-admin:login') + + +def config(request): + """ + Edit project config. + """ + project_config = ProjectConfiguration.objects.get(id=1) + context = {'project_config': project_config} + return render(request, 'project_admin/config.html', context=context) + + +def admin_login(request): + """ + Log in as project admin. + """ + if request.method == 'POST': + if not settings.ADMIN_PASSWORD: + return render(request, 'project_admin/login.html', + context={'error': 'ADMIN_PASSWORD environment ' + 'variable needs to be set!'}) + elif request.POST['password'] == settings.ADMIN_PASSWORD: + admin_user = User.objects.get(username='admin') + login(request, admin_user, + backend='django.contrib.auth.backends.ModelBackend') + return redirect('project-admin:home') + else: + return render(request, 'project_admin/login.html', + context={'error': 'Password incorrect.'}) + + return render(request, 'project_admin/login.html') + + +def config_general_settings(request): + """ + Update Open Humans project configuration. + """ + if request.user.username != 'admin': + return redirect('project-admin:home') + + if request.method == 'POST': + project_config = ProjectConfiguration.objects.get(id=1) + project_config.project_title = request.POST['project_title'] + project_config.project_description = request.POST['project_description'] + project_config.more_info_url = request.POST['more_info_url'] + project_config.logo_url = request.POST['logo_url'] + project_config.save() + return redirect('project-admin:home') + + return render(request, 'project_admin/config-general-settings.html') + + +def config_oh_settings(request): + """ + Update Open Humans project configuration. + """ + if request.user.username != 'admin': + return redirect('project-admin:home') + + if request.method == 'POST': + project_config = ProjectConfiguration.objects.get(id=1) + project_config.oh_client_id = request.POST['client_id'] + project_config.oh_client_secret = request.POST['client_secret'] + project_config.oh_activity_page = request.POST['activity_page'] + project_config.save() + return redirect('project-admin:home') + + return render(request, 'project_admin/config-oh-settings.html') + + +def config_homepage_text(request): + """ + Update Open Humans project configuration. + """ + if request.user.username != 'admin': + return redirect('project-admin:home') + + if request.method == 'POST': + project_config = ProjectConfiguration.objects.get(id=1) + project_config.homepage_text = request.POST['homepage_text'] + project_config.save() + return redirect('project-admin:home') + + return render(request, 'project_admin/config-homepage-text.html') diff --git a/static/example_logo.png b/static/default_logo.png similarity index 100% rename from static/example_logo.png rename to static/default_logo.png