diff --git a/compose/django/Dockerfile b/compose/django/Dockerfile index d84436c4..c1e14c52 100644 --- a/compose/django/Dockerfile +++ b/compose/django/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-slim +FROM python:3.10-slim AS neurovault ENV PYTHONUNBUFFERED 1 ENV PYTHONDONTWRITEBYTECODE 1 @@ -10,6 +10,54 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ libpq-dev \ git +RUN apt-get -y update \ + && apt-get install -y wget subversion && \ + wget -qO- ftp://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/6.0.0/freesurfer-Linux-centos6_x86_64-stable-pub-v6.0.0.tar.gz | tar zxv -C /opt \ + --exclude='freesurfer/trctrain' \ + --exclude='freesurfer/subjects/fsaverage_sym' \ + --exclude='freesurfer/subjects/fsaverage3' \ + --exclude='freesurfer/subjects/fsaverage4' \ + --exclude='freesurfer/subjects/fsaverage5' \ + --exclude='freesurfer/subjects/fsaverage6' \ + --exclude='freesurfer/subjects/cvs_avg35' \ + --exclude='freesurfer/subjects/cvs_avg35_inMNI152' \ + --exclude='freesurfer/subjects/bert' \ + --exclude='freesurfer/subjects/V1_average' \ + --exclude='freesurfer/average/mult-comp-cor' \ + --exclude='freesurfer/lib/cuda' \ + --exclude='freesurfer/lib/qt' && \ + apt-get install -y tcsh bc tar libgomp1 perl-modules curl && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Set up the environment +ENV OS Linux +ENV FS_OVERRIDE 0 +ENV FIX_VERTEX_AREA= +ENV SUBJECTS_DIR /opt/freesurfer/subjects +ENV FSF_OUTPUT_FORMAT nii.gz +ENV MNI_DIR /opt/freesurfer/mni +ENV LOCAL_DIR /opt/freesurfer/local +ENV FREESURFER_HOME /opt/freesurfer +ENV FSFAST_HOME /opt/freesurfer/fsfast +ENV MINC_BIN_DIR /opt/freesurfer/mni/bin +ENV MINC_LIB_DIR /opt/freesurfer/mni/lib +ENV MNI_DATAPATH /opt/freesurfer/mni/data +ENV FMRI_ANALYSIS_DIR /opt/freesurfer/fsfast +ENV PERL5LIB /opt/freesurfer/mni/lib/perl5/5.8.5 +ENV MNI_PERL5LIB /opt/freesurfer/mni/lib/perl5/5.8.5 +ENV PATH /opt/freesurfer/bin:/opt/freesurfer/fsfast/bin:/opt/freesurfer/tktools:/opt/freesurfer/mni/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH +RUN echo "cHJpbnRmICJrcnp5c3p0b2YuZ29yZ29sZXdza2lAZ21haWwuY29tXG41MTcyXG4gKkN2dW12RVYzelRmZ1xuRlM1Si8yYzFhZ2c0RVxuIiA+IC9vcHQvZnJlZXN1cmZlci9saWNlbnNlLnR4dAo=" | base64 -d | sh + +RUN svn export --force https://github.com/NeuroVault/neurovault_data/trunk/pycortex_datastore /usr/local/share/pycortex/ +RUN apt-get update && apt-get -y install tcsh libglu1-mesa libxmu6 +RUN /opt/freesurfer/bin/mri_convert /opt/freesurfer/subjects/fsaverage/mri/brain.mgz /opt/freesurfer/subjects/fsaverage/mri/brain.nii.gz +RUN mkdir /usr/local/share/pycortex/db/fsaverage/transforms/ +RUN pip install tornado + +RUN apt-get update && apt-get -y install zip +RUN wget https://ndownloader.figshare.com/files/6891069 -O icbm.zip && unzip icbm.zip -d /opt/freesurfer/subjects/ && rm icbm.zip + COPY ./compose/django/requirements.txt requirements.txt RUN pip install -r requirements.txt diff --git a/compose/django/Dockerfile_fs b/compose/django/Dockerfile_fs deleted file mode 100644 index aa724fda..00000000 --- a/compose/django/Dockerfile_fs +++ /dev/null @@ -1,49 +0,0 @@ -FROM neurovault/neurovault - -RUN apt-get -y update \ - && apt-get install -y wget subversion && \ - wget -qO- ftp://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/6.0.0/freesurfer-Linux-centos6_x86_64-stable-pub-v6.0.0.tar.gz | tar zxv -C /opt \ - --exclude='freesurfer/trctrain' \ - --exclude='freesurfer/subjects/fsaverage_sym' \ - --exclude='freesurfer/subjects/fsaverage3' \ - --exclude='freesurfer/subjects/fsaverage4' \ - --exclude='freesurfer/subjects/fsaverage5' \ - --exclude='freesurfer/subjects/fsaverage6' \ - --exclude='freesurfer/subjects/cvs_avg35' \ - --exclude='freesurfer/subjects/cvs_avg35_inMNI152' \ - --exclude='freesurfer/subjects/bert' \ - --exclude='freesurfer/subjects/V1_average' \ - --exclude='freesurfer/average/mult-comp-cor' \ - --exclude='freesurfer/lib/cuda' \ - --exclude='freesurfer/lib/qt' && \ - apt-get install -y tcsh bc tar libgomp1 perl-modules curl && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Set up the environment -ENV OS Linux -ENV FS_OVERRIDE 0 -ENV FIX_VERTEX_AREA= -ENV SUBJECTS_DIR /opt/freesurfer/subjects -ENV FSF_OUTPUT_FORMAT nii.gz -ENV MNI_DIR /opt/freesurfer/mni -ENV LOCAL_DIR /opt/freesurfer/local -ENV FREESURFER_HOME /opt/freesurfer -ENV FSFAST_HOME /opt/freesurfer/fsfast -ENV MINC_BIN_DIR /opt/freesurfer/mni/bin -ENV MINC_LIB_DIR /opt/freesurfer/mni/lib -ENV MNI_DATAPATH /opt/freesurfer/mni/data -ENV FMRI_ANALYSIS_DIR /opt/freesurfer/fsfast -ENV PERL5LIB /opt/freesurfer/mni/lib/perl5/5.8.5 -ENV MNI_PERL5LIB /opt/freesurfer/mni/lib/perl5/5.8.5 -ENV PATH /opt/freesurfer/bin:/opt/freesurfer/fsfast/bin:/opt/freesurfer/tktools:/opt/freesurfer/mni/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH -RUN echo "cHJpbnRmICJrcnp5c3p0b2YuZ29yZ29sZXdza2lAZ21haWwuY29tXG41MTcyXG4gKkN2dW12RVYzelRmZ1xuRlM1Si8yYzFhZ2c0RVxuIiA+IC9vcHQvZnJlZXN1cmZlci9saWNlbnNlLnR4dAo=" | base64 -d | sh - -RUN svn export --force https://github.com/NeuroVault/neurovault_data/trunk/pycortex_datastore /usr/local/share/pycortex/ -RUN apt-get update && apt-get -y install tcsh libglu1-mesa libxmu6 -RUN /opt/freesurfer/bin/mri_convert /opt/freesurfer/subjects/fsaverage/mri/brain.mgz /opt/freesurfer/subjects/fsaverage/mri/brain.nii.gz -RUN mkdir /usr/local/share/pycortex/db/fsaverage/transforms/ -RUN pip install tornado - -RUN apt-get update && apt-get -y install zip -RUN wget https://ndownloader.figshare.com/files/6891069 -O icbm.zip && unzip icbm.zip -d /opt/freesurfer/subjects/ && rm icbm.zip diff --git a/compose/django/Dockerfile_multi b/compose/django/Dockerfile_multi deleted file mode 100644 index 631d55f6..00000000 --- a/compose/django/Dockerfile_multi +++ /dev/null @@ -1,68 +0,0 @@ -FROM python:3.10-slim AS neurovault - -ENV PYTHONUNBUFFERED 1 -ENV PYTHONDONTWRITEBYTECODE 1 - -RUN apt-get update && apt-get install --no-install-recommends -y \ - # dependencies for building Python packages - build-essential \ - # psycopg2 dependencies - libpq-dev \ - git - -COPY ./compose/django/requirements.txt requirements.txt -RUN pip install -r requirements.txt - -WORKDIR /code - -EXPOSE 8000 - -FROM neurovault AS neurovault_fs - -RUN apt-get -y update \ - && apt-get install -y wget subversion && \ - wget -qO- ftp://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/6.0.0/freesurfer-Linux-centos6_x86_64-stable-pub-v6.0.0.tar.gz | tar zxv -C /opt \ - --exclude='freesurfer/trctrain' \ - --exclude='freesurfer/subjects/fsaverage_sym' \ - --exclude='freesurfer/subjects/fsaverage3' \ - --exclude='freesurfer/subjects/fsaverage4' \ - --exclude='freesurfer/subjects/fsaverage5' \ - --exclude='freesurfer/subjects/fsaverage6' \ - --exclude='freesurfer/subjects/cvs_avg35' \ - --exclude='freesurfer/subjects/cvs_avg35_inMNI152' \ - --exclude='freesurfer/subjects/bert' \ - --exclude='freesurfer/subjects/V1_average' \ - --exclude='freesurfer/average/mult-comp-cor' \ - --exclude='freesurfer/lib/cuda' \ - --exclude='freesurfer/lib/qt' && \ - apt-get install -y tcsh bc tar libgomp1 perl-modules curl && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -# Set up the environment -ENV OS Linux -ENV FS_OVERRIDE 0 -ENV FIX_VERTEX_AREA= -ENV SUBJECTS_DIR /opt/freesurfer/subjects -ENV FSF_OUTPUT_FORMAT nii.gz -ENV MNI_DIR /opt/freesurfer/mni -ENV LOCAL_DIR /opt/freesurfer/local -ENV FREESURFER_HOME /opt/freesurfer -ENV FSFAST_HOME /opt/freesurfer/fsfast -ENV MINC_BIN_DIR /opt/freesurfer/mni/bin -ENV MINC_LIB_DIR /opt/freesurfer/mni/lib -ENV MNI_DATAPATH /opt/freesurfer/mni/data -ENV FMRI_ANALYSIS_DIR /opt/freesurfer/fsfast -ENV PERL5LIB /opt/freesurfer/mni/lib/perl5/5.8.5 -ENV MNI_PERL5LIB /opt/freesurfer/mni/lib/perl5/5.8.5 -ENV PATH /opt/freesurfer/bin:/opt/freesurfer/fsfast/bin:/opt/freesurfer/tktools:/opt/freesurfer/mni/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH -RUN echo "cHJpbnRmICJrcnp5c3p0b2YuZ29yZ29sZXdza2lAZ21haWwuY29tXG41MTcyXG4gKkN2dW12RVYzelRmZ1xuRlM1Si8yYzFhZ2c0RVxuIiA+IC9vcHQvZnJlZXN1cmZlci9saWNlbnNlLnR4dAo=" | base64 -d | sh - -RUN svn export --force https://github.com/NeuroVault/neurovault_data/trunk/pycortex_datastore /usr/local/share/pycortex/ -RUN apt-get update && apt-get -y install tcsh libglu1-mesa libxmu6 -RUN /opt/freesurfer/bin/mri_convert /opt/freesurfer/subjects/fsaverage/mri/brain.mgz /opt/freesurfer/subjects/fsaverage/mri/brain.nii.gz -RUN mkdir /usr/local/share/pycortex/db/fsaverage/transforms/ -RUN pip install tornado - -RUN apt-get update && apt-get -y install zip -RUN wget https://ndownloader.figshare.com/files/6891069 -O icbm.zip && unzip icbm.zip -d /opt/freesurfer/subjects/ && rm icbm.zip diff --git a/docker-compose.yml b/docker-compose.yml index 593cfd22..59a95de0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,9 +4,9 @@ services: restart: always build: context: . - dockerfile: ./compose/django/Dockerfile_multi - target: neurovault_fs - image: neurovault/neurovault_fs + dockerfile: ./compose/django/Dockerfile + target: neurovault + image: neurovault/neurovault command: python manage.py runserver 0.0.0.0:8000 ports: - "8000:8000" @@ -35,7 +35,7 @@ services: worker: build: context: . - dockerfile: ./compose/django/Dockerfile_multi + dockerfile: ./compose/django/Dockerfile target: neurovault image: neurovault/neurovault command: celery -A neurovault.celery worker -Q default -n default@%h diff --git a/neurovault/apps/statmaps/tests/test_data/statmaps/saccade.I_C.MNI__SetA_Tstat.nii.gz b/neurovault/apps/statmaps/tests/test_data/statmaps/saccade.I_C.MNI__SetA_Tstat.nii.gz deleted file mode 100644 index 369cd493..00000000 Binary files a/neurovault/apps/statmaps/tests/test_data/statmaps/saccade.I_C.MNI__SetA_Tstat.nii.gz and /dev/null differ diff --git a/neurovault/apps/statmaps/tests/test_data/statmaps/saccade.I_C.MNI__SetA_mean.nii.gz b/neurovault/apps/statmaps/tests/test_data/statmaps/saccade.I_C.MNI__SetA_mean.nii.gz deleted file mode 100644 index 7e4c4134..00000000 Binary files a/neurovault/apps/statmaps/tests/test_data/statmaps/saccade.I_C.MNI__SetA_mean.nii.gz and /dev/null differ diff --git a/neurovault/apps/users/forms.py b/neurovault/apps/users/forms.py index 98ea77de..25ab2ffc 100644 --- a/neurovault/apps/users/forms.py +++ b/neurovault/apps/users/forms.py @@ -2,7 +2,6 @@ from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm from django.utils.translation import gettext_lazy as _ -from oauth2_provider.models import Application class UserCreateForm(UserCreationForm): @@ -57,18 +56,3 @@ def save(self, commit=True): def clean_password(self): return "" - - -class ApplicationEditForm(forms.ModelForm): - name = forms.CharField(required=True) - - class Meta: - model = Application - fields = ( - "name", - "client_id", - "client_secret", - "client_type", - "authorization_grant_type", - "redirect_uris", - ) diff --git a/neurovault/apps/users/migrations/0001_initial.py b/neurovault/apps/users/migrations/0001_initial.py index 56eb50ea..97d049fd 100644 --- a/neurovault/apps/users/migrations/0001_initial.py +++ b/neurovault/apps/users/migrations/0001_initial.py @@ -5,12 +5,15 @@ def create_default_app(apps, schema_editor): + ''' Application = apps.get_registered_model("oauth2_provider", "Application") Application.objects.get_or_create( name=settings.DEFAULT_OAUTH_APP_NAME, redirect_uris=["http://localhost"], pk=settings.DEFAULT_OAUTH_APPLICATION_ID, ) + ''' + pass class Migration(migrations.Migration): diff --git a/neurovault/apps/users/templates/base_settings.html b/neurovault/apps/users/templates/base_settings.html index 4982cc0a..c44c17c9 100644 --- a/neurovault/apps/users/templates/base_settings.html +++ b/neurovault/apps/users/templates/base_settings.html @@ -15,12 +15,6 @@

