Skip to content

Commit

Permalink
Merge pull request #91 from squirrelo/webfleshout
Browse files Browse the repository at this point in the history
Base meta-analysis workflow interface
  • Loading branch information
josenavas committed Jun 18, 2014
2 parents cc3b832 + 12b53ec commit f505bf0
Show file tree
Hide file tree
Showing 38 changed files with 893 additions and 5 deletions.
46 changes: 44 additions & 2 deletions qiita_core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,52 @@
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------
from smtplib import SMTP, SMTP_SSL, SMTPException
try:
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
except ImportError: # python3
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

from qiita_core.qiita_settings import qiita_config
from qiita_db.sql_connection import SQLConnectionHandler
from qiita_db.environment_manager import (LAYOUT_FP, INITIALIZE_FP,
POPULATE_FP)
from qiita_core.qiita_settings import qiita_config


def send_email(to, subject, body):
# create email
msg = MIMEMultipart()
msg['From'] = qiita_config.smtp_email
msg['To'] = to
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain'))

# connect to smtp server, using ssl if needed
if qiita_config.smtp_ssl:
smtp = SMTP_SSL()
else:
smtp = SMTP()
smtp.set_debuglevel(False)
smtp.connect(qiita_config.smtp_host, qiita_config.smtp_port)
# try tls, if not available on server just ignore error
try:
smtp.starttls()
except SMTPException:
pass
smtp.ehlo_or_helo_if_needed()

if qiita_config.smtp_user:
smtp.login(qiita_config.smtp_user, qiita_config.smtp_password)

# send email
try:
smtp.sendmail(qiita_config.smtp_email, to, msg.as_string())
except Exception:
raise RuntimeError("Can't send email!")
finally:
smtp.close()


def build_test_database(setup_fn):
Expand Down Expand Up @@ -86,4 +128,4 @@ def tearDown(self):
del self.conn_handler

return DecoratedClass
return class_modifier
return class_modifier
19 changes: 17 additions & 2 deletions qiita_db/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@
# -----------------------------------------------------------------------------

from __future__ import division
from future.builtins import zip
from future.utils import viewitems
from datetime import date
from copy import deepcopy
Expand All @@ -105,7 +104,7 @@
from .base import QiitaStatusObject, QiitaObject
from .exceptions import (QiitaDBDuplicateError, QiitaDBStatusError,
QiitaDBColumnError)
from .util import check_required_columns, check_table_cols
from .util import check_required_columns, check_table_cols, convert_to_id
from .sql_connection import SQLConnectionHandler


Expand Down Expand Up @@ -149,6 +148,22 @@ def _status_setter_checks(self, conn_handler):
"""
self._lock_public(conn_handler)

@classmethod
def get_public(cls):
"""Returns study for all public Studies
Returns
-------
list of Study objects
All public studies in the database
"""
conn_handler = SQLConnectionHandler()
sql = ("SELECT study_id FROM qiita.{0} WHERE "
"{0}_status_id = %s".format(cls._table))
# MAGIC NUMBER 2: status id for a public study
return [cls(x[0]) for x in
conn_handler.execute_fetchall(sql, (2,))]

@classmethod
def create(cls, owner, title, efo, info, investigation=None):
"""Creates a new study on the database
Expand Down
6 changes: 6 additions & 0 deletions qiita_db/test/test_study.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ def setUp(self):
'lab_person_id': StudyPerson(1),
'number_samples_collected': 27}

def test_get_public(self):
new = Study.create(User('test@foo.bar'), 'Identification of the '
'Microbiomes for Cannabis Soils', [1], self.info)
obs = Study.get_public()
self.assertEqual(obs, [Study(1)])

