├── backend
├── api
│ ├── __init__.py
│ └── schema.py
├── assets
│ └── .gitkeep
├── locale
│ └── .gitkeep
├── pycon_ml
│ ├── __init__.py
│ ├── settings
│ │ ├── __init__.py
│ │ ├── prod.py
│ │ ├── dev.py
│ │ ├── test.py
│ │ └── base.py
│ ├── wsgi.py
│ └── urls.py
├── tests
│ ├── __init__.py
│ ├── users
│ │ ├── __init__.py
│ │ ├── test_models.py
│ │ └── test_managers.py
│ ├── api
│ │ ├── users
│ │ │ ├── __init__.py
│ │ │ ├── test_logout.py
│ │ │ ├── test_me.py
│ │ │ ├── test_login.py
│ │ │ └── test_register.py
│ │ ├── challenges
│ │ │ ├── __init__.py
│ │ │ ├── test_query_challenge_by_id.py
│ │ │ ├── test_query_challenges.py
│ │ │ ├── test_upload_submission.py
│ │ │ ├── test_query_submissions.py
│ │ │ └── test_query_leaderboard.py
│ │ ├── fixtures.py
│ │ ├── test_root.py
│ │ └── utils.py
│ └── conftest.py
├── users
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── schema.py
│ │ ├── types.py
│ │ └── mutations.py
│ ├── migrations
│ │ ├── __init__.py
│ │ ├── 0002_user_is_staff.py
│ │ ├── 0003_user_username.py
│ │ ├── 0006_auto_20180410_1907.py
│ │ ├── 0004_auto_20180408_2249.py
│ │ ├── 0005_team.py
│ │ └── 0001_initial.py
│ ├── templates
│ │ └── users
│ │ │ ├── post-login.html
│ │ │ └── test-login.html
│ ├── forms.py
│ ├── managers.py
│ ├── models.py
│ └── admin.py
├── challenges
│ ├── __init__.py
│ ├── api
│ │ ├── __init__.py
│ │ ├── types.py
│ │ ├── mutations.py
│ │ └── schema.py
│ ├── migrations
│ │ ├── __init__.py
│ │ ├── 0004_auto_20180420_0626.py
│ │ ├── 0005_auto_20180421_0728.py
│ │ ├── 0001_initial.py
│ │ ├── 0003_auto_20180420_0623.py
│ │ └── 0002_submission.py
│ ├── apps.py
│ ├── admin.py
│ ├── models.py
│ └── forms.py
├── pytest.ini
├── .env.sample
├── manage.py
├── Pipfile
├── .editorconfig
├── .gitignore
├── Dockerfile
└── Pipfile.lock
├── .dockerignore
├── frontend
├── .eslintrc
├── src
│ ├── index.css
│ ├── client
│ │ └── index.js
│ ├── index.js
│ ├── authentication
│ │ └── index.js
│ ├── pages
│ │ ├── login
│ │ │ ├── index.js
│ │ │ └── form.js
│ │ ├── profile
│ │ │ └── index.js
│ │ ├── leaderboard
│ │ │ └── index.js
│ │ ├── submission
│ │ │ ├── index.js
│ │ │ └── form.js
│ │ ├── register
│ │ │ └── index.js
│ │ └── home
│ │ │ └── index.js
│ ├── components
│ │ ├── private-route
│ │ │ └── index.js
│ │ └── header
│ │ │ └── index.js
│ └── app.js
├── public
│ ├── favicon.ico
│ ├── data
│ │ ├── variants.csv
│ │ ├── variants_year.db
│ │ └── description.txt
│ ├── manifest.json
│ └── index.html
├── Dockerfile
├── .gitignore
└── package.json
├── nginx
├── Dockerfile
└── nginx.conf
├── .gitignore
├── .env.sample
├── docker-compose.yml
└── README.md
/backend/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/locale/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/backend/pycon_ml/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/users/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/challenges/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/tests/users/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/users/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/challenges/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/pycon_ml/settings/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/tests/api/users/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/users/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/challenges/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/tests/api/challenges/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app"
3 | }
4 |
--------------------------------------------------------------------------------
/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx
2 |
3 | COPY nginx.conf /etc/nginx/nginx.conf
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | .DS_Store
3 | score_calculator.py
4 | ground_truth.csv
5 |
--------------------------------------------------------------------------------
/backend/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | DJANGO_SETTINGS_MODULE=pycon_ml.settings.test
3 |
--------------------------------------------------------------------------------
/.env.sample:
--------------------------------------------------------------------------------
1 | DEBUG=False
2 | SECRET_KEY=change-this-in-prod
3 | ALLOWED_HOSTS=localhost,example
--------------------------------------------------------------------------------
/backend/.env.sample:
--------------------------------------------------------------------------------
1 | SECRET_KEY=a!e(-@8mbndh=dontusethis$1#&!y9@*-+ett5#-p
2 | DEBUG=True
3 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/backend/pycon_ml/settings/prod.py:
--------------------------------------------------------------------------------
1 | from .base import * # noqa
2 |
3 |
4 | SECRET_KEY = env('SECRET_KEY')
5 |
--------------------------------------------------------------------------------
/backend/pycon_ml/settings/dev.py:
--------------------------------------------------------------------------------
1 | from .base import * # noqa
2 |
3 | SECRET_KEY = 'do not use this in production'
4 |
--------------------------------------------------------------------------------
/backend/pycon_ml/settings/test.py:
--------------------------------------------------------------------------------
1 | from .base import * # noqa
2 |
3 | SECRET_KEY = 'do not use this in production'
4 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leriomaggio/machine-learning-contest/master/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/src/client/index.js:
--------------------------------------------------------------------------------
1 | import ApolloClient from "apollo-boost";
2 |
3 | export const client = new ApolloClient();
4 |
--------------------------------------------------------------------------------
/backend/challenges/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class ChallengesConfig(AppConfig):
5 | name = 'challenges'
6 |
--------------------------------------------------------------------------------
/frontend/public/data/variants.csv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leriomaggio/machine-learning-contest/master/frontend/public/data/variants.csv
--------------------------------------------------------------------------------
/frontend/public/data/variants_year.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leriomaggio/machine-learning-contest/master/frontend/public/data/variants_year.db
--------------------------------------------------------------------------------
/backend/challenges/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Challenge, Submission
4 |
5 |
6 | admin.site.register(Challenge)
7 | admin.site.register(Submission)
8 |
--------------------------------------------------------------------------------
/backend/tests/api/fixtures.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.fixture()
5 | def graphql_client(client):
6 | from .utils import GraphQLClient
7 |
8 | return GraphQLClient(client)
9 |
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './app';
5 |
6 | ReactDOM.render( , document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/backend/tests/api/test_root.py:
--------------------------------------------------------------------------------
1 | def test_root(graphql_client):
2 | resp = graphql_client.query('''
3 | {
4 | hello
5 | }
6 | ''')
7 |
8 | assert resp['data']['hello'] == "world"
9 |
--------------------------------------------------------------------------------
/backend/pycon_ml/wsgi.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from django.core.wsgi import get_wsgi_application
4 |
5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pycon_ml.settings.prod")
6 |
7 | application = get_wsgi_application()
8 |
--------------------------------------------------------------------------------
/frontend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:9
2 |
3 | RUN curl -o- -L https://yarnpkg.com/install.sh | bash
4 |
5 | RUN mkdir /app
6 |
7 | COPY ./package.json ./yarn.* /tmp/
8 | WORKDIR /tmp
9 | RUN yarn install
10 | WORKDIR /app
11 | RUN ln -s /tmp/node_modules .
12 | COPY . /app/
13 |
14 | RUN yarn build
15 |
--------------------------------------------------------------------------------
/backend/pycon_ml/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from django.contrib import admin
3 | from django.views.decorators.csrf import csrf_exempt
4 |
5 | from graphene_django.views import GraphQLView
6 |
7 |
8 | urlpatterns = [
9 | path('admin/', admin.site.urls),
10 | path('graphql', csrf_exempt(GraphQLView.as_view(graphiql=True))),
11 | ]
12 |
--------------------------------------------------------------------------------
/backend/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | import environ
6 |
7 | environ.Env.read_env()
8 |
9 |
10 | if __name__ == "__main__":
11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pycon_ml.settings.prod")
12 |
13 | from django.core.management import execute_from_command_line
14 |
15 | execute_from_command_line(sys.argv)
16 |
--------------------------------------------------------------------------------
/backend/users/templates/users/post-login.html:
--------------------------------------------------------------------------------
1 | {% comment %} TODO: design {% endcomment %}
2 |
3 | logging in...
4 |
5 |
6 |
16 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/backend/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 |
3 | url = "https://pypi.python.org/simple"
4 | verify_ssl = true
5 | name = "pypi"
6 |
7 |
8 | [packages]
9 |
10 | django = "*"
11 | django-environ = "*"
12 | graphene = "*"
13 | graphene-django = "*"
14 | django-model-utils = "*"
15 | "psycopg2" = "*"
16 | uwsgi = "*"
17 | pandas = "*"
18 | sklearn = "*"
19 | scipy = "*"
20 |
21 |
22 | [dev-packages]
23 |
24 | pytest = "*"
25 | pytest-django = "*"
26 | "flake8" = "*"
27 |
--------------------------------------------------------------------------------
/backend/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from users.models import User
4 |
5 | from .api.fixtures import * # noqa
6 |
7 |
8 | @pytest.fixture()
9 | def admin_user(db):
10 | user = User._default_manager.create_superuser(
11 | 'admin@example.com', 'password'
12 | )
13 |
14 | return user
15 |
16 |
17 | @pytest.fixture()
18 | def user(db):
19 | user = User._default_manager.create_user('user@example.com', 'password')
20 |
21 | return user
22 |
--------------------------------------------------------------------------------
/backend/challenges/migrations/0004_auto_20180420_0626.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.4 on 2018-04-20 06:26
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('challenges', '0003_auto_20180420_0623'),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name='submission',
15 | old_name='data',
16 | new_name='solution',
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/backend/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 | end_of_line = lf
9 | charset = utf-8
10 |
11 | [*.py]
12 | max_line_length = 119
13 |
14 | # Use 2 spaces for the HTML files
15 | [*.html]
16 | indent_size = 2
17 |
18 | # The JSON files contain newlines inconsistently
19 | [*.json]
20 | indent_size = 2
21 |
22 | # Makefiles always use tabs for indentation
23 | [Makefile]
24 | indent_style = tab
25 |
--------------------------------------------------------------------------------
/backend/challenges/migrations/0005_auto_20180421_0728.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.4 on 2018-04-21 07:28
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('challenges', '0004_auto_20180420_0626'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name='submission',
15 | name='score',
16 | field=models.CharField(editable=False, max_length=30),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[co]
2 | *.sw[po]
3 | .coverage
4 | .env
5 | *.orig
6 | .noseids
7 | *~
8 | #*
9 | .#*
10 | *.egg-info
11 | *.sqlite3
12 | notes.org
13 | .DS_Store
14 | .sass-cache
15 | pip-log.txt
16 | __pycache__/
17 | .cache/
18 | .vagrant/
19 | dist/
20 | node_modules/
21 | tmp/
22 | tags
23 | secrets/
24 | *.tfstate.backup
25 | media/*.jpg
26 | .elasticbeanstalk/
27 | # Elastic Beanstalk Files
28 | .elasticbeanstalk/*
29 | !.elasticbeanstalk/*.cfg.yml
30 | !.elasticbeanstalk/*.global.yml
31 | !.elasticbeanstalk/config.yml
32 | .pytest_cache/
33 |
--------------------------------------------------------------------------------
/backend/users/api/schema.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from .mutations import LoginMutation, LogoutMutation, RegisterMutation
4 |
5 | from .types import UserType
6 |
7 |
8 | class UserMutations():
9 | login = LoginMutation.Field()
10 | logout = LogoutMutation.Field()
11 | register = RegisterMutation.Field()
12 |
13 |
14 | class UserQuery():
15 | me = graphene.Field(UserType)
16 |
17 | def resolve_me(self, info):
18 | if not info.context.user.is_authenticated:
19 | raise ValueError('You are not logged in.')
20 |
21 | return info.context.user
22 |
--------------------------------------------------------------------------------
/backend/users/migrations/0002_user_is_staff.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11 on 2017-07-15 14:22
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('users', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='user',
17 | name='is_staff',
18 | field=models.BooleanField(default=False, verbose_name='is staff'),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/backend/api/schema.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from users.api.schema import UserMutations, UserQuery
4 | from challenges.api.schema import ChallengeQuery, ChallengeMutations
5 |
6 |
7 | class Query(
8 | ChallengeQuery,
9 | UserQuery,
10 | graphene.ObjectType
11 | ):
12 | hello = graphene.String()
13 |
14 | def resolve_hello(self, info):
15 | return 'world'
16 |
17 |
18 | class Mutations(
19 | UserMutations,
20 | ChallengeMutations,
21 | graphene.ObjectType
22 | ):
23 | pass
24 |
25 |
26 | schema = graphene.Schema(query=Query, mutation=Mutations)
27 |
--------------------------------------------------------------------------------
/backend/users/migrations/0003_user_username.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11 on 2017-07-22 16:11
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('users', '0002_user_is_staff'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='user',
17 | name='username',
18 | field=models.CharField(blank=True, max_length=100, null=True, verbose_name='username'),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/backend/challenges/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.4 on 2018-04-10 19:07
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = [
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='Challenge',
16 | fields=[
17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18 | ('name', models.CharField(max_length=100)),
19 | ],
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/backend/users/api/types.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene_django.types import DjangoObjectType
3 |
4 | from ..models import User, Team
5 |
6 |
7 | class TeamType(DjangoObjectType):
8 | class Meta:
9 | model = Team
10 | only_fields = ('name', )
11 |
12 |
13 | class UserType(DjangoObjectType):
14 | team = graphene.Field(TeamType)
15 |
16 | def resolve_team(self, context):
17 | try:
18 | return self.team
19 | except Team.DoesNotExist:
20 | return None
21 |
22 | class Meta:
23 | model = User
24 | only_fields = ('full_name', 'email')
25 |
--------------------------------------------------------------------------------
/backend/users/migrations/0006_auto_20180410_1907.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.4 on 2018-04-10 19:07
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 = [
11 | ('users', '0005_team'),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name='team',
17 | name='owner',
18 | field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='team', to=settings.AUTH_USER_MODEL),
19 | ),
20 | ]
21 |
--------------------------------------------------------------------------------
/backend/tests/api/users/test_logout.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.mark.django_db
5 | def test_correct_logout_removes_cookies_and_session(
6 | graphql_client,
7 | admin_user,
8 | ):
9 | graphql_client.client.force_login(admin_user)
10 |
11 | resp = graphql_client.query('''
12 | mutation Logout {
13 | logout {
14 | ok
15 | }
16 | }
17 | ''')
18 |
19 | assert 'errors' not in resp
20 |
21 | assert resp['data']['logout']['ok']
22 |
23 | cookies = graphql_client.client.cookies
24 |
25 | assert 'sessionid' in cookies
26 | assert cookies['sessionid'].value == ''
27 |
--------------------------------------------------------------------------------
/backend/users/migrations/0004_auto_20180408_2249.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.4 on 2018-04-08 22:49
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('users', '0003_user_username'),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name='user',
15 | name='username',
16 | ),
17 | migrations.AddField(
18 | model_name='user',
19 | name='full_name',
20 | field=models.CharField(blank=True, max_length=200, null=True, verbose_name='full name'),
21 | ),
22 | ]
23 |
--------------------------------------------------------------------------------
/backend/users/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 | from .models import User, Team
4 |
5 |
6 | class RegisterForm(forms.ModelForm):
7 | team_name = forms.CharField(max_length=200, required=False)
8 |
9 | class Meta:
10 | model = User
11 | fields = '__all__'
12 |
13 | def save(self, *args, **kwargs):
14 | obj = super().save(*args, **kwargs)
15 |
16 | obj.is_active = True
17 | obj.set_password(self.cleaned_data['password'])
18 | obj.save()
19 |
20 | if self.cleaned_data['team_name']:
21 | Team.objects.create(name=self.cleaned_data['team_name'], owner=obj)
22 |
23 | return obj
24 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | backend:
4 | build: backend
5 | depends_on:
6 | - postgres
7 | ports:
8 | - 8000
9 | environment:
10 | DATABASE_URL: postgres://pyconmlsite@postgres/pyconml
11 | SECRET_KEY: ${SECRET_KEY}
12 | ALLOWED_HOSTS: ${ALLOWED_HOSTS}
13 | DEBUG: ${DEBUG}
14 |
15 | volumes:
16 | - ./backend:/app
17 |
18 | postgres:
19 | image: postgres:9.6.2-alpine
20 | environment:
21 | POSTGRES_USER: pyconmlsite
22 | POSTGRES_DB: pyconml
23 |
24 | nginx:
25 | build: nginx
26 | volumes:
27 | - ./frontend/build:/app/build
28 | ports:
29 | - 80:80
30 | depends_on:
31 | - backend
32 |
--------------------------------------------------------------------------------
/backend/challenges/migrations/0003_auto_20180420_0623.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.4 on 2018-04-20 06:23
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('challenges', '0002_submission'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='submission',
15 | name='code',
16 | field=models.TextField(default=''),
17 | preserve_default=False,
18 | ),
19 | migrations.AlterField(
20 | model_name='submission',
21 | name='score',
22 | field=models.CharField(editable=False, max_length=10),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/frontend/src/authentication/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import localforage from "localforage";
3 |
4 | const KEY = "IS_AUTHENTICATED";
5 |
6 | export const isAuthenticated = () => localforage.getItem(KEY);
7 | export const setIsAuthenticated = value => localforage.setItem(KEY, value);
8 |
9 | export class IsAuthenticated extends React.Component {
10 | state = {
11 | loading: true,
12 | isAuthenticated: null
13 | };
14 |
15 | componentDidMount() {
16 | isAuthenticated().then(is =>
17 | this.setState({ loading: false, isAuthenticated: is })
18 | );
19 | }
20 |
21 | render() {
22 | if (this.state.loading) {
23 | return null;
24 | }
25 |
26 | return this.props.children(this.state.isAuthenticated);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/backend/challenges/models.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.db import models
3 |
4 | from model_utils.models import TimeStampedModel
5 |
6 |
7 | class Challenge(models.Model):
8 | name = models.CharField(max_length=100)
9 |
10 | def __str__(self):
11 | return self.name
12 |
13 |
14 | class Submission(TimeStampedModel):
15 | solution = models.TextField()
16 | code = models.TextField()
17 | score = models.CharField(max_length=30, editable=False)
18 | submitted_by = models.ForeignKey(
19 | settings.AUTH_USER_MODEL,
20 | on_delete=models.PROTECT
21 | )
22 | challenge = models.ForeignKey(Challenge, on_delete=models.PROTECT)
23 |
24 | def __str__(self):
25 | return f'Submission on {self.challenge} by {self.submitted_by}'
26 |
--------------------------------------------------------------------------------
/backend/users/migrations/0005_team.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.4 on 2018-04-09 21:16
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 = [
11 | ('users', '0004_auto_20180408_2249'),
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Team',
17 | fields=[
18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19 | ('name', models.CharField(max_length=200)),
20 | ('owner', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
21 | ],
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ml-submission-frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/icons": "^1.0.0-beta.42",
7 | "apollo-boost": "^0.1.4",
8 | "date-fns": "^1.29.0",
9 | "graphql": "^0.13.2",
10 | "idx": "^2.2.0",
11 | "localforage": "^1.7.1",
12 | "material-ui": "^1.0.0-beta.40",
13 | "material-ui-icons": "^1.0.0-beta.36",
14 | "react": "^16.3.1",
15 | "react-apollo": "^2.1.3",
16 | "react-dom": "^16.3.1",
17 | "react-router": "^4.2.0",
18 | "react-router-dom": "^4.2.2",
19 | "react-scripts": "1.1.4"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test --env=jsdom",
25 | "eject": "react-scripts eject"
26 | },
27 | "proxy": "http://localhost:8000"
28 | }
29 |
--------------------------------------------------------------------------------
/backend/challenges/api/types.py:
--------------------------------------------------------------------------------
1 | import graphene
2 | from graphene_django.types import DjangoObjectType
3 |
4 | from django.core.exceptions import ObjectDoesNotExist
5 |
6 | from ..models import Challenge, Submission
7 |
8 |
9 | class ChallengeType(DjangoObjectType):
10 | class Meta:
11 | model = Challenge
12 | only_fields = ('name', )
13 |
14 |
15 | class LeaderboardSubmissionType(DjangoObjectType):
16 | name = graphene.String()
17 |
18 | class Meta:
19 | model = Submission
20 | only_fields = ('score', )
21 |
22 | def resolve_name(root, info):
23 | user = root.submitted_by
24 |
25 | try:
26 | return user.team.name
27 | except ObjectDoesNotExist:
28 | pass
29 |
30 | return user.full_name
31 |
32 |
33 | class SubmissionType(DjangoObjectType):
34 | class Meta:
35 | model = Submission
36 |
--------------------------------------------------------------------------------
/backend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.6.4@sha256:bf0718e2882cabc144c9c07d758b09c65efc104a6ddc72a9a976f8b26f67c2ee
2 |
3 | RUN pip install pipenv
4 |
5 | # -- Install Application into container:
6 | RUN set -ex && mkdir /app
7 |
8 | WORKDIR /app
9 |
10 | # -- Adding Pipfiles
11 | COPY Pipfile Pipfile
12 | COPY Pipfile.lock Pipfile.lock
13 |
14 | RUN pipenv install --deploy --system
15 |
16 | # -- Install dependencies:
17 | COPY . /app/
18 |
19 | # # uWSGI will listen on this port
20 | EXPOSE 8000
21 |
22 | ENV DJANGO_SETTINGS_MODULE=pycon_ml.settings.prod
23 |
24 | # # uWSGI configuration (customize as needed):
25 | ENV UWSGI_WSGI_FILE=pycon_ml/wsgi.py UWSGI_HTTP=:8000 UWSGI_MASTER=1 UWSGI_WORKERS=3
26 | ENV UWSGI_THREADS=10 UWSGI_UID=1000 UWSGI_GID=2000 UWSGI_LAZY_APPS=1 UWSGI_WSGI_ENV_BEHAVIOR=holy
27 |
28 | # # Start uWSGI
29 | CMD ["uwsgi", "--http-auto-chunked", "--http-keepalive", "--static-map", "/static=/app/static"]
30 |
--------------------------------------------------------------------------------
/backend/tests/api/challenges/test_query_challenge_by_id.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from challenges.models import Challenge
4 |
5 |
6 | @pytest.mark.django_db
7 | def test_returns_none_when_no_challenge(graphql_client):
8 | resp = graphql_client.query('''
9 | query {
10 | challenge(id: 999) {
11 | name
12 | }
13 | }
14 | ''')
15 |
16 | assert 'errors' not in resp
17 |
18 | assert resp['data']['challenge'] is None
19 |
20 |
21 | @pytest.mark.django_db
22 | def test_returns_challenge(graphql_client):
23 | challenge = Challenge.objects.create(name='demo')
24 |
25 | resp = graphql_client.query(f'''
26 | query {{
27 | challenge(id: {challenge.id}) {{
28 | name
29 | }}
30 | }}
31 | ''')
32 |
33 | assert 'errors' not in resp
34 |
35 | assert resp['data']['challenge']['name'] == 'demo'
36 |
--------------------------------------------------------------------------------
/backend/tests/api/users/test_me.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.mark.django_db
5 | def test_returns_error_when_not_logged_in(graphql_client):
6 | resp = graphql_client.query('''
7 | query {
8 | me {
9 | fullName
10 | }
11 | }
12 | ''')
13 |
14 | assert 'errors' in resp
15 |
16 | assert resp['errors'][0]['message'] == 'You are not logged in.'
17 |
18 |
19 | @pytest.mark.django_db
20 | def test_returns_user_when_logged_in(graphql_client, user):
21 | user.full_name = 'Polly'
22 | user.save()
23 | graphql_client.client.force_login(user)
24 |
25 | resp = graphql_client.query('''
26 | query {
27 | me {
28 | fullName
29 | }
30 | }
31 | ''')
32 |
33 | assert 'errors' not in resp
34 |
35 | assert resp['data']['me']
36 | assert resp['data']['me']['fullName'] == 'Polly'
37 |
--------------------------------------------------------------------------------
/backend/tests/api/challenges/test_query_challenges.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from challenges.models import Challenge
4 |
5 |
6 | @pytest.mark.django_db
7 | def test_returns_empty_when_no_challenges(graphql_client):
8 | resp = graphql_client.query('''
9 | query {
10 | challenges {
11 | name
12 | }
13 | }
14 | ''')
15 |
16 | assert 'errors' not in resp
17 |
18 | assert resp['data']['challenges'] == []
19 |
20 |
21 | @pytest.mark.django_db
22 | def test_returns_challenges(graphql_client):
23 | Challenge.objects.create(name='demo')
24 |
25 | resp = graphql_client.query('''
26 | query {
27 | challenges {
28 | name
29 | }
30 | }
31 | ''')
32 |
33 | assert 'errors' not in resp
34 |
35 | assert len(resp['data']['challenges']) == 1
36 | assert resp['data']['challenges'][0]['name'] == 'demo'
37 |
--------------------------------------------------------------------------------
/frontend/src/pages/login/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { withStyles } from "material-ui/styles";
3 | import Typography from "material-ui/Typography";
4 | import LoginForm from "./form";
5 |
6 | import { Link } from "react-router-dom";
7 |
8 | const styles = theme => ({});
9 |
10 | class Login extends PureComponent {
11 | state = {};
12 |
13 | handleChange = name => event => {
14 | this.setState({
15 | [name]: event.target.value
16 | });
17 | };
18 |
19 | render() {
20 | return (
21 |
22 |
23 | Login
24 |
25 |
26 |
27 | Not registered? Register here
28 |
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
36 | export default withStyles(styles)(Login);
37 |
--------------------------------------------------------------------------------
/backend/tests/users/test_models.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import authenticate, get_user_model
2 |
3 | import pytest
4 | from users.models import User
5 | from users.managers import UserManager
6 |
7 |
8 | def test_that_get_user_model_returns_correct_model():
9 | assert get_user_model() == User
10 |
11 | @pytest.mark.django_db
12 | def test_user_manager():
13 | assert isinstance(User.objects, UserManager)
14 |
15 |
16 | @pytest.mark.django_db
17 | def test_create_user_and_authenticate():
18 | user = User.objects.create_user('lennon@thebeatles.com', 'johnpassword')
19 |
20 | authenticated_user = authenticate(
21 | username='lennon@thebeatles.com',
22 | password='johnpassword'
23 | )
24 |
25 | assert user == authenticated_user
26 |
27 | @pytest.mark.django_db
28 | def test_user_get_short_name():
29 | user = User.objects.create_user('lennon@thebeatles.com', 'johnpassword')
30 |
31 | assert 'lennon@thebeatles.com' == user.get_short_name()
32 |
--------------------------------------------------------------------------------
/frontend/src/components/private-route/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { Route, Redirect } from "react-router";
4 | import { isAuthenticated } from "../../authentication";
5 |
6 | export class PrivateRoute extends React.Component {
7 | state = {
8 | isAuthenticated: null
9 | };
10 |
11 | componentDidMount() {
12 | isAuthenticated().then(authenticated =>
13 | this.setState({
14 | isAuthenticated: !!authenticated
15 | })
16 | );
17 | }
18 |
19 | render() {
20 | const { component: Component, ...rest } = this.props;
21 |
22 | if (this.state.isAuthenticated === null) {
23 | return null;
24 | }
25 |
26 | return (
27 |
30 | this.state.isAuthenticated ? (
31 |
32 | ) : (
33 |
39 | )
40 | }
41 | />
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/backend/users/managers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.base_user import BaseUserManager
2 |
3 |
4 | class UserManager(BaseUserManager):
5 | use_in_migrations = True
6 |
7 | def _create_user(self, email, password, **extra_fields):
8 | if not email:
9 | raise ValueError('The given email must be set')
10 |
11 | email = self.normalize_email(email)
12 | user = self.model(email=email, **extra_fields)
13 | user.set_password(password)
14 | user.save(using=self._db)
15 |
16 | return user
17 |
18 | def create_user(self, email, password=None, **extra_fields):
19 | extra_fields.setdefault('is_superuser', False)
20 | return self._create_user(email, password, **extra_fields)
21 |
22 | def create_superuser(self, email, password, **extra_fields):
23 | extra_fields.setdefault('is_superuser', True)
24 | extra_fields.setdefault('is_staff', True)
25 |
26 | if extra_fields.get('is_superuser') is not True:
27 | raise ValueError('Superuser must have is_superuser=True.')
28 |
29 | return self._create_user(email, password, **extra_fields)
30 |
--------------------------------------------------------------------------------
/backend/challenges/forms.py:
--------------------------------------------------------------------------------
1 | import tempfile
2 |
3 | from django import forms
4 |
5 | from .models import Submission
6 |
7 | # try:
8 | from .score_calculator import calculate_score
9 | # except ImportError:
10 | # def calculate_score(*args):
11 | # return 1
12 |
13 |
14 | def save_to_tmp(content):
15 | with tempfile.NamedTemporaryFile('w', suffix='.csv', delete=False) as tmp:
16 | tmp.write(content)
17 |
18 | return tmp.name
19 |
20 |
21 | class SubmissionForm(forms.ModelForm):
22 | class Meta:
23 | model = Submission
24 | fields = ['code', 'solution', 'challenge']
25 |
26 | def clean_solution(self):
27 | path = save_to_tmp(self.cleaned_data['solution'])
28 |
29 | try:
30 | self.score = calculate_score(path)
31 | except Exception as error:
32 | raise forms.ValidationError(error)
33 |
34 | return self.cleaned_data['solution']
35 |
36 | def save(self, user):
37 | submission = super().save(commit=False)
38 | submission.submitted_by = user
39 | submission.score = self.score
40 |
41 | submission.save()
42 |
43 | return submission
44 |
--------------------------------------------------------------------------------
/backend/users/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from .managers import UserManager
4 |
5 | from django.utils.translation import ugettext_lazy as _
6 |
7 | from django.contrib.auth.models import PermissionsMixin
8 | from django.contrib.auth.base_user import AbstractBaseUser
9 |
10 |
11 | class User(AbstractBaseUser, PermissionsMixin):
12 | full_name = models.CharField(
13 | _('full name'),
14 | max_length=200,
15 | null=True,
16 | blank=True,
17 | )
18 | email = models.EmailField(_('email address'), unique=True)
19 |
20 | date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
21 | is_active = models.BooleanField(_('active'), default=True)
22 | is_staff = models.BooleanField(_('is staff'), default=False)
23 |
24 | objects = UserManager()
25 |
26 | USERNAME_FIELD = 'email'
27 | REQUIRED_FIELDS = []
28 |
29 | objects = UserManager()
30 |
31 | def get_short_name(self):
32 | return self.email
33 |
34 |
35 | class Team(models.Model):
36 | name = models.CharField(max_length=200)
37 | owner = models.OneToOneField(
38 | User,
39 | on_delete=models.PROTECT,
40 | related_name='team'
41 | )
42 |
--------------------------------------------------------------------------------
/backend/tests/api/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 |
4 | class GraphQLClient():
5 | def __init__(self, client):
6 | self.client = client
7 |
8 | def query(self, query, op_name=None, variables=None):
9 | """
10 | Args:
11 | query (string) - GraphQL query to run
12 | op_name (string) - If the query is a mutation or named query, you
13 | must supply the op_name. For annon queries
14 | ("{ ... }"), should be None (default).
15 | variables (dict) - If provided, the variables in GraphQL will be
16 | set to this value
17 | Returns:
18 | dict, response from graphql endpoint.
19 | The response has the "data" key.
20 | It will have the "error" key if any error happened.
21 | """
22 | body = {'query': query}
23 | if op_name:
24 | body['operation_name'] = op_name
25 | if variables:
26 | body['variables'] = variables
27 |
28 | resp = self.client.post(
29 | '/graphql', json.dumps(body), content_type='application/json'
30 | )
31 |
32 | return json.loads(resp.content.decode())
33 |
--------------------------------------------------------------------------------
/frontend/src/pages/profile/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { withStyles } from "material-ui/styles";
3 | import Typography from "material-ui/Typography";
4 |
5 | import gql from "graphql-tag";
6 | import { Query } from "react-apollo";
7 |
8 | const QUERY = gql`
9 | query {
10 | me {
11 | fullName
12 | }
13 | }
14 | `;
15 |
16 | const styles = theme => ({});
17 |
18 | class Login extends PureComponent {
19 | state = {};
20 |
21 | handleChange = name => event => {
22 | this.setState({
23 | [name]: event.target.value
24 | });
25 | };
26 |
27 | render() {
28 | return (
29 |
30 |
31 | Profile
32 |
33 |
34 |
35 | {({ data, loading, error }) => {
36 | if (loading) {
37 | return Loading ;
38 | }
39 |
40 | return (
41 |
42 | Hello {data.me.fullName || "you"}!
43 |
44 | );
45 | }}
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | export default withStyles(styles)(Login);
53 |
--------------------------------------------------------------------------------
/backend/users/templates/users/test-login.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
38 |
--------------------------------------------------------------------------------
/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | events {
2 | worker_connections 1024;
3 | }
4 |
5 | http {
6 | include /etc/nginx/mime.types;
7 | default_type application/octet-stream;
8 | charset utf-8;
9 | keepalive_timeout 65;
10 | server_tokens off;
11 | tcp_nopush on;
12 | tcp_nodelay off;
13 |
14 | server {
15 | root /app/build;
16 | index index.html index.htm;
17 | client_max_body_size 128m;
18 |
19 | location ~* \.(?:manifest|appcache|html?|xml|json)$ {
20 | expires -1;
21 | }
22 |
23 |
24 | location ~ ^/(graphql|admin|static/admin) {
25 | proxy_set_header Host $host;
26 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
27 |
28 | proxy_redirect off;
29 | proxy_pass http://backend:8000;
30 | }
31 |
32 | location ~* \.(?:css|js)$ {
33 | try_files $uri =404;
34 | expires 1y;
35 | access_log off;
36 | add_header Cache-Control "public";
37 | }
38 |
39 | # Any route containing a file extension (e.g. /devicesfile.js)
40 | location ~ ^.+\..+$ {
41 | try_files $uri =404;
42 | }
43 |
44 | # Any route that doesn't have a file extension (e.g. /devices)
45 | location / {
46 | try_files $uri $uri/ /index.html;
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/backend/challenges/migrations/0002_submission.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.4 on 2018-04-11 12:06
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 | import django.utils.timezone
7 | import model_utils.fields
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ('challenges', '0001_initial'),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name='Submission',
20 | fields=[
21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22 | ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
23 | ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
24 | ('data', models.TextField()),
25 | ('score', models.CharField(max_length=10)),
26 | ('challenge', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='challenges.Challenge')),
27 | ('submitted_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
28 | ],
29 | options={
30 | 'abstract': False,
31 | },
32 | ),
33 | ]
34 |
--------------------------------------------------------------------------------
/backend/challenges/api/mutations.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from ..forms import SubmissionForm
4 |
5 |
6 | class UploadSubmissionInput(graphene.InputObjectType):
7 | challenge = graphene.Int(required=True)
8 | code = graphene.String(required=True)
9 | solution = graphene.String(required=True)
10 |
11 |
12 | class UploadSubmissionErrors(graphene.ObjectType):
13 | challenge = graphene.List(graphene.NonNull(graphene.String))
14 | code = graphene.List(graphene.NonNull(graphene.String))
15 | solution = graphene.List(graphene.NonNull(graphene.String))
16 |
17 |
18 | class UploadSubmissionMutation(graphene.Mutation):
19 | ok = graphene.Boolean()
20 | result = graphene.String()
21 | errors = graphene.Field(UploadSubmissionErrors, required=True)
22 |
23 | class Arguments:
24 | input = UploadSubmissionInput(required=True)
25 |
26 | @classmethod
27 | def mutate(cls, root, info, input):
28 | if not info.context.user.is_authenticated:
29 | raise ValueError('You are not logged in.')
30 |
31 | form = SubmissionForm(data=input)
32 |
33 | if form.is_valid():
34 | submission = form.save(info.context.user)
35 |
36 | return cls(
37 | ok=True,
38 | result=submission.score,
39 | errors=UploadSubmissionErrors()
40 | )
41 |
42 | return cls(
43 | ok=False,
44 | result=None,
45 | errors=UploadSubmissionErrors(**form.errors)
46 | )
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PyCon Nove ML Contest
2 |
3 | This is the source code of the app used for the Machine Learning contest
4 | that will be launched during the PyCon Nove Conference.
5 |
6 | ## Backend
7 |
8 | The backend is a Python application using Django and Graphene.
9 |
10 | ## Frontend
11 |
12 | The frontend is a single page application built with React and Material UI.
13 |
14 | ## How it works - Registration and Teams
15 |
16 | Users can login and create their team. A team can be formed by one or more
17 | people. You will always be in the team you have created. One user can only be
18 | in one single team.
19 |
20 | ### User Model
21 |
22 | - email
23 | - password
24 |
25 | ### Team Model
26 |
27 | - name
28 | - owner (One to One to user)
29 | - members (list of emails, excluding the current user)
30 |
31 | Instead of asking all the user to register we ask the team owner to provider
32 | their emails. Again, one user can only be in one team and no superuser can
33 | be in a team, since they have access to the admin.
34 |
35 | ## How it works - Challenges
36 |
37 | Each team can partecipate in one or more challenges. The challenges might be
38 | unlocked by a code (TBD).
39 |
40 | ### Challenge Model
41 |
42 | - name
43 | - description
44 | - data (url to dataset)
45 | - unlock_code (?)
46 | - solution (needed to validate a submission)
47 |
48 | A user can submit a solution more than once and they will get instant
49 | feedback on their result.
50 |
51 | ### Solution Model
52 |
53 | - team
54 | - created
55 | - score
56 | - submitted_data
--------------------------------------------------------------------------------
/frontend/src/pages/leaderboard/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { withStyles } from "material-ui/styles";
3 | import Typography from "material-ui/Typography";
4 | import { ListItem, ListItemIcon, ListItemText } from "material-ui/List";
5 | import GradeIcon from "material-ui-icons/Grade";
6 |
7 | import { gql } from "apollo-boost";
8 | import { Query } from "react-apollo";
9 |
10 | const styles = theme => ({});
11 |
12 | const LEADERBOARD = gql`
13 | query {
14 | leaderboard {
15 | name
16 | score
17 | }
18 | }
19 | `;
20 |
21 | class Leaderboard extends PureComponent {
22 | render() {
23 | return (
24 |
25 |
26 | Leaderboard
27 |
28 |
29 |
30 | {({ loading, error, data }) => {
31 | if (loading) return Loading...
;
32 | if (error) return Error :(
;
33 | if (data.leaderboard.length === 0)
34 | return No submission yet
;
35 |
36 | return data.leaderboard.map(x => (
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ));
45 | }}
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | export default withStyles(styles)(Leaderboard);
53 |
--------------------------------------------------------------------------------
/backend/challenges/api/schema.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from ..models import Challenge, Submission
4 |
5 | from .types import ChallengeType, LeaderboardSubmissionType, SubmissionType
6 | from .mutations import UploadSubmissionMutation
7 |
8 |
9 | class ChallengeQuery():
10 | challenge = graphene.Field(ChallengeType, id=graphene.ID())
11 | challenges = graphene.List(graphene.NonNull(ChallengeType))
12 | leaderboard = graphene.List(graphene.NonNull(LeaderboardSubmissionType))
13 | my_submissions = graphene.List(graphene.NonNull(SubmissionType))
14 |
15 | def resolve_challenges(self, info):
16 | return Challenge.objects.all()
17 |
18 | def resolve_challenge(self, info, id):
19 | try:
20 | return Challenge.objects.get(pk=id)
21 | except Challenge.DoesNotExist:
22 | return None
23 |
24 | def resolve_leaderboard(self, info):
25 | all_submissions = Submission.objects.order_by('-score')
26 | leaderboard = []
27 |
28 | seen_user = set()
29 |
30 | for submission in all_submissions:
31 | user = submission.submitted_by
32 |
33 | if user.pk not in seen_user:
34 | leaderboard.append(submission)
35 |
36 | seen_user.add(user.pk)
37 |
38 | return leaderboard
39 |
40 | def resolve_my_submissions(self, info):
41 | if not info.context.user.is_authenticated:
42 | return Submission.objects.none()
43 |
44 | return Submission.objects.order_by('-created').filter(
45 | submitted_by=info.context.user
46 | )
47 |
48 |
49 | class ChallengeMutations():
50 | upload_submission = UploadSubmissionMutation.Field()
51 |
--------------------------------------------------------------------------------
/backend/tests/users/test_managers.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from users.models import User
4 | from users.managers import UserManager
5 |
6 |
7 | @pytest.mark.django_db
8 | def test_create_user_empty_email():
9 | with pytest.raises(ValueError) as error:
10 | UserManager().create_user('', 'johnpassword')
11 |
12 | assert 'The given email must be set' in str(error.value)
13 |
14 |
15 | @pytest.mark.django_db
16 | def test_create_user_with_email_and_password():
17 | user = User.objects.create_user('lennon@thebeatles.com', 'johnpassword')
18 |
19 | assert user.email == 'lennon@thebeatles.com'
20 | assert not user.is_superuser
21 | assert not user.is_staff
22 |
23 |
24 | @pytest.mark.django_db
25 | def test_create_user_superuser_with_email_and_password():
26 | user = User.objects.create_user(
27 | 'lennon@thebeatles.com',
28 | 'johnpassword',
29 | is_superuser=True
30 | )
31 |
32 | assert user.email == 'lennon@thebeatles.com'
33 | assert user.is_superuser
34 | assert not user.is_staff
35 |
36 |
37 | @pytest.mark.django_db
38 | def test_cannot_create_superuser_not_superuser_flag():
39 | with pytest.raises(ValueError) as error:
40 | User.objects.create_superuser(
41 | 'lennon@thebeatles.com',
42 | 'johnpassword',
43 | is_superuser=False
44 | )
45 |
46 | assert 'Superuser must have is_superuser=True.' in str(error.value)
47 |
48 |
49 | @pytest.mark.django_db
50 | def test_create_superuser_with_email_and_password():
51 | user = User.objects.create_superuser(
52 | 'lennon@thebeatles.com',
53 | 'johnpassword'
54 | )
55 |
56 | assert user.email == 'lennon@thebeatles.com'
57 | assert user.is_staff
58 | assert user.is_superuser
59 |
--------------------------------------------------------------------------------
/backend/tests/api/challenges/test_upload_submission.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from challenges.models import Challenge, Submission
4 |
5 |
6 | @pytest.mark.django_db
7 | def test_does_not_work_when_not_logged_in(graphql_client):
8 | challenge = Challenge.objects.create(name='demo')
9 |
10 | resp = graphql_client.query('''
11 | mutation UploadSubmission($input: UploadSubmissionInput!) {
12 | uploadSubmission(input: $input) {
13 | ok
14 | result
15 | }
16 | }
17 | ''', variables={
18 | 'input': {
19 | 'challenge': challenge.pk,
20 | 'code': 'example code',
21 | 'solution': 'example data',
22 | }
23 | })
24 |
25 | assert 'errors' in resp
26 |
27 | assert resp['errors'][0]['message'] == 'You are not logged in.'
28 |
29 |
30 | # TODO: "404s"
31 |
32 | @pytest.mark.django_db
33 | def test_works_when_logged_in(graphql_client, user):
34 | challenge = Challenge.objects.create(name='demo')
35 | graphql_client.client.force_login(user)
36 |
37 | resp = graphql_client.query('''
38 | mutation UploadSubmission($input: UploadSubmissionInput!) {
39 | uploadSubmission(input: $input) {
40 | ok
41 | result
42 | }
43 | }
44 | ''', variables={
45 | 'input': {
46 | 'challenge': challenge.pk,
47 | 'code': 'example code',
48 | 'solution': 'example data',
49 | }
50 | })
51 |
52 | assert 'errors' not in resp
53 |
54 | assert Submission.objects.count() == 1
55 |
56 | # TODO: test score
57 | submission = Submission.objects.first()
58 |
59 | assert submission.submitted_by == user
60 | assert submission.challenge == challenge
61 |
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
24 | PyData Challenge
25 |
26 |
27 |
28 | You need to enable JavaScript to run this app.
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/backend/tests/api/users/test_login.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from django.contrib.sessions.models import Session
4 |
5 |
6 | @pytest.mark.django_db
7 | def test_correct_login_adds_cookie(graphql_client, admin_user):
8 | resp = graphql_client.query('''
9 | mutation Login($username: String!, $password: String!) {
10 | login(username: $username, password: $password) {
11 | ok
12 | errors {
13 | nonFieldErrors
14 | }
15 | }
16 | }
17 | ''', variables={
18 | 'username': admin_user.email,
19 | 'password': 'password',
20 | })
21 |
22 | assert 'errors' not in resp
23 | assert not resp['data']['login']['errors']['nonFieldErrors']
24 |
25 | assert resp['data']['login']['ok']
26 |
27 | cookies = graphql_client.client.cookies
28 |
29 | assert 'sessionid' in cookies
30 |
31 | session = Session.objects.get(session_key=cookies['sessionid'].value)
32 | uid = session.get_decoded().get('_auth_user_id')
33 |
34 | assert uid == str(admin_user.id)
35 |
36 |
37 | @pytest.mark.django_db
38 | def test_wrong_email_returns_error(graphql_client, admin_user):
39 | resp = graphql_client.query('''
40 | mutation Login($username: String!, $password: String!) {
41 | login(username: $username, password: $password) {
42 | ok
43 | errors {
44 | nonFieldErrors
45 | }
46 | }
47 | }
48 | ''', variables={
49 | 'username': 'wrong-email',
50 | 'password': 'password',
51 | })
52 |
53 | assert not resp['data']['login']['ok']
54 | assert resp['data']['login']['errors']['nonFieldErrors']
55 |
56 | errors = resp['data']['login']['errors']
57 |
58 | assert (
59 | errors['nonFieldErrors'] == [
60 | 'Unable to log in with provided credentials'
61 | ]
62 | )
63 |
64 | cookies = graphql_client.client.cookies
65 |
66 | assert 'sessionid' not in cookies
67 |
--------------------------------------------------------------------------------
/frontend/src/app.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { MuiThemeProvider, createMuiTheme } from "material-ui/styles";
3 | import Grid from "material-ui/Grid";
4 |
5 | import { ApolloProvider } from "react-apollo";
6 |
7 | import { Switch, Route } from "react-router";
8 |
9 | import { BrowserRouter } from "react-router-dom";
10 |
11 | import Header from "./components/header";
12 |
13 | import HomePage from "./pages/home";
14 | import RegisterPage from "./pages/register";
15 | import SubmissionPage from "./pages/submission";
16 | import LeaderboardPage from "./pages/leaderboard";
17 | import LoginPage from "./pages/login";
18 | import ProfilePage from "./pages/profile";
19 |
20 | import { client } from "./client";
21 | import { PrivateRoute } from "./components/private-route";
22 |
23 | const theme = createMuiTheme({
24 | typography: {
25 | fontSize: 15
26 | }
27 | });
28 |
29 | const App = () => (
30 |
31 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 |
51 | Not Found } />
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 |
60 | export default App;
61 |
--------------------------------------------------------------------------------
/backend/users/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.11 on 2017-07-15 14:09
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 | import users.managers
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | initial = True
12 |
13 | dependencies = [
14 | ('auth', '0008_alter_user_username_max_length'),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name='User',
20 | fields=[
21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22 | ('password', models.CharField(max_length=128, verbose_name='password')),
23 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
24 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
25 | ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
26 | ('date_joined', models.DateTimeField(auto_now_add=True, verbose_name='date joined')),
27 | ('is_active', models.BooleanField(default=True, verbose_name='active')),
28 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
29 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
30 | ],
31 | options={
32 | 'abstract': False,
33 | },
34 | managers=[
35 | ('objects', users.managers.UserManager()),
36 | ],
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/backend/users/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.contrib.auth.admin import UserAdmin
3 | from django.contrib.auth.forms import UserChangeForm, UserCreationForm
4 | from django.utils.translation import ugettext_lazy as _
5 |
6 | from .models import User
7 |
8 |
9 | class PyConUserCreationForm(UserCreationForm):
10 | """
11 | A form that creates a user, with no privileges, from the given email and
12 | password.
13 | """
14 |
15 | class Meta:
16 | model = User
17 | fields = ('email', )
18 |
19 |
20 | class PyConUserChangeForm(UserChangeForm):
21 | """A form for updating users. Includes all the fields on
22 | the user, but replaces the password field with admin's
23 | password hash display field.
24 | """
25 |
26 | class Meta:
27 | model = User
28 | fields = '__all__'
29 |
30 |
31 | class PyConUserAdmin(UserAdmin):
32 | # The forms to add and change user instances
33 |
34 | # The fields to be used in displaying the User model.
35 | # These override the definitions on the base UserAdmin
36 | # that reference the removed 'username' field
37 | fieldsets = (
38 | (None, {
39 | 'fields': ('email', 'password')
40 | }),
41 | (_('Permissions'), {
42 | 'fields': (
43 | 'is_active',
44 | 'is_staff',
45 | 'is_superuser',
46 | 'groups',
47 | 'user_permissions',
48 | )
49 | }),
50 | (_('Important dates'), {
51 | 'fields': ('last_login', 'date_joined')
52 | }),
53 | )
54 |
55 | add_fieldsets = ((None, {
56 | 'classes': ('wide', ),
57 | 'fields': ('email', 'password1', 'password2')
58 | }), )
59 |
60 | form = PyConUserChangeForm
61 | add_form = PyConUserCreationForm
62 | readonly_fields = ('date_joined', )
63 | list_display = ('email', 'is_staff', 'is_superuser')
64 | search_fields = ('email', )
65 | ordering = ('email', )
66 |
67 |
68 | admin.site.register(User, PyConUserAdmin)
69 |
--------------------------------------------------------------------------------
/backend/tests/api/challenges/test_query_submissions.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from challenges.models import Submission, Challenge
4 | from users.models import User
5 |
6 |
7 | @pytest.mark.django_db
8 | def test_returns_empty_when_no_submissions(graphql_client):
9 | resp = graphql_client.query('''
10 | query {
11 | mySubmissions {
12 | score
13 | }
14 | }
15 | ''')
16 |
17 | assert 'errors' not in resp
18 |
19 | assert resp['data']['mySubmissions'] == []
20 |
21 |
22 | @pytest.mark.django_db
23 | def test_returns_submissions(graphql_client, user):
24 | challenge = Challenge.objects.create(name='demo')
25 |
26 | Submission.objects.create(
27 | solution='abc',
28 | code='abc',
29 | score=123,
30 | submitted_by=user,
31 | challenge=challenge,
32 | )
33 |
34 | graphql_client.client.force_login(user)
35 |
36 | resp = graphql_client.query('''
37 | query {
38 | mySubmissions {
39 | score
40 | }
41 | }
42 | ''')
43 |
44 | assert 'errors' not in resp
45 |
46 | assert len(resp['data']['mySubmissions']) == 1
47 | assert resp['data']['mySubmissions'][0]['score'] == '123'
48 |
49 |
50 | @pytest.mark.django_db
51 | def test_returns_user_submissions(graphql_client, user):
52 | challenge = Challenge.objects.create(name='demo')
53 |
54 | b = User.objects.create(full_name='example')
55 |
56 | Submission.objects.create(
57 | solution='abc',
58 | code='abc',
59 | score=123,
60 | submitted_by=user,
61 | challenge=challenge,
62 | )
63 |
64 | Submission.objects.create(
65 | solution='abc',
66 | code='abc',
67 | score=777,
68 | submitted_by=b,
69 | challenge=challenge,
70 | )
71 |
72 | graphql_client.client.force_login(user)
73 |
74 | resp = graphql_client.query('''
75 | query {
76 | mySubmissions {
77 | score
78 | }
79 | }
80 | ''')
81 |
82 | assert 'errors' not in resp
83 |
84 | assert len(resp['data']['mySubmissions']) == 1
85 | assert resp['data']['mySubmissions'][0]['score'] == '123'
86 |
--------------------------------------------------------------------------------
/backend/tests/api/challenges/test_query_leaderboard.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from users.models import Team
4 | from challenges.models import Submission, Challenge
5 |
6 |
7 | @pytest.mark.django_db
8 | def test_returns_empty_when_no_submissions(graphql_client):
9 | resp = graphql_client.query('''
10 | query {
11 | leaderboard {
12 | name
13 | score
14 | }
15 | }
16 | ''')
17 |
18 | assert 'errors' not in resp
19 |
20 | assert resp['data']['leaderboard'] == []
21 |
22 |
23 | @pytest.mark.django_db
24 | def test_returns_submissions(graphql_client, user):
25 | challenge = Challenge.objects.create(name='demo')
26 | user.full_name = 'Patrick'
27 | user.save()
28 |
29 | Submission.objects.create(
30 | solution='abc',
31 | code='abc',
32 | score=123,
33 | submitted_by=user,
34 | challenge=challenge,
35 | )
36 |
37 | resp = graphql_client.query('''
38 | query {
39 | leaderboard {
40 | name
41 | score
42 | }
43 | }
44 | ''')
45 |
46 | assert 'errors' not in resp
47 |
48 | assert len(resp['data']['leaderboard']) == 1
49 | assert resp['data']['leaderboard'][0]['name'] == user.full_name
50 |
51 |
52 | @pytest.mark.django_db
53 | def test_returns_team_name_submissions(graphql_client, user):
54 | challenge = Challenge.objects.create(name='demo')
55 | user.full_name = 'Patrick'
56 | user.save()
57 | team = Team.objects.create(name='broccoletti', owner=user)
58 |
59 | Submission.objects.create(
60 | solution='abc',
61 | code='abc',
62 | score=123,
63 | submitted_by=user,
64 | challenge=challenge,
65 | )
66 |
67 | resp = graphql_client.query('''
68 | query {
69 | leaderboard {
70 | name
71 | score
72 | }
73 | }
74 | ''')
75 |
76 | assert 'errors' not in resp
77 |
78 | assert len(resp['data']['leaderboard']) == 1
79 | assert resp['data']['leaderboard'][0]['name'] == team.name
80 |
81 |
82 | @pytest.mark.django_db
83 | def test_returns_single_per_user_submissions(graphql_client, user):
84 | challenge = Challenge.objects.create(name='demo')
85 | user.full_name = 'Patrick'
86 | user.save()
87 |
88 | Submission.objects.create(
89 | solution='abc',
90 | code='abc',
91 | score=123,
92 | submitted_by=user,
93 | challenge=challenge,
94 | )
95 |
96 | Submission.objects.create(
97 | solution='abc',
98 | code='abc',
99 | score=256,
100 | submitted_by=user,
101 | challenge=challenge,
102 | )
103 |
104 | resp = graphql_client.query('''
105 | query {
106 | leaderboard {
107 | name
108 | score
109 | }
110 | }
111 | ''')
112 |
113 | assert 'errors' not in resp
114 |
115 | assert len(resp['data']['leaderboard']) == 1
116 | assert resp['data']['leaderboard'][0]['score'] == '256'
117 |
--------------------------------------------------------------------------------
/backend/users/api/mutations.py:
--------------------------------------------------------------------------------
1 | import graphene
2 |
3 | from django.contrib.auth import login, logout, authenticate
4 |
5 | from ..forms import RegisterForm
6 | from .types import UserType
7 |
8 |
9 | class LoginErrors(graphene.ObjectType):
10 | non_field_errors = graphene.List(graphene.NonNull(graphene.String))
11 |
12 |
13 | class LoginMutation(graphene.Mutation):
14 | ok = graphene.Boolean()
15 | errors = graphene.Field(LoginErrors, required=True)
16 |
17 | class Arguments:
18 | username = graphene.String()
19 | password = graphene.String()
20 |
21 | @classmethod
22 | def mutate(cls, root, info, username, password):
23 | user = authenticate(username=username, password=password)
24 |
25 | if user is None:
26 | error = [
27 | 'Unable to log in with provided credentials'
28 | ]
29 |
30 | return cls(
31 | errors=LoginErrors(non_field_errors=error),
32 | ok=False,
33 | )
34 |
35 | if not user.is_active:
36 | error = [
37 | 'It seems your account has been disabled'
38 | ]
39 |
40 | return cls(
41 | errors=LoginErrors(non_field_errors=error),
42 | ok=False,
43 | )
44 |
45 | login(info.context, user)
46 |
47 | return cls(ok=True, errors=LoginErrors())
48 |
49 |
50 | class LogoutMutation(graphene.Mutation):
51 | ok = graphene.Boolean()
52 |
53 | @classmethod
54 | def mutate(cls, root, info):
55 | logout(info.context)
56 |
57 | return cls(ok=True)
58 |
59 |
60 | class RegistrationInput(graphene.InputObjectType):
61 | full_name = graphene.String(required=True)
62 | team_name = graphene.String()
63 | email = graphene.String(required=True)
64 | password = graphene.String(required=True)
65 |
66 |
67 | class RegistrationErrors(graphene.ObjectType):
68 | full_name = graphene.List(graphene.NonNull(graphene.String))
69 | team_name = graphene.List(graphene.NonNull(graphene.String))
70 | email = graphene.List(graphene.NonNull(graphene.String))
71 | password = graphene.List(graphene.NonNull(graphene.String))
72 | non_field_errors = graphene.List(graphene.NonNull(graphene.String))
73 |
74 |
75 | class RegisterMutation(graphene.Mutation):
76 | ok = graphene.Boolean()
77 | user = graphene.Field(UserType)
78 | errors = graphene.Field(RegistrationErrors, required=True)
79 |
80 | class Arguments:
81 | input = RegistrationInput(required=True)
82 |
83 | @classmethod
84 | def mutate(cls, root, info, input):
85 | form = RegisterForm(data=input)
86 |
87 | if form.is_valid():
88 | user = form.save()
89 |
90 | login(info.context, user)
91 |
92 | return cls(
93 | ok=True,
94 | user=user,
95 | errors=RegistrationErrors()
96 | )
97 |
98 | return cls(
99 | ok=False,
100 | user=None,
101 | errors=RegistrationErrors(**form.errors)
102 | )
103 |
--------------------------------------------------------------------------------
/frontend/src/pages/login/form.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { withStyles } from "material-ui/styles";
3 | import Button from "material-ui/Button";
4 | import { LinearProgress } from "material-ui/Progress";
5 | import TextField from "material-ui/TextField";
6 | import idx from "idx";
7 |
8 | import gql from "graphql-tag";
9 | import { Mutation } from "react-apollo";
10 | import { FormHelperText } from "material-ui";
11 |
12 | import { setIsAuthenticated } from "../../authentication";
13 |
14 | const LOGIN = gql`
15 | mutation LoginMutation($email: String!, $password: String!) {
16 | login(username: $email, password: $password) {
17 | ok
18 | errors {
19 | nonFieldErrors
20 | }
21 | }
22 | }
23 | `;
24 |
25 | const styles = theme => ({
26 | container: {
27 | display: "flex",
28 | flexWrap: "wrap",
29 | flexDirection: "column"
30 | }
31 | });
32 |
33 | class LoginForm extends PureComponent {
34 | state = {};
35 |
36 | handleChange = name => event => {
37 | this.setState({
38 | [name]: event.target.value
39 | });
40 | };
41 |
42 | render() {
43 | const { classes } = this.props;
44 |
45 | return (
46 | {
49 | if (login.ok) {
50 | setIsAuthenticated(true).catch(e => console.warn(e));
51 | }
52 | }}
53 | >
54 | {(login, { data, loading, called }) => {
55 | const errors = idx(data, _ => _.login.errors.nonFieldErrors);
56 |
57 | if (called && data && data.login.ok) {
58 | window.location = "/profile";
59 | }
60 |
61 | return (
62 |
104 | );
105 | }}
106 |
107 | );
108 | }
109 | }
110 |
111 | export default withStyles(styles)(LoginForm);
112 |
--------------------------------------------------------------------------------
/backend/pycon_ml/settings/base.py:
--------------------------------------------------------------------------------
1 | import environ
2 |
3 | root = environ.Path(__file__) - 3
4 |
5 | env = environ.Env(
6 | DEBUG=(bool, False),
7 | ALLOWED_HOSTS=(list, []),
8 | )
9 |
10 | environ.Env.read_env(root('.env'))
11 |
12 | DEBUG = env('DEBUG')
13 |
14 | ALLOWED_HOSTS = env('ALLOWED_HOSTS')
15 |
16 | # Application definition
17 |
18 | INSTALLED_APPS = [
19 | 'django.contrib.admin',
20 | 'django.contrib.auth',
21 | 'django.contrib.contenttypes',
22 | 'django.contrib.sessions',
23 | 'django.contrib.messages',
24 | 'django.contrib.staticfiles',
25 |
26 | 'graphene_django',
27 |
28 | 'users',
29 | 'challenges',
30 | ]
31 |
32 | MIDDLEWARE = [
33 | 'django.middleware.security.SecurityMiddleware',
34 | 'django.contrib.sessions.middleware.SessionMiddleware',
35 | 'django.middleware.common.CommonMiddleware',
36 | 'django.middleware.csrf.CsrfViewMiddleware',
37 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
38 | 'django.contrib.messages.middleware.MessageMiddleware',
39 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
40 | ]
41 |
42 | ROOT_URLCONF = 'pycon_ml.urls'
43 |
44 | TEMPLATES = [
45 | {
46 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
47 | 'DIRS': [
48 | root('frontend'),
49 | ],
50 | 'APP_DIRS': True,
51 | 'OPTIONS': {
52 | 'context_processors': [
53 | 'django.template.context_processors.debug',
54 | 'django.template.context_processors.request',
55 | 'django.contrib.auth.context_processors.auth',
56 | 'django.contrib.messages.context_processors.messages',
57 | ],
58 | },
59 | },
60 | ]
61 |
62 | WSGI_APPLICATION = 'pycon_ml.wsgi.application'
63 |
64 |
65 | # Database
66 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
67 |
68 | DATABASES = {
69 | 'default': env.db(default='sqlite:///{}'.format(root('db.sqlite3'))),
70 | }
71 |
72 | # Password validation
73 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
74 |
75 | AUTH_PASSWORD_VALIDATORS = [{
76 | 'NAME':
77 | 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
78 | }, {
79 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
80 | }, {
81 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
82 | }, {
83 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
84 | }]
85 |
86 |
87 | # Internationalization
88 | # https://docs.djangoproject.com/en/1.10/topics/i18n/
89 |
90 | LANGUAGE_CODE = 'en-us'
91 |
92 | LOCALE_PATHS = [
93 | root('locale'),
94 | ]
95 |
96 | TIME_ZONE = 'UTC'
97 |
98 | USE_I18N = True
99 |
100 | USE_L10N = True
101 |
102 | USE_TZ = True
103 |
104 |
105 | # Static files (CSS, JavaScript, Images)
106 | # https://docs.djangoproject.com/en/1.9/howto/static-files/
107 |
108 | STATICFILES_DIRS = [
109 | root('assets'),
110 | ]
111 |
112 | STATIC_URL = '/static/'
113 | STATIC_ROOT = root('static')
114 |
115 | MEDIA_URL = '/media/'
116 | MEDIA_ROOT = root('media')
117 |
118 | AUTH_USER_MODEL = 'users.User'
119 |
120 | GRAPHENE = {
121 | 'SCHEMA': 'api.schema.schema'
122 | }
123 |
--------------------------------------------------------------------------------
/frontend/src/pages/submission/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { withStyles } from "material-ui/styles";
3 | import Typography from "material-ui/Typography";
4 | import { ListItem, ListItemIcon, ListItemText } from "material-ui/List";
5 | import GradeIcon from "material-ui-icons/Grade";
6 | import { format } from "date-fns";
7 |
8 | import { gql } from "apollo-boost";
9 | import { Query } from "react-apollo";
10 |
11 | import Form from "./form";
12 |
13 | const printDate = s => format(Date.parse(s), "D MMM [at] HH[:]mm");
14 |
15 | const GET_CHALLENGE = gql`
16 | query GetChallenge($id: ID!) {
17 | challenge(id: $id) {
18 | name
19 | }
20 | }
21 | `;
22 |
23 | const GET_SUBMISSIONS = gql`
24 | query {
25 | mySubmissions {
26 | created
27 | score
28 | }
29 | }
30 | `;
31 |
32 | const styles = theme => ({
33 | container: {
34 | display: "flex",
35 | flexWrap: "wrap",
36 | flexDirection: "column"
37 | },
38 | label: {
39 | width: 100,
40 | margin: 10,
41 | display: "inline-block"
42 | },
43 | button: {
44 | margin: theme.spacing.unit
45 | }
46 | });
47 |
48 | class Submission extends PureComponent {
49 | state = {};
50 |
51 | handleChange = name => event => {
52 | this.setState({
53 | [name]: event.target.value
54 | });
55 | };
56 |
57 | render() {
58 | const { classes } = this.props;
59 |
60 | return (
61 |
62 |
63 | {({ loading, error, data }) => {
64 | if (loading) return Loading...
;
65 | if (error || data.challenge === null) return Error :(
;
66 |
67 | const { challenge } = data;
68 |
69 | return (
70 |
71 |
72 | Submit for {challenge.name}
73 |
74 |
75 |
76 |
77 | );
78 | }}
79 |
80 |
81 |
87 | Previous submissions
88 |
89 |
90 |
91 | {({ loading, error, data }) => {
92 | if (loading) return Loading previous submissions...
;
93 | if (error) return Error while loading your submissions.
;
94 |
95 | const { mySubmissions } = data;
96 |
97 | if (mySubmissions.length === 0)
98 | return No previous submission.
;
99 |
100 | return mySubmissions.map(x => (
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | ));
109 | }}
110 |
111 |
112 | );
113 | }
114 | }
115 |
116 | export default withStyles(styles)(Submission);
117 |
--------------------------------------------------------------------------------
/frontend/src/pages/submission/form.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import Button from "material-ui/Button";
3 | import { LinearProgress } from "material-ui/Progress";
4 | import Chip from "material-ui/Chip";
5 | import idx from "idx";
6 |
7 | import gql from "graphql-tag";
8 | import { Mutation } from "react-apollo";
9 | import { FormHelperText } from "material-ui";
10 |
11 | const UPLOAD_SUBMISSION = gql`
12 | mutation UploadSubmission($input: UploadSubmissionInput!) {
13 | uploadSubmission(input: $input) {
14 | ok
15 | result
16 | errors {
17 | solution
18 | }
19 | }
20 | }
21 | `;
22 |
23 | class SubmissionForm extends PureComponent {
24 | state = {
25 | solution: null,
26 | code: null
27 | };
28 |
29 | handleFileChange = event => {
30 | const { name } = event.target;
31 |
32 | const file = event.target.files[0];
33 | const reader = new FileReader();
34 |
35 | reader.onload = reader => {
36 | this.setState({ [name]: reader.target.result });
37 | };
38 |
39 | reader.readAsText(file);
40 | };
41 |
42 | render() {
43 | const { classes } = this.props;
44 |
45 | const hasData = this.state.code && this.state.solution;
46 |
47 | return (
48 |
49 | {(upload, { data, errors, loading, called }) => {
50 | const solutionErrors = idx(
51 | data,
52 | _ => _.uploadSubmission.errors.solution
53 | );
54 | const ok = idx(data, _ => _.uploadSubmission.ok);
55 |
56 | return (
57 |
132 | );
133 | }}
134 |
135 | );
136 | }
137 | }
138 |
139 | export default SubmissionForm;
140 |
--------------------------------------------------------------------------------
/frontend/src/components/header/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { withStyles } from "material-ui/styles";
4 | import AppBar from "material-ui/AppBar";
5 | import Toolbar from "material-ui/Toolbar";
6 | import Typography from "material-ui/Typography";
7 | import Button from "material-ui/Button";
8 | import Drawer from "material-ui/Drawer";
9 | import IconButton from "material-ui/IconButton";
10 | import MenuIcon from "material-ui-icons/Menu";
11 | import List from "material-ui/List";
12 | import Divider from "material-ui/Divider";
13 | import { ListItem, ListItemIcon, ListItemText } from "material-ui/List";
14 | import HomeIcon from "material-ui-icons/Home";
15 | import StarIcon from "material-ui-icons/Star";
16 | import AccountCircleIcon from "material-ui-icons/AccountCircle";
17 | import ExitIcon from "material-ui-icons/ExitToApp";
18 |
19 | import { Link } from "react-router-dom";
20 | import { IsAuthenticated } from "../../authentication";
21 |
22 | const styles = {
23 | root: {
24 | width: "100%"
25 | },
26 | flex: {
27 | flex: 1
28 | },
29 | menuButton: {
30 | marginLeft: -12,
31 | marginRight: 20
32 | },
33 | list: {
34 | width: 250
35 | },
36 | link: {
37 | textDecoration: "none",
38 | color: "inherit"
39 | }
40 | };
41 |
42 | class Header extends React.Component {
43 | state = {
44 | drawerOpen: false
45 | };
46 |
47 | toggleDrawer = open => () => {
48 | this.setState({
49 | drawerOpen: open
50 | });
51 | };
52 |
53 | render() {
54 | const { classes } = this.props;
55 | return (
56 |
57 |
58 |
59 |
65 |
66 |
67 |
72 |
73 | PyData Challenge
74 |
75 |
76 |
77 |
78 | {authenticated =>
79 | !authenticated && (
80 |
81 | Login
82 |
83 | )
84 | }
85 |
86 |
87 |
88 |
89 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | {authenticated =>
112 | !authenticated && (
113 |
114 |
115 |
116 |
117 |
118 |
119 | )
120 | }
121 |
122 |
123 |
124 |
125 | (
128 | {children}
129 | )}
130 | target="_blank"
131 | href="https://www.pycon.it"
132 | >
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | );
144 | }
145 | }
146 |
147 | Header.propTypes = {
148 | classes: PropTypes.object.isRequired
149 | };
150 |
151 | export default withStyles(styles)(Header);
152 |
--------------------------------------------------------------------------------
/frontend/public/data/description.txt:
--------------------------------------------------------------------------------
1 | The “variants” fields file informations.
2 |
3 | chrom
4 | The chromosome on which the variant resides.
5 |
6 | start
7 | Physical position on the chromosome of the variant
8 |
9 | ref
10 | Reference allele.
11 |
12 | alt
13 | Alternate allele for the variant
14 |
15 | gene
16 | Corresponding gene name of the highly affected transcript.
17 |
18 | Aminoacid
19 | The changing of aminoacid.
20 |
21 | Condition(s)
22 | The phenotype caused from the aminoacid changing.
23 |
24 | Clinical significance
25 | The global impact of the variant.
26 |
27 |
28 |
29 | The “variants_anno.db” fields file informations.
30 |
31 | chrom
32 | The chromosome on which the variant resides.
33 |
34 | start
35 | Physical position on the chromosome of the variant
36 |
37 | end
38 | The 1-based end position of the variant.
39 |
40 | ref
41 | Reference allele.
42 |
43 | alt
44 | Alternate allele for the variant
45 |
46 | qual
47 | Quality score for the assertion made in ALT
48 |
49 | filter
50 | A string of filters passed/failed in variant calling
51 |
52 | type
53 | The type of variant [snp, indel].
54 |
55 | sub_type
56 | The variant sub-type. If type is snp: [ts, (transition), tv (transversion)]. If type is indel: [ins, (insertion), del (deletion)].
57 |
58 | aaf
59 | The observed allele frequency for the alternate allele
60 |
61 | hwe
62 | The Chi-square probability of deviation from HWE (assumes random mating).
63 |
64 | inbreeding_coeff
65 | The inbreeding co-efficient that expresses the likelihood of effects due to inbreeding.
66 |
67 | pi
68 | The computed nucleotide diversity (pi) for the site.
69 |
70 | is_exonic
71 | Does the variant affect an exon for >= 1 transcript?
72 |
73 | is_coding
74 | Does the variant fall in a coding region (excl. 3’ & 5’ UTRs) for >= 1 transcript?
75 |
76 | is_lof
77 | Based on the value of the impact col, is the variant LOF for >= transcript?
78 |
79 | is_splicing
80 | Does the variant affect a canonical or possible splice site? That is, set to TRUE if the SO term is any of splice_acceptor_variant, splice_donor_variant, or splice_region_variant.
81 |
82 | codon_change
83 | What is the codon change?
84 |
85 | impact
86 | The consequence of the most severely affected transcript
87 |
88 | impact_so
89 | The Sequence ontology term for the most severe consequence
90 |
91 | impact_severity
92 | Severity of the highest order observed for the variant
93 |
94 | anc_allele
95 | Ancestral allele (based on 1000 genomes reference data)
96 |
97 | rms_bq
98 | The RMS base quality at this position.
99 |
100 | rms_map_qual
101 | RMS mapping quality, a measure of variance of quality scores
102 |
103 | in_dbsnp
104 | Absence (0) or presence (1) of the variant in dbsnp
105 |
106 | rs_ids
107 | A comma-separated list of rs ids for variants present in dbSNP
108 |
109 | in_hm2
110 | Whether the variant was part of HapMap2.
111 |
112 | in_hm3
113 | Whether the variant was part of HapMap3.
114 |
115 | in_esp
116 | Presence/absence of the variant in the ESP project data.
117 |
118 | n_1kg
119 | Presence/absence of the variant in the 1000 genome project data (phase 3).
120 |
121 | max_aaf_all
122 | The maximum of aaf_gnomad{afr,amr,eas,nfe,sas},aaf_esp_ea, aaf_esp_aa, aaf_1kg_amr, aaf_1kg_eas,aaf_1kg_sas,aaf_1kg_afr,aaf_1kg_eur,aaf_adj_exac_afr,aaf_adj_exac_amr,aaf_adj_exac_eas,aaf_adj_exac_nfe,aaf_adj_exac_sas. and -1 if none of those databases/populations contain the variant.
123 |
124 | in_cpg_island
125 | Does the variant overlap a CpG island?. Based on UCSC: Regulation > CpG Islands > cpgIslandExt
126 |
127 | in_segdup
128 | Does the variant overlap a segmental duplication?. Based on UCSC: Variation&Repeats > Segmental Dups > genomicSuperDups track
129 |
130 | is_conserved
131 | Does the variant overlap a conserved region? Based on the 29-way mammalian conservation study.
132 |
133 | gerp_bp_score
134 | GERP conservation score. Higher scores reflect greater conservation. At base-pair resolution.
135 |
136 | gerp_element_pval
137 | GERP elements P-val. Lower P-values scores reflect greater conservation. Not at base-pair resolution.
138 |
139 | recomb_rate
140 | Returns the mean recombination rate at the variant site. Based on HapMapII_GRCh37 genetic map.
141 |
142 | cadd_raw
143 | Raw CADD scores for scoring deleteriousness of SNV’s in the human genome
144 |
145 | cadd_scaled
146 | Scaled CADD scores (Phred like) for scoring deleteriousness of SNV’s
147 |
148 | fitcons
149 | fitCons scores estimating the probability that a point mutation at each position in a genome will influence fitness. Higher scores have more potential for interesting genomic function. Common ranges: 0.05-0.35 for non-coding and 0.4-0.8 for coding.
150 |
151 | encode_consensus_gm12878
152 | ENCODE consensus segmentation prediction for GM12878. CTCF: CTCF-enriched element; E: Predicted enhancer; PF: Predicted promoter flanking region; R: Predicted repressed or low-activity region; TSS: Predicted promoter region including TSS; T: Predicted transcribed region; WE: Predicted weak enhancer or open chromatin cis-regulatory element | unknown: This region of the genome had no functional prediction.
153 |
154 | encode_consensus_h1hesc
155 | ENCODE consensus segmentation prediction for h1HESC.
156 |
157 | encode_consensus_helas3
158 | ENCODE consensus segmentation prediction for Helas3.
159 |
160 | encode_consensus_hepg2
161 | ENCODE consensus segmentation prediction for HEPG2.
162 |
163 | encode_consensus_huvec
164 | ENCODE consensus segmentation prediction for HuVEC.
165 |
166 | encode_consensus_k562
167 | ENCODE consensus segmentation prediction for k562.
168 |
169 | vista_enhancers
170 | Experimentally validated human enhancers from VISTA.
171 |
172 | vep_hgvsc
173 | Human Genome Variation Sequence c-syntax nomenclature.
174 | vep_hgvsp
175 | Human Genome Variation Sequence p-syntax nomenclature.
--------------------------------------------------------------------------------
/frontend/src/pages/register/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { withStyles } from "material-ui/styles";
3 | import Typography from "material-ui/Typography";
4 | import Button from "material-ui/Button";
5 | import TextField from "material-ui/TextField";
6 | import { gql } from "apollo-boost";
7 | import { Mutation } from "react-apollo";
8 | import { FormControl, FormHelperText } from "material-ui/Form";
9 | import { LinearProgress } from "material-ui/Progress";
10 | import { withRouter } from "react-router-dom";
11 |
12 | import idx from "idx";
13 |
14 | const getErrors = (data, key) => idx(data, _ => _.register.errors[key]) || [];
15 | const hasErrors = (data, key) => getErrors(data, key).length > 0;
16 |
17 | const styles = theme => ({
18 | container: {
19 | display: "flex",
20 | flexWrap: "wrap",
21 | flexDirection: "column"
22 | }
23 | });
24 |
25 | const REGISTER = gql`
26 | mutation Register($input: RegistrationInput!) {
27 | register(input: $input) {
28 | ok
29 | user {
30 | fullName
31 | team {
32 | name
33 | }
34 | }
35 | errors {
36 | fullName
37 | teamName
38 | email
39 | password
40 | nonFieldErrors
41 | }
42 | }
43 | }
44 | `;
45 |
46 | class Registration extends PureComponent {
47 | state = {
48 | fullName: "",
49 | email: "",
50 | teamName: "",
51 | password: ""
52 | };
53 |
54 | handleChange = name => event => {
55 | this.setState({
56 | [name]: event.target.value
57 | });
58 | };
59 |
60 | render() {
61 | const { classes } = this.props;
62 |
63 | return (
64 |
65 |
66 | Register
67 |
68 |
69 | {
72 | const ok = idx(data, _ => _.register.ok);
73 |
74 | if (ok) {
75 | window.location = "/profile";
76 | }
77 | }}
78 | variables={{ input: this.state }}
79 | >
80 | {(register, { loading, error, data }) => (
81 |
82 | {loading && }
83 |
84 | {error && (
85 |
86 | {error.message}, make sure you have compiled all the field
87 | correctly
88 |
89 | )}
90 |
91 |
171 |
172 | )}
173 |
174 |
175 | );
176 | }
177 | }
178 |
179 | export default withRouter(withStyles(styles)(Registration));
180 |
--------------------------------------------------------------------------------
/backend/tests/api/users/test_register.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from users.models import User, Team
4 |
5 | from django.contrib.sessions.models import Session
6 |
7 |
8 | @pytest.mark.django_db
9 | def test_registration_works(graphql_client):
10 | resp = graphql_client.query('''
11 | mutation Register($input: RegistrationInput!) {
12 | register(input: $input) {
13 | ok
14 | user {
15 | fullName
16 | }
17 | errors {
18 | fullName
19 | teamName
20 | email
21 | password
22 | nonFieldErrors
23 | }
24 | }
25 | }
26 | ''', variables={
27 | 'input': {
28 | 'fullName': 'Patrick Arminio',
29 | 'teamName': 'ORO 🏆',
30 | 'email': 'patrick.arminio@gmail.com',
31 | 'password': 'do you even 🏋🏼',
32 | }
33 | })
34 |
35 | assert 'errors' not in resp
36 |
37 | data = resp['data']['register']
38 |
39 | assert all([value is None for key, value in data['errors'].items()])
40 | assert data['ok']
41 | assert data['user']['fullName'] == 'Patrick Arminio'
42 |
43 | assert User.objects.count() == 1
44 |
45 | user = User.objects.first()
46 |
47 | assert user.full_name == 'Patrick Arminio'
48 |
49 | cookies = graphql_client.client.cookies
50 |
51 | assert 'sessionid' in cookies
52 |
53 | session = Session.objects.get(session_key=cookies['sessionid'].value)
54 | uid = session.get_decoded().get('_auth_user_id')
55 |
56 | assert uid == str(user.id)
57 |
58 |
59 | @pytest.mark.django_db
60 | def test_registration_with_errors(graphql_client):
61 | resp = graphql_client.query('''
62 | mutation Register($input: RegistrationInput!) {
63 | register(input: $input) {
64 | ok
65 | user {
66 | fullName
67 | }
68 | errors {
69 | fullName
70 | teamName
71 | email
72 | password
73 | nonFieldErrors
74 | }
75 | }
76 | }
77 | ''', variables={
78 | 'input': {
79 | 'fullName': 'Patrick Arminio',
80 | 'teamName': 'ORO 🏆',
81 | 'email': '',
82 | 'password': 'do you even 🏋🏼',
83 | }
84 | })
85 |
86 | assert 'errors' not in resp
87 |
88 | data = resp['data']['register']
89 |
90 | assert data['errors']['email'] == ['This field is required.']
91 |
92 |
93 | @pytest.mark.django_db
94 | def test_registration_bad_email(graphql_client):
95 | resp = graphql_client.query('''
96 | mutation Register($input: RegistrationInput!) {
97 | register(input: $input) {
98 | ok
99 | user {
100 | fullName
101 | }
102 | errors {
103 | fullName
104 | teamName
105 | email
106 | password
107 | nonFieldErrors
108 | }
109 | }
110 | }
111 | ''', variables={
112 | 'input': {
113 | 'fullName': 'Patrick Arminio',
114 | 'teamName': 'ORO 🏆',
115 | 'email': 'aaaaa',
116 | 'password': 'do you even 🏋🏼',
117 | }
118 | })
119 |
120 | assert 'errors' not in resp
121 |
122 | data = resp['data']['register']
123 |
124 | assert data['errors']['email'] == ['Enter a valid email address.']
125 |
126 |
127 | @pytest.mark.django_db
128 | def test_registration_unique_email(graphql_client):
129 | pat = User.objects.create(email='patrick.arminio@gmail.com')
130 |
131 | resp = graphql_client.query('''
132 | mutation Register($input: RegistrationInput!) {
133 | register(input: $input) {
134 | ok
135 | user {
136 | fullName
137 | }
138 | errors {
139 | fullName
140 | teamName
141 | email
142 | password
143 | nonFieldErrors
144 | }
145 | }
146 | }
147 | ''', variables={
148 | 'input': {
149 | 'fullName': 'Patrick Arminio',
150 | 'teamName': 'ORO 🏆',
151 | 'email': pat.email,
152 | 'password': 'do you even 🏋🏼',
153 | }
154 | })
155 |
156 | assert 'errors' not in resp
157 |
158 | data = resp['data']['register']
159 |
160 | assert data['errors']['email'] == [
161 | 'User with this Email address already exists.'
162 | ]
163 |
164 |
165 | @pytest.mark.django_db
166 | def test_registration_creates_team(graphql_client):
167 | resp = graphql_client.query('''
168 | mutation Register($input: RegistrationInput!) {
169 | register(input: $input) {
170 | ok
171 | user {
172 | fullName
173 | team {
174 | name
175 | }
176 | }
177 | errors {
178 | fullName
179 | teamName
180 | email
181 | password
182 | nonFieldErrors
183 | }
184 | }
185 | }
186 | ''', variables={
187 | 'input': {
188 | 'fullName': 'Patrick Arminio',
189 | 'teamName': 'ORO 🏆',
190 | 'email': 'patrick.arminio@gmail.com',
191 | 'password': 'do you even 🏋🏼',
192 | }
193 | })
194 |
195 | assert 'errors' not in resp
196 |
197 | data = resp['data']['register']
198 |
199 | assert all([value is None for key, value in data['errors'].items()])
200 | assert data['ok']
201 | assert data['user']['team']['name'] == 'ORO 🏆'
202 |
203 | assert Team.objects.count() == 1
204 |
205 | team = Team.objects.first()
206 |
207 | assert team.name == 'ORO 🏆'
208 |
209 |
210 | @pytest.mark.django_db
211 | def test_registration_without_team(graphql_client):
212 | resp = graphql_client.query('''
213 | mutation Register($input: RegistrationInput!) {
214 | register(input: $input) {
215 | ok
216 | user {
217 | fullName
218 | team {
219 | name
220 | }
221 | }
222 | errors {
223 | fullName
224 | teamName
225 | email
226 | password
227 | nonFieldErrors
228 | }
229 | }
230 | }
231 | ''', variables={
232 | 'input': {
233 | 'fullName': 'Patrick Arminio',
234 | 'teamName': '',
235 | 'email': 'patrick.arminio@gmail.com',
236 | 'password': 'do you even 🏋🏼',
237 | }
238 | })
239 |
240 | assert 'errors' not in resp
241 |
242 | data = resp['data']['register']
243 |
244 | assert all([value is None for key, value in data['errors'].items()])
245 | assert data['ok']
246 | assert data['user']['team'] is None
247 |
248 | assert Team.objects.count() == 0
249 |
--------------------------------------------------------------------------------
/frontend/src/pages/home/index.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react";
2 | import { withStyles } from "material-ui/styles";
3 | import Typography from "material-ui/Typography";
4 | import List, { ListItem, ListItemText } from "material-ui/List";
5 | import FileDownloadIcon from "@material-ui/icons/FileDownload";
6 | import Avatar from "material-ui/Avatar";
7 | import Button from "material-ui/Button";
8 | import Stepper, { Step, StepLabel } from "material-ui/Stepper";
9 | import { Link } from "react-router-dom";
10 |
11 | const styles = theme => ({});
12 |
13 | class Home extends PureComponent {
14 | render() {
15 | return (
16 |
17 |
18 | Welcome to the PyCon Italia Machine Learning Challenge
19 |
20 |
21 |
22 | We have a prepared a little contest for those of you who love
23 | challenges and machine learning.
24 |
25 |
26 |
27 | The challenge "AI for Precision Medicine" has been created by{" "}
28 |
29 | GenomeUp
30 | .
31 |
32 |
33 |
34 | You will have the following files:
35 |
36 |
40 |
41 |
42 |
43 |
44 |
48 |
49 |
50 |
54 |
55 |
56 |
57 |
58 |
62 |
63 |
64 |
68 |
69 |
70 |
71 |
72 |
76 |
77 |
78 |
79 |
80 |
81 | Objective
82 |
83 |
84 |
85 | The objective of the challenge is to complete the data for the
86 | "Clinical significance" for the entries missing from the file{" "}
87 | variants.csv .
88 |
89 | The “Clinical significance” field conveys the degree of harmfulness of
90 | the corresponding mutation.
91 |
92 | Each row is uniquely identified by one of the following four field
93 | information: "CHROM", "START", "REF", "ALT".
94 |
95 | The values of the Clinical significance can be one of the following:
96 |
97 | Benign
98 | Likely-Benign
99 | Benign / Likely-Benign
100 | Likely-Pathogenic
101 | Likely-Pathogenic / Pathogenic
102 | Pathogenic
103 | Other
104 |
105 | The only classes of interest for the challenge are
106 | the following:{" "}
107 | Benign, Likely-Benign, Likely-Pathogenic, Pathogenic .
108 |
109 |
110 |
111 | Steps
112 |
113 |
114 |
115 | Phase one
116 |
117 |
118 |
119 |
120 | Read each row from the variants.csv file
121 |
122 |
123 |
124 | Get additional data for each row from the variants_year.db
125 |
126 |
127 |
128 |
129 | A multicore solution will be considered a plus
130 |
131 |
132 |
133 |
134 |
135 | Phase two
136 |
137 |
138 |
139 |
140 |
141 | Create a machine learning algorithm that will be able to find the
142 | degree of harmfulness of the genetic mutations considering the
143 | data matched in phase one
144 |
145 |
146 |
147 |
148 |
149 | Output format
150 |
151 |
152 |
153 | The solution is expected to be a textual file formatted as follows:
154 | CHROM;START;REF;ALT;{"{PREDICTED CLINICAL SIGNIFICANCE}"}
155 | where {"{PREDICTED CLINICAL SIGNIFICANCE}"} is one of 0,1,3,5 from the
156 | list above
157 |
158 | {`chr22;49910903;G;A;5
159 | chr9;37781507;T;C;5
160 | chr7;21590585;C;G;0
161 | chr21;46134692;G;A;1`}
162 |
163 |
164 |
165 |
166 | Solution
167 |
168 |
169 |
170 | Once you have completed the previous phases you can upload the CSV
171 | file with the results and the code here:
172 |
173 |
174 |
184 | Upload solutions
185 |
186 |
187 | NOTE: solutions without code won't qualify for the
188 | prizes.
189 |
190 |
191 | Scoring
192 |
193 |
194 |
195 | The winner will be judged based on the accuracy of the machine
196 | learning model
197 |
198 |
199 | );
200 | }
201 | }
202 |
203 | export default withStyles(styles)(Home);
204 |
--------------------------------------------------------------------------------
/backend/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "fb3ffe80d82f1881771e51ddec79a7b56fe358a2718a0d41a639c633d607f51a"
5 | },
6 | "host-environment-markers": {
7 | "implementation_name": "cpython",
8 | "implementation_version": "3.6.1",
9 | "os_name": "posix",
10 | "platform_machine": "x86_64",
11 | "platform_python_implementation": "CPython",
12 | "platform_release": "17.5.0",
13 | "platform_system": "Darwin",
14 | "platform_version": "Darwin Kernel Version 17.5.0: Mon Mar 5 22:24:32 PST 2018; root:xnu-4570.51.1~1/RELEASE_X86_64",
15 | "python_full_version": "3.6.1",
16 | "python_version": "3.6",
17 | "sys_platform": "darwin"
18 | },
19 | "pipfile-spec": 6,
20 | "requires": {},
21 | "sources": [
22 | {
23 | "name": "pypi",
24 | "url": "https://pypi.python.org/simple",
25 | "verify_ssl": true
26 | }
27 | ]
28 | },
29 | "default": {
30 | "aniso8601": {
31 | "hashes": [
32 | "sha256:f7052eb342bf2000c6264a253acedb362513bf9270800be2bc8e3e229fe08b5a",
33 | "sha256:7cf068e7aec00edeb21879c2bbda048656c34d281e133a77425be03b352122d8"
34 | ],
35 | "version": "==3.0.0"
36 | },
37 | "django": {
38 | "hashes": [
39 | "sha256:2d8b9eed8815f172a8e898678ae4289a5e9176bc08295676eff4228dd638ea61",
40 | "sha256:d81a1652963c81488e709729a80b510394050e312f386037f26b54912a3a10d0"
41 | ],
42 | "version": "==2.0.4"
43 | },
44 | "django-environ": {
45 | "hashes": [
46 | "sha256:e9c171b9d5f540e6f3bc42866941d9cd0bd77fb110a7c13a7c4857a2c08cfa40",
47 | "sha256:ee2f8405d83137e3328b26b3de01bd715b5395fca22feb919dcc905fb6099cfa"
48 | ],
49 | "version": "==0.4.4"
50 | },
51 | "django-model-utils": {
52 | "hashes": [
53 | "sha256:4356fad5f6fc9910da865fbf9371f9c2e028606dff2bbd8e32e67569260e530d",
54 | "sha256:a9baa7de943b4e8afa61728ce8c42ce99e88cc87d40e74df2b060a78d22c0f5c"
55 | ],
56 | "version": "==3.1.1"
57 | },
58 | "graphene": {
59 | "hashes": [
60 | "sha256:dc95daa3f808fa1c1b26b780d103216a3d562eb95f72744a054b5bf6609d5c5c",
61 | "sha256:cf8c7fd4dc74c959a44cd817d9e1b07abad185bbf586aac1f75650c1abdd5d31"
62 | ],
63 | "version": "==2.1"
64 | },
65 | "graphene-django": {
66 | "hashes": [
67 | "sha256:5cabf46b59f242a533fe1df1912c87d5ad6606246937609be2e6c9086cfdf7a4",
68 | "sha256:70d9358bc48c806b6a9458e8344f0a1211cd1f1ef923a092aa55e6bcacc2c3fc"
69 | ],
70 | "version": "==2.0.0"
71 | },
72 | "graphql-core": {
73 | "hashes": [
74 | "sha256:539355351343dede3ecb771e0d273a1b72405cb6d64f45bb8f92ecc4d7109af0",
75 | "sha256:4830699be53f9154273fa15726fc8b0c90bc22bbb8fc7c932586503b3cb9330e"
76 | ],
77 | "version": "==2.0"
78 | },
79 | "graphql-relay": {
80 | "hashes": [
81 | "sha256:2716b7245d97091af21abf096fabafac576905096d21ba7118fba722596f65db"
82 | ],
83 | "version": "==0.4.5"
84 | },
85 | "iso8601": {
86 | "hashes": [
87 | "sha256:210e0134677cc0d02f6028087fee1df1e1d76d372ee1db0bf30bf66c5c1c89a3",
88 | "sha256:bbbae5fb4a7abfe71d4688fd64bff70b91bbd74ef6a99d964bab18f7fdf286dd",
89 | "sha256:49c4b20e1f38aa5cf109ddcd39647ac419f928512c869dc01d5c7098eddede82"
90 | ],
91 | "version": "==0.1.12"
92 | },
93 | "numpy": {
94 | "hashes": [
95 | "sha256:719d914f564f35cce4dc103808f8297c807c9f0297ac183ed81ae8b5650e698e",
96 | "sha256:0f6a5ed0cd7ab1da11f5c07a8ecada73fc55a70ef7bb6311a4109891341d7277",
97 | "sha256:d0928076d9bd8a98de44e79b1abe50c1456e7abbb40af7ef58092086f1a6c729",
98 | "sha256:d858423f5ed444d494b15c4cc90a206e1b8c31354c781ac7584da0d21c09c1c3",
99 | "sha256:20cac3123d791e4bf8482a580d98d6b5969ba348b9d5364df791ba3a666b660d",
100 | "sha256:528ce59ded2008f9e8543e0146acb3a98a9890da00adf8904b1e18c82099418b",
101 | "sha256:56e392b7c738bd70e6f46cf48c8194d3d1dd4c5a59fae4b30c58bb6ef86e5233",
102 | "sha256:99051e03b445117b26028623f1a487112ddf61a09a27e2d25e6bc07d37d94f25",
103 | "sha256:768e777cc1ffdbf97c507f65975c8686ebafe0f3dc8925d02ac117acc4669ce9",
104 | "sha256:675e0f23967ce71067d12b6944add505d5f0a251f819cfb44bdf8ee7072c090d",
105 | "sha256:a958bf9d4834c72dee4f91a0476e7837b8a2966dc6fcfc42c421405f98d0da51",
106 | "sha256:bb370120de6d26004358611441e07acda26840e41dfedc259d7f8cc613f96495",
107 | "sha256:f2b1378b63bdb581d5d7af2ec0373c8d40d651941d283a2afd7fc71184b3f570",
108 | "sha256:a1413d06abfa942ca0553bf3bccaff5fdb36d55b84f2248e36228db871147dab",
109 | "sha256:7f76d406c6b998d6410198dcb82688dcdaec7d846aa87e263ccf52efdcfeba30",
110 | "sha256:a7157c9ac6bddd2908c35ef099e4b643bc0e0ebb4d653deb54891d29258dd329",
111 | "sha256:0fd65cbbfdbf76bbf80c445d923b3accefea0fe2c2082049e0ce947c81fe1d3f",
112 | "sha256:8c18ee4dddd5c6a811930c0a7c7947bf16387da3b394725f6063f1366311187d",
113 | "sha256:0739146eaf4985962f07c62f7133aca89f3a600faac891ce6c7f3a1e2afe5272",
114 | "sha256:07e21f14490324cc1160db101e9b6c1233c33985af4cb1d301dd02650fea1d7f",
115 | "sha256:e6120d63b50e2248219f53302af7ec6fa2a42ed1f37e9cda2c76dbaca65036a7",
116 | "sha256:6be6b0ca705321c178c9858e5ad5611af664bbdfae1df1541f938a840a103888",
117 | "sha256:facc6f925c3099ac01a1f03758100772560a0b020fb9d70f210404be08006bcb"
118 | ],
119 | "version": "==1.14.2"
120 | },
121 | "pandas": {
122 | "hashes": [
123 | "sha256:68ac484e857dcbbd07ea7c6f516cc67f7f143f5313d9bc661470e7f473528882",
124 | "sha256:12f2a19d0b0adf31170d98d0e8bcbc59add0965a9b0c65d39e0665400491c0c5",
125 | "sha256:68b121d13177f5128a4c118bb4f73ba40df28292c038389961aa55ea5a996427",
126 | "sha256:06efae5c00b9f4c6e6d3fe1eb52e590ff0ea8e5cb58032c724e04d31c540de53",
127 | "sha256:02541a4fdd31315f213a5c8e18708abad719ee03eda05f603c4fe973e9b9d770",
128 | "sha256:2907f3fe91ca2119ac3c38de6891bbbc83333bfe0d98309768fee28de563ee7a",
129 | "sha256:052a66f58783a59ea38fdfee25de083b107baa81fdbe38fabd169d0f9efce2bf",
130 | "sha256:244ae0b9e998cfa88452a49b20e29bf582cc7c0e69093876d505aec4f8e1c7fe",
131 | "sha256:66403162c8b45325a995493bdd78ad4d8be085e527d721dbfa773d56fbba9c88",
132 | "sha256:af0dbac881f6f87acd325415adea0ce8cccf28f5d4ad7a54b6a1e176e2f7bf70",
133 | "sha256:c2cd884794924687edbaad40d18ac984054d247bb877890932c4d41e3c3aba31",
134 | "sha256:c372db80a5bcb143c9cb254d50f902772c3b093a4f965275197ec2d2184b1e61",
135 | "sha256:97c8223d42d43d86ca359a57b4702ca0529c6553e83d736e93a5699951f0f8db",
136 | "sha256:587a9816cc663c958fcff7907c553b73fe196604f990bc98e1b71ebf07e45b44",
137 | "sha256:44a94091dd71f05922eec661638ec1a35f26d573c119aa2fad964f10a2880e6c"
138 | ],
139 | "version": "==0.22.0"
140 | },
141 | "promise": {
142 | "hashes": [
143 | "sha256:0bca4ed933e3d50e3d18fb54fc1432fa84b0564838cd093e824abcd718ab9304",
144 | "sha256:95506bac89df7a495e0b8c813fd782dd1ae590decb52f95248e316c6659ca49b"
145 | ],
146 | "version": "==2.1"
147 | },
148 | "psycopg2": {
149 | "hashes": [
150 | "sha256:aeaba399254ca79c299d9fe6aa811d3c3eac61458dee10270de7f4e71c624998",
151 | "sha256:1d90379d01d0dc50ae9b40c863933d87ff82d51dd7d52cea5d1cb7019afd72cd",
152 | "sha256:36030ca7f4b4519ee4f52a74edc4ec73c75abfb6ea1d80ac7480953d1c0aa3c3",
153 | "sha256:7cbc3b21ce2f681ca9ad2d8c0901090b23a30c955e980ebf1006d41f37068a95",
154 | "sha256:b178e0923c93393e16646155794521e063ec17b7cc9f943f15b7d4b39776ea2c",
155 | "sha256:fe6a7f87356116f5ea840c65b032af17deef0e1a5c34013a2962dd6f99b860dd",
156 | "sha256:6f302c486132f8dd11f143e919e236ea4467d53bf18c451cac577e6988ecbd05",
157 | "sha256:888bba7841116e529f407f15c6d28fe3ef0760df8c45257442ec2f14f161c871",
158 | "sha256:932a4c101af007cb3132b1f8a9ffef23386acc53dad46536dc5ba43a3235ae02",
159 | "sha256:179c52eb870110a8c1b460c86d4f696d58510ea025602cd3f81453746fccb94f",
160 | "sha256:33f9e1032095e1436fa9ec424abcbd4c170da934fb70e391c5d78275d0307c75",
161 | "sha256:092a80da1b052a181b6e6c765849c9b32d46c5dac3b81bf8c9b83e697f3cdbe8",
162 | "sha256:f3d3a88128f0c219bdc5b2d9ccd496517199660cea021c560a3252116df91cbd",
163 | "sha256:19983b77ec1fc2a210092aa0333ee48811fd9fb5f194c6cd5b927ed409aea5f8",
164 | "sha256:027ae518d0e3b8fff41990e598bc7774c3d08a3a20e9ecc0b59fb2aaaf152f7f",
165 | "sha256:363fbbf4189722fc46779be1fad2597e2c40b3f577dc618f353a46391cf5d235",
166 | "sha256:d74cf9234ba76426add5e123449be08993a9b13ff434c6efa3a07caa305a619f",
167 | "sha256:32702e3bd8bfe12b36226ba9846ed9e22336fc4bd710039d594b36bd432ae255",
168 | "sha256:8eb94c0625c529215b53c08fb4e461546e2f3fc96a49c13d5474b5ad7aeab6cf",
169 | "sha256:8ebba5314c609a05c6955e5773c7e0e57b8dd817e4f751f30de729be58fa5e78",
170 | "sha256:27467fd5af1dcc0a82d72927113b8f92da8f44b2efbdb8906bd76face95b596d",
171 | "sha256:b68e89bb086a9476fa85298caab43f92d0a6af135a5f433d1f6b6d82cafa7b55",
172 | "sha256:0b9851e798bae024ed1a2a6377a8dab4b8a128a56ed406f572f9f06194e4b275",
173 | "sha256:733166464598c239323142c071fa4c9b91c14359176e5ae7e202db6bcc1d2eb5",
174 | "sha256:ad75fe10bea19ad2188c5cb5fc4cdf53ee808d9b44578c94a3cd1e9fc2beb656",
175 | "sha256:8966829cb0d21a08a3c5ac971a2eb67c3927ae27c247300a8476554cc0ce2ae8",
176 | "sha256:8bf51191d60f6987482ef0cfe8511bbf4877a5aa7f313d7b488b53189cf26209"
177 | ],
178 | "version": "==2.7.4"
179 | },
180 | "python-dateutil": {
181 | "hashes": [
182 | "sha256:3220490fb9741e2342e1cf29a503394fdac874bc39568288717ee67047ff29df",
183 | "sha256:9d8074be4c993fbe4947878ce593052f71dac82932a677d49194d8ce9778002e"
184 | ],
185 | "version": "==2.7.2"
186 | },
187 | "pytz": {
188 | "hashes": [
189 | "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555",
190 | "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749"
191 | ],
192 | "version": "==2018.4"
193 | },
194 | "rx": {
195 | "hashes": [
196 | "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105",
197 | "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"
198 | ],
199 | "version": "==1.6.1"
200 | },
201 | "scikit-learn": {
202 | "hashes": [
203 | "sha256:3775cca4ce3f94508bb7c8a6b113044b78c16b0a30a5c169ddeb6b9fe57a8a72",
204 | "sha256:873245b03361710f47c5410a050dc56ee8ae97b9f8dcc6e3a81521ca2b64ad10",
205 | "sha256:370919e3148253fd6552496c33a1e3d78290a336fc8d1b9349d9e9770fae6ec0",
206 | "sha256:ce78bf4d10bd7e28807c36c6d2ab25a9934aaf80906ad987622a5e45627d91a2",
207 | "sha256:ba3fd442ae1a46830789b3578867daaf2c8409dcca6bf192e30e85beeabbfc2f",
208 | "sha256:a21cf8217e31a9e8e32c559246e05e6909981816152406945ae2e3e244dfcc1f",
209 | "sha256:e54a3dd1fe1f8124de90b93c48d120e6da2ea8df29b6895325df01ddc1bd8e26",
210 | "sha256:f9abae483f4d52acd6f660addb1b67e35dc5748655250af479de2ea6aefc6df0",
211 | "sha256:5c9ff456d67ef9094e5ea272fff2be05d399a47fc30c6c8ed653b94bdf787bd1",
212 | "sha256:871669cdb5b3481650fe3adff46eb97c455e30ecdc307eaf382ef90d4e2570ab",
213 | "sha256:d4da369614e55540c7e830ccdd17ab4fe5412ff8e803a4906d3ece393e2e3a63",
214 | "sha256:42f3c5bd893ed73bf47ccccf04dfb98fae743f397d688bb58c2238c0e6ec15d2",
215 | "sha256:95b155ef6bf829ddfba6026f100ba8e4218b7171ecab97b2163bc9e8d206848f",
216 | "sha256:72c194c5092e921d6107a8de8a5adae58c35bbc54e030ba624b6f02fd823bb21",
217 | "sha256:f528c4b2bba652cf116f5cccf36f4db95a7f9cbfcd1ee549c4e8d0f8628783b5",
218 | "sha256:d384e6f9a055b7a43492f9d27779adb717eb5dcf78b0603b01d0f070a608d241",
219 | "sha256:ee8c3b1898c728b6e5b5659c233f547700a1fea13ce876b6fe7d3434c70cc0e0",
220 | "sha256:56cfa19c31edf62e6414da0a337efee37a4af488b135640e67238786b9be6ab3",
221 | "sha256:5db9e68a384ce80a17fc449d4d5d9b45025fe17cf468429599bf404eccb51049",
222 | "sha256:8b17fc29554c5c98d88142f895516a5bec2b6b61daa815e1193a64c868ad53d2",
223 | "sha256:13136c6e4f6b808569f7f59299d439b2cd718f85d72ea14b5b6077d44ebc7d17",
224 | "sha256:ddc1eb10138ae93c136cc4b5945d3977f302b5d693592a4731b2805a7d7f2a74",
225 | "sha256:5ca0ad32ee04abe0d4ba02c8d89d501b4e5e0304bdf4d45c2e9875a735b323a0",
226 | "sha256:6e0899953611d0c47c0d49c5950082ab016b38811fced91cd2dcc889dd94f50a",
227 | "sha256:b2a10e2f9b73de10d8486f7a23549093436062b69139158802910a0f154aa53b",
228 | "sha256:a58746d4f389ea7df1d908dba8b52f709835f91c342f459a3ade5424330c69d1",
229 | "sha256:fdc39e89bd3466befb76dfc0c258d4ccad159df974954a87de3be5759172a067"
230 | ],
231 | "version": "==0.19.1"
232 | },
233 | "scipy": {
234 | "hashes": [
235 | "sha256:3fab61f6ffbc1da9e1f8e813ba235929b5f361c1fdd728d120ead8f78560427c",
236 | "sha256:4a9f5a3adbfd08dd047de48a6b436bd4dae364913aa5b6349a41e9eaeb774050",
237 | "sha256:cc85b9d10a5da07a6b27449d17584b1a3d953f7286189f170a6045c6c297b0bc",
238 | "sha256:76f32095863f7eccecaf5224624d4c7f0f3b922b2cd0d0b0d6f037e4d9c54db6",
239 | "sha256:232a848a9935c37ffe2cade857296a13680724aef7d4623160dd08f99dbb9d02",
240 | "sha256:3eb28bdc6f40a964761c1cb339e45fef076ea2ca729886e1bce956932ef7e487",
241 | "sha256:103fd8253fd3f0df3f50a554abc01b13bee1d70483f23bb03636a74b0a0cbc71",
242 | "sha256:f26e54dcdeec3daff6ec71854a7b3bba719b78cf07e2d23645ee09d67a5e93df",
243 | "sha256:a3287b438b3c13970c4851a34bbc82db11e4680d1d6cdedd066479ca83028042",
244 | "sha256:200ca3dfebbd0bbacbc0d7389e6eda77428b49793cc1e0e3247f835687d1a6be",
245 | "sha256:f7c0624de958e2e2e6353813c78aa4f5f3f1ed0a3567fb496b6dad99f55e695e",
246 | "sha256:544e6f7796b9c131f8f965967e806da187553abf5d7766278b96a2a76abd19a7",
247 | "sha256:4a2837b07e77f8e2fc5303f38e224d7fd9f79f8cbf9c60ac72cf98594e1db6b5",
248 | "sha256:11cddcfc348ef593feb4ffe4b69c18a064eca199602f751d34083838bdc2f05a",
249 | "sha256:164f774de04e546fd10e1894d423b54e36255bb42887e005f0fbfb8eef6736f1",
250 | "sha256:800abaa55cfad422f00f5b3802403dd634ab9888f560731c09977a3c35e0acae",
251 | "sha256:be6f0af3f591c100923158f3e3f0f12fa16a0b231616eda407c528e1f9e10014",
252 | "sha256:1766acf2d8ff79ed59cae841d4272587af94772f513619dd217226027bd2ab76",
253 | "sha256:f2a51f70ef868c03430ed40d8983daa2f38d2e2160a0de4b57b7d9d070912c76",
254 | "sha256:89c1d2a80760a373e7f12f490446c60a7221b641435a789439e8ddb01f5ab7d4",
255 | "sha256:823a4b1a2eabd09f9e72003c14ceaac63f54d1f99a49f8c1b534f6a73135e995",
256 | "sha256:cc04cf28b9f77255eeb612af41a6f207142f92a082555871f782c773c83b7789",
257 | "sha256:8739c67842ed9a1c34c62d6cca6301d0ade40d50ef14ba292bd331f0d6c940ba"
258 | ],
259 | "version": "==1.0.1"
260 | },
261 | "singledispatch": {
262 | "hashes": [
263 | "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8",
264 | "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c"
265 | ],
266 | "version": "==3.4.0.3"
267 | },
268 | "six": {
269 | "hashes": [
270 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb",
271 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
272 | ],
273 | "version": "==1.11.0"
274 | },
275 | "sklearn": {
276 | "hashes": [
277 | "sha256:e23001573aa194b834122d2b9562459bf5ae494a2d59ca6b8aa22c85a44c0e31"
278 | ],
279 | "version": "==0.0"
280 | },
281 | "typing": {
282 | "hashes": [
283 | "sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8",
284 | "sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf",
285 | "sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2"
286 | ],
287 | "version": "==3.6.4"
288 | },
289 | "uwsgi": {
290 | "hashes": [
291 | "sha256:3dc2e9b48db92b67bfec1badec0d3fdcc0771316486c5efa3217569da3528bf2"
292 | ],
293 | "version": "==2.0.17"
294 | }
295 | },
296 | "develop": {
297 | "attrs": {
298 | "hashes": [
299 | "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450",
300 | "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"
301 | ],
302 | "version": "==17.4.0"
303 | },
304 | "flake8": {
305 | "hashes": [
306 | "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37",
307 | "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0"
308 | ],
309 | "version": "==3.5.0"
310 | },
311 | "mccabe": {
312 | "hashes": [
313 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
314 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
315 | ],
316 | "version": "==0.6.1"
317 | },
318 | "more-itertools": {
319 | "hashes": [
320 | "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e",
321 | "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea",
322 | "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44"
323 | ],
324 | "version": "==4.1.0"
325 | },
326 | "pluggy": {
327 | "hashes": [
328 | "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
329 | "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5",
330 | "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"
331 | ],
332 | "version": "==0.6.0"
333 | },
334 | "py": {
335 | "hashes": [
336 | "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a",
337 | "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881"
338 | ],
339 | "version": "==1.5.3"
340 | },
341 | "pycodestyle": {
342 | "hashes": [
343 | "sha256:5ff2fbcbab997895ba9ead77e1b38b3ebc2e5c3b8a6194ef918666e4c790a00e",
344 | "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9",
345 | "sha256:1ec08a51c901dfe44921576ed6e4c1f5b7ecbad403f871397feedb5eb8e4fa14",
346 | "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766"
347 | ],
348 | "version": "==2.3.1"
349 | },
350 | "pyflakes": {
351 | "hashes": [
352 | "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
353 | "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
354 | ],
355 | "version": "==1.6.0"
356 | },
357 | "pytest": {
358 | "hashes": [
359 | "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c",
360 | "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1"
361 | ],
362 | "version": "==3.5.0"
363 | },
364 | "pytest-django": {
365 | "hashes": [
366 | "sha256:534505e0261cc566279032d9d887f844235342806fd63a6925689670fa1b29d7",
367 | "sha256:7501942093db2250a32a4e36826edfc542347bb9b26c78ed0649cdcfd49e5789"
368 | ],
369 | "version": "==3.2.1"
370 | },
371 | "six": {
372 | "hashes": [
373 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb",
374 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
375 | ],
376 | "version": "==1.11.0"
377 | }
378 | }
379 | }
380 |
--------------------------------------------------------------------------------