Settings

- - {% block setting_content %}{% endblock %} {% endblock %} diff --git a/neurovault/apps/users/templates/oauth2_provider/application_confirm_delete.html b/neurovault/apps/users/templates/oauth2_provider/application_confirm_delete.html deleted file mode 100644 index e58d8358..00000000 --- a/neurovault/apps/users/templates/oauth2_provider/application_confirm_delete.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base_settings.html" %} - -{% block developerappstab %}class="active"{% endblock%} - -{% block setting_content %} -
-
-
- -

Delete this application?

-

{{ application.name }}

-
- {% csrf_token %} -
- - Cancel -
-
-
-
-
-{% endblock setting_content %} diff --git a/neurovault/apps/users/templates/oauth2_provider/application_form.html b/neurovault/apps/users/templates/oauth2_provider/application_form.html deleted file mode 100644 index e9946aff..00000000 --- a/neurovault/apps/users/templates/oauth2_provider/application_form.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "base_settings.html" %} -{% load crispy_forms_tags %} - -{% block developerappstab %}class="active"{% endblock%} - -{% block setting_content %} -
-
-

- {% block app-form-title %} - Edit application / {{ application.name }} - {% endblock app-form-title %} -

-
- {% csrf_token %} - {{ form | crispy }} - -
- - {% block app-form-delete-button %}Delete{% endblock app-form-delete-button %} - - Cancel -
-
-
-
-{% endblock %} diff --git a/neurovault/apps/users/templates/oauth2_provider/application_list.html b/neurovault/apps/users/templates/oauth2_provider/application_list.html deleted file mode 100644 index f333cde4..00000000 --- a/neurovault/apps/users/templates/oauth2_provider/application_list.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "base_settings.html" %} - -{% block developerappstab %}class="active"{% endblock%} - -{% block setting_content %} -
-
- - {% if messages %} - {% include "_messages_block.html" with messages=messages %} - {% endif %} - - {% if applications %} - - - - - - - - - {% for application in applications %} - - - - - {% endfor %} - -
NameClient ID
{{ application.name }} - - {{ application.client_id }} -
- - Register new application - {% else %} -
-