def test_create_study_min_data(self):
"""Insert a study into the database"""
obs = Study.create(User('test@foo.bar'), "Fried chicken microbiome",
Expand Down
10 changes: 10 additions & 0 deletions qiita_pet/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python
from __future__ import division

# -----------------------------------------------------------------------------
# Copyright (c) 2014--, The Qiita Development Team.
#
# Distributed under the terms of the BSD 3-clause License.
#
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------
34 changes: 34 additions & 0 deletions qiita_pet/handlers/analysis_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from tornado.web import authenticated

from qiita_pet.handlers.base_handlers import BaseHandler
from qiita_db.user import User
from qiita_db.analysis import Analysis
from qiita_db.study import Study
# login code modified from https://gist.github.com/guillaumevincent/4771570


class CreateAnalysisHandler(BaseHandler):
"""Analysis creation"""
@authenticated
def get(self):
self.render('create_analysis.html', user=self.get_current_user())


class SelectStudiesHandler(BaseHandler):
"""Study selection"""
@authenticated
def post(self):
name = self.get_argument('name')
description = self.get_argument('description')
user = self.get_current_user()
# create list of studies
study_ids = {s.id for s in Study.get_public()}
userobj = User(user)
[study_ids.add(x) for x in userobj.private_studies]
[study_ids.add(x) for x in userobj.shared_studies]

studies = [Study(i) for i in study_ids]
analysis = Analysis.create(User(user), name, description)

self.render('select_studies.html', user=user, aid=analysis.id,
studies=studies)
98 changes: 98 additions & 0 deletions qiita_pet/handlers/auth_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python

from tornado.escape import url_escape, json_encode

from qiita_pet.handlers.base_handlers import BaseHandler
from qiita_core.util import send_email
from qiita_core.exceptions import IncorrectPasswordError, IncorrectEmailError
from qiita_db.user import User
from qiita_db.exceptions import QiitaDBUnknownIDError
# login code modified from https://gist.github.com/guillaumevincent/4771570


class AuthCreateHandler(BaseHandler):
"""User Creation"""
def get(self):
try:
error_message = self.get_argument("error")
# Tornado can raise an Exception directly, not a defined type
except:
error_message = ""
self.render("create_user.html", user=self.get_current_user(),
error=error_message)

def post(self):
username = self.get_argument("username", "")
password = self.get_argument("pass", "")
info = {}
for info_column in ("name", "affiliation", "address", "phone"):
hold = self.get_argument(info_column, None)
if hold:
info[info_column] = hold

created = User.create(username, password, info)

if created:
send_email(username, "FORGE: Verify Email Address", "Please click "
"the following link to verify email address: "
"http://forge-dev.colorado.edu/auth/verify/%s" % msg)
self.redirect(u"/")
else:
error_msg = u"?error=" + url_escape(msg)
self.redirect(u"/auth/create/" + error_msg)


class AuthVerifyHandler(BaseHandler):
def get(self):
email = self.get_argument("email")
code = self.get_argument("code")
try:
User(email).level = 3
msg = "Successfully verified user!"
except QiitaDBUnknownIDError:
msg = "Code not valid!"

self.render("user_verified.html", user=None, error=msg)


class AuthLoginHandler(BaseHandler):
"""user login, no page necessary"""
def post(self):
username = self.get_argument("username", "")
passwd = self.get_argument("password", "")
# check the user level
try:
if User(username).level == 4: # 4 is id for unverified
# email not verified so dont log in
msg = "Email not verified"
except QiitaDBUnknownIDError:
msg = "Unknown user"

# Check the login information
login = None
try:
login = User.login(username, passwd)
except IncorrectEmailError:
msg = "Unknown user"
except IncorrectPasswordError:
msg = "Incorrect password"

if login:
# everthing good so log in
self.set_current_user(username)
self.redirect("/")
return
self.render("index.html", user=None, loginerror=msg)

def set_current_user(self, user):
if user:
self.set_secure_cookie("user", json_encode(user))
else:
self.clear_cookie("user")


class AuthLogoutHandler(BaseHandler):
"""Logout handler, no page necessary"""
def get(self):
self.clear_cookie("user")
self.redirect("/")
46 changes: 46 additions & 0 deletions qiita_pet/handlers/base_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from tornado.web import RequestHandler


class BaseHandler(RequestHandler):
def get_current_user(self):
'''Overrides default method of returning user curently connected'''
user = self.get_secure_cookie("user")
if user is None:
self.clear_cookie("user")
return ''
else:
return user.strip('" ')

def write_error(self, status_code, **kwargs):
'''Overrides the error page created by Tornado'''
from traceback import format_exception
if self.settings.get("debug") and "exc_info" in kwargs:
exc_info = kwargs["exc_info"]
trace_info = ''.join(["%s<br />" % line for line in
format_exception(*exc_info)])
request_info = ''.join(["<strong>%s</strong>: %s<br />" %
(k, self.request.__dict__[k]) for k in
self.request.__dict__.keys()])
error = exc_info[1]

self.render('error.html', error=error, trace_info=trace_info,
request_info=request_info,
user=self.get_current_user())


class MainHandler(BaseHandler):
'''Index page'''
def get(self):
username = self.get_current_user()
completedanalyses = []
self.render("index.html", user=username, analyses=completedanalyses)


class MockupHandler(BaseHandler):
def get(self):
self.render("mockup.html", user=self.get_current_user())


class NoPageHandler(BaseHandler):
def get(self):
self.render("404.html", user=self.get_current_user())
64 changes: 64 additions & 0 deletions qiita_pet/handlers/websocket_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# adapted from
# https://github.com/leporo/tornado-redis/blob/master/demos/websockets

from redis import Redis
from tornadoredis import Client
from tornado.websocket import WebSocketHandler
import tornado.gen
from json import loads

# all messages are in json format. They must have the following format:
# 'job': jobname
# 'msg': message to print
# 'analysis': what analysis this is from in format datatype:analysis
# 'results': list of files created if any


class MessageHandler(WebSocketHandler):
def __init__(self, *args, **kwargs):
super(MessageHandler, self).__init__(*args, **kwargs)
self.r_server = Redis()
self.redis = Client()
self.redis.connect()

def get_current_user(self):
user = self.get_secure_cookie("user")
if user is None:
return ''
else:
return user.strip('" ')

def on_message(self, msg):
msginfo = loads(msg)
# listens for handshake from page
if "user:" in msginfo['msg']:
self.channel = msginfo['msg'].split(':')[1]
# need to split the rest off to new func so it can be asynchronous
self.listen()

# decorator turns the function into an asynchronous generator object
@tornado.gen.engine
def listen(self):
# runs task given, with the yield required to get returned value
# equivalent of callback/wait pairing from tornado.gen
yield tornado.gen.Task(self.redis.subscribe, self.channel)
if not self.redis.subscribed:
self.write_message('ERROR IN SUBSCRIPTION')
# listen from tornadoredis makes the listen object asynchronous
# if using standard redis lib, it blocks while listening
self.redis.listen(self.callback)
# fight race condition by loading from redis after listen started
# need to use std redis lib because tornadoredis is in subscribed state
oldmessages = self.r_server.lrange(self.channel + ':messages', 0, -1)
if oldmessages is not None:
for message in oldmessages:
self.write_message(message)

def callback(self, msg):
if msg.kind == 'message':
self.write_message(str(msg.body))

@tornado.gen.engine
def on_close(self):
yield tornado.gen.Task(self.redis.unsubscribe, self.channel)
self.redis.disconnect()
1 change: 1 addition & 0 deletions qiita_pet/results/admin/jobname/placeholder.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
THIS SHOULD SHOW UP!
Loading

0 comments on commit f505bf0

Please sign in to comment.