No developer applications

-

Developer applications are used for enhanced access to the NeuroVault API.

-

Register an application to get started.

-
- {% endif %} -
- -
-{% endblock setting_content %} diff --git a/neurovault/apps/users/templates/oauth2_provider/application_registration_form.html b/neurovault/apps/users/templates/oauth2_provider/application_registration_form.html deleted file mode 100644 index fed17257..00000000 --- a/neurovault/apps/users/templates/oauth2_provider/application_registration_form.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "oauth2_provider/application_form.html" %} - -{% block app-form-title %}Register a new application{% endblock app-form-title %} - -{% block app-form-action-url %}{% url 'users:developerapps_register' %}{% endblock app-form-action-url %} - -{% block app-form-delete-button %}{% endblock app-form-delete-button %} - -{% block app-form-back-url %}{% url 'users:developerapps_list' %}"{% endblock app-form-back-url %} diff --git a/neurovault/apps/users/templates/oauth2_provider/authorize.html b/neurovault/apps/users/templates/oauth2_provider/authorize.html deleted file mode 100644 index b0a594b1..00000000 --- a/neurovault/apps/users/templates/oauth2_provider/authorize.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
- {% if not error %} -
-

Authorize application

- {% csrf_token %} - - {% for field in form %} - {% if field.is_hidden %} - {{ field }} - {% endif %} - {% endfor %} - -

{{ application.name }} requires following permissions to access your account

- - - {{ form.errors }} - {{ form.non_field_errors }} - -
-
- - -
-
-
- - {% else %} -

Error: {{ error.error }}

-

{{ error.description }}

- {% endif %} -
-{% endblock %} diff --git a/neurovault/apps/users/templates/oauth2_provider/connection_confirm_delete.html b/neurovault/apps/users/templates/oauth2_provider/connection_confirm_delete.html deleted file mode 100644 index 6f4cf910..00000000 --- a/neurovault/apps/users/templates/oauth2_provider/connection_confirm_delete.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base_settings.html" %} - -{% block connectedappstab %}class="active"{% endblock%} - -{% block setting_content %} -
-
-
- -

Revoke the app authorization?

-

{{ object.name }} will no longer be able to access your account on your behalf.

-
- {% csrf_token %} -
- - Cancel -
-
-
-
-
-{% endblock setting_content %} diff --git a/neurovault/apps/users/templates/oauth2_provider/connection_list.html b/neurovault/apps/users/templates/oauth2_provider/connection_list.html deleted file mode 100644 index 498a4b17..00000000 --- a/neurovault/apps/users/templates/oauth2_provider/connection_list.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "base_settings.html" %} - -{% block connectedappstab %}class="active"{% endblock%} - -{% block setting_content %} -
-
- - {% if messages %} - {% include "_messages_block.html" with messages=messages %} - {% endif %} - - {% if object_list %} - - - - - - - - - - - {% for rt in object_list %} - - - - - - {% endfor %} - -
You have authorized these applications to access your NeuroVault account on your behalf.
NameOwner
{{ rt.application.name }} - {{ rt.application.user.username }} - Revoke access -
- {% else %} -
-

No connected applications

-

You have no applications authorized to access your account on your behalf via NeuroVault API.

-
- {% endif %} -
- -
-{% endblock setting_content %} diff --git a/neurovault/apps/users/templates/oauth2_provider/personal_token_confirm_delete.html b/neurovault/apps/users/templates/oauth2_provider/personal_token_confirm_delete.html deleted file mode 100644 index b01bb391..00000000 --- a/neurovault/apps/users/templates/oauth2_provider/personal_token_confirm_delete.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base_settings.html" %} - -{% block personalaccesstokenstab %}class="active"{% endblock%} - -{% block setting_content %} -
-
-
- -

Delete the token?

-

Any applications or scripts using this token will no longer be able to access the NeuroVault API.

-
- {% csrf_token %} -
- - Cancel -
-
-
-
-
-{% endblock setting_content %} diff --git a/neurovault/apps/users/templates/oauth2_provider/personal_token_list.html b/neurovault/apps/users/templates/oauth2_provider/personal_token_list.html deleted file mode 100644 index e5123909..00000000 --- a/neurovault/apps/users/templates/oauth2_provider/personal_token_list.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "base_settings.html" %} - -{% block personalaccesstokenstab %}class="active"{% endblock%} - -{% block setting_content %} -
-
- - {% if messages %} - {% include "_messages_block.html" with messages=messages %} - {% endif %} - - {% if object_list %} - - - - - - - - - - {% for token in object_list %} - - - - - {% endfor %} - -
Tokens you have generated that can be used to access the NeuroVault API.
Token
{{ token.token }}Delete -
- {% include "_personal_token_form.html" %} - {% else %} -
-

No personal access tokens

-

Personal access tokens are used to access your account via the NeuroVault API.

-
- {% include "_personal_token_form.html" %} -
-
- {% endif %} -
- -
-{% endblock setting_content %} diff --git a/neurovault/apps/users/templates/show_token.html b/neurovault/apps/users/templates/show_token.html new file mode 100644 index 00000000..0647a901 --- /dev/null +++ b/neurovault/apps/users/templates/show_token.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block content %} +

Token

+
{{ token }}
+

+ Regenerate Token +{% endblock %} diff --git a/neurovault/apps/users/tests/test_application_views.py b/neurovault/apps/users/tests/test_application_views.py deleted file mode 100644 index 31ae1459..00000000 --- a/neurovault/apps/users/tests/test_application_views.py +++ /dev/null @@ -1,93 +0,0 @@ -from django.test import TestCase -from django.contrib.auth import get_user_model -from django.urls import reverse - -from oauth2_provider.models import Application - -UserModel = get_user_model() - - -class BaseTest(TestCase): - def setUp(self): - self.user_password = "random" - self.user = UserModel.objects.create_user( - "neurouser", "neurouser@example.com", self.user_password - ) - - def tearDown(self): - self.user.delete() - - -class TestApplicationRegistrationView(BaseTest): - def test_application_registration_user(self): - self.client.login(username=self.user.username, password=self.user_password) - - form_data = { - "name": "Random App", - "client_id": "client_id", - "client_secret": "client_secret", - "client_type": Application.CLIENT_CONFIDENTIAL, - "redirect_uris": "http://example.com", - "authorization_grant_type": Application.GRANT_AUTHORIZATION_CODE, - } - - response = self.client.post(reverse("users:developerapps_register"), form_data) - self.assertEqual(response.status_code, 302) - - app = Application.objects.get(name="Random App") - self.assertEqual(app.user.username, self.user.username) - - -class TestApplicationViews(BaseTest): - def _create_application(self, name, user): - app = Application.objects.create( - name=name, - redirect_uris="http://example.com", - client_type=Application.CLIENT_CONFIDENTIAL, - authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE, - user=user, - ) - return app - - def setUp(self): - super(TestApplicationViews, self).setUp() - self.app = self._create_application("app 1", self.user) - - def tearDown(self): - super(TestApplicationViews, self).tearDown() - self.app.delete() - - def test_application_list(self): - self.client.login(username=self.user.username, password=self.user_password) - - response = self.client.get(reverse("users:developerapps_list")) - self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["object_list"]), 1) - - def test_application_detail(self): - self.client.login(username=self.user.username, password=self.user_password) - - response = self.client.get( - reverse("users:developerapps_update", args=(self.app.pk,)) - ) - self.assertEqual(response.status_code, 200) - - def test_application_update(self): - self.client.login(username=self.user.username, password=self.user_password) - - form_data = { - "name": "Changed App", - "client_id": "client_id", - "client_secret": "client_secret", - "client_type": Application.CLIENT_CONFIDENTIAL, - "redirect_uris": "http://example.com", - "authorization_grant_type": Application.GRANT_AUTHORIZATION_CODE, - } - - response = self.client.post( - reverse("users:developerapps_update", args=(self.app.pk,)), form_data - ) - self.assertEqual(response.status_code, 302) - - app = Application.objects.get(name="Changed App") - self.assertEqual(app.user.username, self.user.username) diff --git a/neurovault/apps/users/tests/test_oauth.py b/neurovault/apps/users/tests/test_oauth.py deleted file mode 100644 index 63741fa0..00000000 --- a/neurovault/apps/users/tests/test_oauth.py +++ /dev/null @@ -1,67 +0,0 @@ -from django.test import TestCase, Client -from django.test.utils import override_settings -from django.http import HttpResponse -from django.urls import reverse, re_path, include - -from django.contrib.auth import get_user_model - -from rest_framework import permissions -from rest_framework.views import APIView -from oauth2_provider.contrib.rest_framework import OAuth2Authentication -from oauth2_provider.models import AccessToken - -UserModel = get_user_model() - - -class MockView(APIView): - permission_classes = (permissions.IsAuthenticated,) - - def get(self, request): - return HttpResponse({"a": 1, "b": 2, "c": 3}) - - -class OAuth2View(MockView): - authentication_classes = [OAuth2Authentication] - - -urlpatterns = [ - re_path(r"^oauth2/", include("oauth2_provider.urls")), - re_path(r"^oauth2-test/$", OAuth2View.as_view()), - re_path(r"^accounts/", include("neurovault.apps.users.urls")), -] - - -@override_settings(ROOT_URLCONF=__name__) -class TestPersonalAccessTokens(TestCase): - def setUp(self): - self.user_password = "l0n6 l1v3 7h3 k1n6!" - self.user = UserModel.objects.create_user( - "bernardo", "bernardo@example.com", self.user_password - ) - self.client = Client() - - def tearDown(self): - self.user.delete() - - def _create_authorization_header(self, token): - return "Bearer {0}".format(token) - - def test_authentication_empty(self): - response = self.client.get("/oauth2-test/") - self.assertEqual(response.status_code, 401) - - def test_authentication_denied(self): - auth = self._create_authorization_header("fake-token") - response = self.client.get("/oauth2-test/", HTTP_AUTHORIZATION=auth) - self.assertEqual(response.status_code, 401) - - def test_authentication_allow(self): - self.client.login(username=self.user, password=self.user_password) - response = self.client.post(reverse("users:token_create")) - self.assertEqual(response.status_code, 302) - - access_token = AccessToken.objects.get(user_id=self.user) - - auth = self._create_authorization_header(access_token) - response = self.client.get("/oauth2-test/", HTTP_AUTHORIZATION=auth) - self.assertEqual(response.status_code, 200) diff --git a/neurovault/apps/users/tests/test_tokens.py b/neurovault/apps/users/tests/test_tokens.py new file mode 100644 index 00000000..494d94ba --- /dev/null +++ b/neurovault/apps/users/tests/test_tokens.py @@ -0,0 +1,64 @@ +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse + +from rest_framework.authtoken.models import Token +from rest_framework.test import APIClient, APITestCase +from rest_framework import status + +UserModel = get_user_model() + +class BaseTest(TestCase): + def setUp(self): + self.user_password = "random" + self.user = UserModel.objects.create_user( + "neurouser", "neurouser@example.com", self.user_password + ) + + def tearDown(self): + self.user.delete() + +class TokenGenerationTest(BaseTest): + def test_token_list(self): + self.client.login(username=self.user.username, password=self.user_password) + response = self.client.get(reverse('users:token_list')) + self.assertEqual(response.status_code, 200) + + def test_token_list_unauthorized(self): + response = self.client.get(reverse('users:token_list')) + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, "/accounts/login/?next=/accounts/token/") + + def test_token_create(self): + self.client.login(username=self.user.username, password=self.user_password) + token = Token.objects.filter(user=self.user).first() + self.assertIsNone(token) + + response = self.client.get(reverse('users:token_list')) + token = Token.objects.filter(user=self.user).first() + self.assertIsNotNone(Token) + self.assertEqual(response.status_code, 200) + + response = self.client.get(reverse('users:token_create')) + new_token = Token.objects.filter(user=self.user).first() + self.assertNotEqual(token, new_token) + + def test_token_create_unauthorized(self): + response = self.client.post(reverse('users:token_create')) + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, "/accounts/login/?next=/accounts/token/new") + + + def test_token_auth(self): + self.client.login(username=self.user.username, password=self.user_password) + self.client.get(reverse('users:token_list')) + token = Token.objects.filter(user=self.user).first() + + client = APIClient() + client.credentials(HTTP_AUTHORIZATION='Token ' + token.key) + post_dict = { + "name": "Test Create Collection", + } + response = client.post("/api/collections/", post_dict) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data["name"], post_dict["name"]) diff --git a/neurovault/apps/users/urls.py b/neurovault/apps/users/urls.py index 29f34cb6..87e165c8 100644 --- a/neurovault/apps/users/urls.py +++ b/neurovault/apps/users/urls.py @@ -7,20 +7,10 @@ create_user, password_change_done, delete_profile, + view_token ) from django.contrib.auth.views import LoginView, LogoutView from django.contrib.auth import views as auth_views -from oauth2_provider.views.application import ApplicationList -from .views import ( - ApplicationRegistration, - ApplicationUpdate, - ApplicationDelete, - ConnectionList, - ConnectionDelete, - PersonalTokenList, - PersonalTokenCreate, - PersonalTokenDelete, -) admin.autodiscover() @@ -69,35 +59,8 @@ re_path(r"^profile/edit$", edit_user, name="edit_user"), re_path(r"^profile/delete$", delete_profile, name="delete_profile"), re_path(r"^profile/.*$", view_profile, name="my_profile"), - re_path(r"^tokens/$", PersonalTokenList.as_view(), name="token_list"), - re_path(r"^tokens/new$", PersonalTokenCreate.as_view(), name="token_create"), - re_path( - r"^tokens/(?P\d+)/delete/$", - PersonalTokenDelete.as_view(), - name="token_delete", - ), - re_path(r"^connections/$", ConnectionList.as_view(), name="connection_list"), - re_path( - r"^connections/(?P\d+)/revoke/$", - ConnectionDelete.as_view(), - name="connection_revoke", - ), - re_path(r"^applications/$", ApplicationList.as_view(), name="developerapps_list"), - re_path( - r"^applications/register/$", - ApplicationRegistration.as_view(), - name="developerapps_register", - ), - re_path( - r"^applications/(?P\d+)/$", - ApplicationUpdate.as_view(), - name="developerapps_update", - ), - re_path( - r"^applications/(?P\d+)/delete/$", - ApplicationDelete.as_view(), - name="developerapps_delete", - ), + re_path(r"^token/$", view_token, {'regenerate': False}, name="token_list"), + re_path(r"^token/new$", view_token, {'regenerate': True}, name="token_create"), re_path(r"^(?P[A-Za-z0-9@/./+/-/_]+)/$", view_profile, name="profile"), ] diff --git a/neurovault/apps/users/views.py b/neurovault/apps/users/views.py index f82e8708..94b15dec 100644 --- a/neurovault/apps/users/views.py +++ b/neurovault/apps/users/views.py @@ -12,11 +12,13 @@ from django.utils.crypto import get_random_string from django.views.generic import View, CreateView, UpdateView, DeleteView, ListView + from braces.views import LoginRequiredMixin -from oauth2_provider.views.application import ApplicationOwnerIsUserMixin -from oauth2_provider.models import RefreshToken, AccessToken, Application +from rest_framework.authtoken.models import Token +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated -from .forms import UserEditForm, UserCreateForm, ApplicationEditForm +from .forms import UserEditForm, UserCreateForm def view_profile(request, username=None): @@ -80,155 +82,15 @@ def password_change_done(request): return HttpResponseRedirect(reverse("users:password_change")) - -# def login(request): -# return render(request, 'home.html', { -# 'plus_id': getattr(settings, 'SOCIAL_AUTH_GOOGLE_PLUS_KEY', None) -# }, RequestContext(request)) - - -class PersonalTokenUserIsRequestUserMixin(LoginRequiredMixin): - - """ - This mixin is used to provide an Connection queryset filtered by the - current request.user. - """ - - fields = "__all__" - - def get_queryset(self): - return AccessToken.objects.filter( - user=self.request.user, application_id=settings.DEFAULT_OAUTH_APPLICATION_ID - ) - - -class PersonalTokenList(PersonalTokenUserIsRequestUserMixin, ListView): - model = AccessToken - template_name = "oauth2_provider/personal_token_list.html" - - -class PersonalTokenCreate(LoginRequiredMixin, View): - def post(self, request, *args, **kwargs): - application = Application.objects.get(pk=settings.DEFAULT_OAUTH_APPLICATION_ID) - AccessToken.objects.create( - user=self.request.user, - token=get_random_string(length=settings.OAUTH_PERSONAL_TOKEN_LENGTH), - application=application, - expires=datetime.date(datetime.MAXYEAR, 12, 30), - scope="read write", - ) - messages.success(self.request, "The new token has been successfully generated.") - - return HttpResponseRedirect(reverse("users:token_list")) - - -class PersonalTokenDelete(PersonalTokenUserIsRequestUserMixin, DeleteView): - template_name = "oauth2_provider/personal_token_confirm_delete.html" - success_url = reverse_lazy("users:token_list") - success_message = "The token has been successfully deleted." - - def delete(self, request, *args, **kwargs): - self.object = self.get_object() - success_url = self.get_success_url() - self.object.revoke() - messages.success(self.request, self.success_message) - - return HttpResponseRedirect(success_url) - - -class ApplicationRegistration(LoginRequiredMixin, CreateView): - - """ - View used to register a new Application for the request.user - """ - - form_class = ApplicationEditForm - template_name = "oauth2_provider/application_registration_form.html" - - def get_success_url(self): - return reverse("users:developerapps_list") - - def form_valid(self, form): - form.instance.user = self.request.user - messages.success( - self.request, "The application has been successfully registered." - ) - - return super(ApplicationRegistration, self).form_valid(form) - - -class ApplicationUpdate(ApplicationOwnerIsUserMixin, UpdateView): - - """ - View used to update an application owned by the request.user - """ - - context_object_name = "application" - template_name = "oauth2_provider/application_form.html" - - # Reset the inherited fields attribute and use a form_class instead - fields = None - form_class = ApplicationEditForm - - def get_success_url(self): - return reverse("users:developerapps_list") - - def form_valid(self, form): - messages.success(self.request, "The application has been successfully updated.") - return super(ApplicationUpdate, self).form_valid(form) - - -class ApplicationDelete(ApplicationOwnerIsUserMixin, DeleteView): - - """ - View used to delete an application owned by the request.user - """ - - context_object_name = "application" - success_url = reverse_lazy("users:developerapps_list") - template_name = "oauth2_provider/application_confirm_delete.html" - success_message = "The application has been successfully deleted." - - def delete(self, request, *args, **kwargs): - messages.success(self.request, self.success_message) - return super(ApplicationDelete, self).delete(request, *args, **kwargs) - - -class ConnectionList(LoginRequiredMixin, ListView): - template_name = "oauth2_provider/connection_list.html" - - def get_queryset(self): - return RefreshToken.objects.filter(user=self.request.user).distinct( - "application" - ) - - -class ConnectionDelete(LoginRequiredMixin, DeleteView): - template_name = "oauth2_provider/connection_confirm_delete.html" - success_url = reverse_lazy("connection_list") - success_message = "The application authorization has been successfully " "revoked." - - def _refresh_token_queryset(self, user, application_id): - return RefreshToken.objects.filter(user=user, application_id=application_id) - - def get_object(self): - pk = self.kwargs.get(self.pk_url_kwarg) - - refresh_token = self._refresh_token_queryset(self.request.user, pk).first() - if not refresh_token: - raise Http404("No application connection found.") - - return refresh_token.application - - def delete(self, request, *args, **kwargs): - self.object = self.get_object() - success_url = self.get_success_url() - - token_list = self._refresh_token_queryset(self.request.user, self.object.id) - - for refresh_token in token_list: - refresh_token.revoke() - - messages.success(self.request, self.success_message) - - return HttpResponseRedirect(success_url) +@login_required +def view_token(request, regenerate=False): + if regenerate: + try: + old_token = Token.objects.get(user=request.user) + old_token.delete() + except Token.DoesNotExist: + pass + token = Token.objects.create(user=request.user) + else: + token, _ = Token.objects.get_or_create(user=request.user) + return render(request, 'show_token.html', {'token': token.key}) diff --git a/neurovault/settings.py b/neurovault/settings.py index 620eb687..3d45e3d0 100644 --- a/neurovault/settings.py +++ b/neurovault/settings.py @@ -155,13 +155,13 @@ "django.contrib.admin", "social_django", "rest_framework", + "rest_framework.authtoken", "taggit", "crispy_forms", "polymorphic", "django_cleanup", "file_resubmit", "guardian", - "oauth2_provider", "django_celery_results", ] @@ -208,7 +208,7 @@ "rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly" ], "DEFAULT_AUTHENTICATION_CLASSES": ( - "oauth2_provider.contrib.rest_framework.OAuth2Authentication", + "rest_framework.authentication.TokenAuthentication", ), "DEFAULT_RENDERER_CLASSES": ( "neurovault.api.utils.ExplicitUnicodeJSONRenderer", @@ -218,7 +218,6 @@ "UNICODE_JSON": True, } -OAUTH2_PROVIDER = {"REQUEST_APPROVAL_PROMPT": "auto"} LOGIN_REDIRECT_URL = "/my_collections/" # LOGIN_URL = '/login-form/' @@ -249,10 +248,6 @@ if os.path.exists("/usr/local/share/pycortex/db/fsaverage"): STATICFILES_DIRS = ( - ( - "pycortex-resources", - "/usr/local/lib/python2.7/site-packages/cortex/webgl/resources", - ), ("pycortex-ctmcache", "/usr/local/share/pycortex/db/fsaverage/cache"), ) @@ -281,12 +276,6 @@ ANONYMOUS_USER_ID = -1 -DEFAULT_OAUTH_APPLICATION_ID = -1 -DEFAULT_OAUTH_APP_NAME = "DefaultOAuthApp" -DEFAULT_OAUTH_APP_OWNER_ID = -2 -DEFAULT_OAUTH_APP_OWNER_USERNAME = "DefaultAppOwner" -OAUTH_PERSONAL_TOKEN_LENGTH = 40 - # Bogus secret key. try: from secrets import * diff --git a/neurovault/urls.py b/neurovault/urls.py index 7da1bef4..7c8360d1 100644 --- a/neurovault/urls.py +++ b/neurovault/urls.py @@ -18,17 +18,6 @@ "CognitiveAtlasTasks": CognitiveAtlasTaskSitemap, } -""" Do these need to live outside o/* oauth urls included below? -oauth_urlpatterns = [ - re_path(r'^authorize/$', oauth_view.AuthorizationView.as_view(), - name="authorize"), - re_path(r'^token/$', oauth_view.TokenView.as_view(), - name="token"), - re_path(r'^revoke_token/$', oauth_view.RevokeTokenView.as_view(), - name="revoke-token"), -] -""" - urlpatterns = [ re_path("", include("social_django.urls", namespace="social")), re_path(r"^", include("neurovault.apps.main.urls")), @@ -39,5 +28,4 @@ re_path(r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")), re_path(r"^sitemap\.xml$", index, {"sitemaps": sitemaps}), re_path(r"^sitemap-(?P

.+)\.xml$", sitemap, {"sitemaps": sitemaps}), - path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")), ] diff --git a/production.yml b/production.yml index dd11bc02..d01f8056 100644 --- a/production.yml +++ b/production.yml @@ -5,10 +5,10 @@ volumes: services: django: restart: always - image: neurovault/neurovault_fs + image: neurovault/neurovault build: context: . - dockerfile: ./compose/django/Dockerfile_fs + dockerfile: ./compose/django/Dockerfile command: ./compose/django/prod_start ports: - "5000:5000" diff --git a/scripts/preparing_AHBA_data.py b/scripts/preparing_AHBA_data.py new file mode 100644 index 00000000..0fa4bb9c --- /dev/null +++ b/scripts/preparing_AHBA_data.py @@ -0,0 +1,139 @@ +''' + +Requirements from old dockerfile: + +RUN apt-get update && apt-get install -y \ + libhdf5-dev \ + libhdf5-8 && \ + apt-get clean autoclean + +RUN pip install numpy==1.11.0 +RUN pip install nibabel \ + tables==3.3.0 \ + h5py==2.6.0 \ + pandas==0.20.3 \ + pybraincompare==0.1.18 + +''' +import urllib.request, urllib.parse, urllib.error +import zipfile +import os +import numpy as np +import nibabel as nb +import numpy.linalg as npl +import pandas as pd +from pybraincompare.mr.datasets import get_standard_mask + +# Downloading microarray data +urls = ["http://human.brain-map.org/api/v2/well_known_file_download/178238387", + "http://human.brain-map.org/api/v2/well_known_file_download/178238373", + "http://human.brain-map.org/api/v2/well_known_file_download/178238359", + "http://human.brain-map.org/api/v2/well_known_file_download/178238316", + "http://human.brain-map.org/api/v2/well_known_file_download/178238266", + "http://human.brain-map.org/api/v2/well_known_file_download/178236545"] + +donor_ids = [""] +download_dir = "/ahba_data" +os.makedirs(download_dir) + +for i, url in enumerate(urls): + print("Downloading %s" % url) + urllib.request.urlretrieve(url, os.path.join(download_dir, "donor%d.zip" % (i + 1))) + zipfile.ZipFile(os.path.join(download_dir, "donor%d.zip" % (i + 1))) + +# Dowloading MNI coordinates +urllib.request.urlretrieve( + "https://raw.githubusercontent.com/chrisfilo/alleninf/master/alleninf/data/corrected_mni_coordinates.csv", + os.path.join(download_dir, "corrected_mni_coordinates.csv")) + +samples = pd.read_csv(os.path.join(download_dir, "corrected_mni_coordinates.csv"), index_col=0) + +mni = get_standard_mask(voxdim=4) + +reduced_coord = [] + +for coord_mni in samples[['corrected_mni_x', 'corrected_mni_y', 'corrected_mni_z']].values: + sample_counts = np.zeros(mni.shape, dtype=int) + coord_data = [int(round(i)) for i in nb.affines.apply_affine(npl.inv(mni.get_affine()), coord_mni)] + sample_counts[coord_data[0], + coord_data[1], + coord_data[2]] = 1 + out_vector = sample_counts[mni.get_data()!=0] + idx = out_vector.argmax() + if idx == (out_vector == 1.0).sum() == 0: + idx = np.nan + reduced_coord.append(idx) + +samples["reduced_coordinate"] = reduced_coord + +#Downloading gene selections list +urllib.request.urlretrieve( + "http://science.sciencemag.org/highwire/filestream/631209/field_highwire_adjunct_files/2/Richiardi_Data_File_S2.xlsx", + os.path.join(download_dir, "Richiardi_Data_File_S2.xlsx")) + +donors = ["H0351.2001", "H0351.2002", "H0351.1009", "H0351.1012", "H0351.1015", "H0351.1016"] + +with zipfile.ZipFile(os.path.join(download_dir, "donor1.zip")) as z: + with z.open("Probes.csv") as f: + probe_info_df = pd.read_csv(f, index_col=0) + +# concatenating data from all donors +dfs = [] +for i, donor_id in enumerate(donors): + with zipfile.ZipFile(os.path.join(download_dir, "donor%d.zip" % (i + 1))) as z: + with z.open("MicroarrayExpression.csv") as f: + df = pd.read_csv(f, header=None, index_col=0) + + # Dropping probes without entrez_id + df.drop(probe_info_df.index[probe_info_df['entrez_id'].isnull()], inplace=True) + + with z.open("SampleAnnot.csv") as f: + sample_annot_df = pd.read_csv(f, index_col="well_id").join(samples, how="inner") + + df.columns = list(sample_annot_df["reduced_coordinate"]) + # removing out side of the brain coordinates + df.drop(sample_annot_df.index[sample_annot_df.reduced_coordinate.isnull()], axis=1, inplace=True) + # averaging measurements from similar locations + df = df.groupby(level=0, axis=1).mean() + + df.columns = pd.MultiIndex.from_tuples([(donor_id, c,) for c in list(df.columns)], + names=['donor_id', 'reduced_coordinate']) + dfs.append(df) + +expression_data = pd.concat(dfs, copy=False, axis=1) +del dfs + +probe_info_df.drop(probe_info_df.index[probe_info_df['entrez_id'].isnull()], inplace=True) + +multiindex = [] +for index in expression_data.index: + entrez_id = int(probe_info_df.loc[index]["entrez_id"]) + multiindex.append((entrez_id, index,)) +expression_data.index = pd.MultiIndex.from_tuples(multiindex, names=['entrez_id', 'probe_id']) + +means = expression_data.mean(axis=1) +idx_maxs = means.groupby(level=0).idxmax() +expression_data = expression_data.reindex(idx_maxs) + +expression_data.index = expression_data.index.droplevel(1) + +multiindex = [] +for index in probe_info_df.index: + entrez_id = int(probe_info_df.loc[index]["entrez_id"]) + multiindex.append((entrez_id, index,)) +probe_info_df.index = pd.MultiIndex.from_tuples(multiindex, names=['entrez_id', 'probe_id']) +probe_info_df = probe_info_df.reindex(idx_maxs) +probe_info_df.index = probe_info_df.index.droplevel(1) +probe_info_df.to_csv(os.path.join(download_dir, 'probe_info_max1.csv')) + +assert (expression_data.index[0] in list(probe_info_df.index)) +assert (expression_data.shape == (20787, 3072)) + +with pd.HDFStore(os.path.join(download_dir, 'store_max1_reduced.h5'), 'w') as store: + for donor_id in donors: + store.append(donor_id.replace(".", "_"), expression_data[donor_id]) + +# Removing downloaded files +for i, url in enumerate(urls): + os.remove(os.path.join(download_dir, "donor%d.zip" % (i + 1))) + diff --git a/scripts/subcortex_drewUpdated.nii.gz b/scripts/subcortex_drewUpdated.nii.gz new file mode 100644 index 00000000..d7cda4a0 Binary files /dev/null and b/scripts/subcortex_drewUpdated.nii.gz differ diff --git a/scripts/subcortex_mask.npy b/scripts/subcortex_mask.npy new file mode 100644 index 00000000..80b48cbe Binary files /dev/null and b/scripts/subcortex_mask.npy differ