Source

yt.hub / ythub / views.py

Full commit
import uuid, string
from random import choice
from ythub import app
from flask import render_template, session, redirect, url_for, escape, \
    request, make_response
from flask import g, flash, abort, Response, jsonify, json
from ythub.models import User, data_query, type_query, obj_query, \
                         MapDataProxy, container, sim_query, \
                         shorturl_query, project_query
from yt.mods import *
from ythub.map_view import MapTileView
from ythub.uploader import UploadHandler
from yt.utilities.minimal_representation import \
    MinimalStaticOutput, MinimalProjectionData
from ythub.authentication import require_api_key
from math import ceil
from ythub.send_email import pw_recovery_template, new_user_notice, \
                             welcome_notice
from ythub.database import db_session, ses_conn
from ythub.local_config import admin_emails

from ythub.authentication import \
    register_user, login_user, login_required, logout_user, \
    SESSION_USERNAME

CHUNK_SIZE = 1024*1024*16

@app.route("/getting_started.html")
def getting_started():
    return render_template("getting_started.html")

@app.route("/")
def index():
    dss = data_query()
    data, projects = [], []
    for ds in dss:
        if hasattr(ds, 'user'):
            u = ds.user
        elif hasattr(ds, 'pf'):
            u = ds.pf.user
        else:
            print "SKIPPING", ds
            continue
        if ds.obj_type == 'project':
            projects.append((u, ds))
        else:
            data.append((u, ds))
    data.sort(key = lambda a: -getattr(a[1], "created", 0))
    projects.sort(key = lambda a: -getattr(a[1], "created", 0))
    return render_template("index.html", data = data, projects = projects)

@app.route("/create_user", methods=["GET", "POST"])
def create_user():
    error = None
    if request.method == 'POST':
        if request.form.get("zap", "") != "rowsdower":
            mylog.warning("False attempt with a manual post.")
            flash("Nice try.")
            return redirect("/")
        if request.form.get('password2') != request.form.get('password'):
            mylog.warning("Bad password matching")
            flash("Passwords don't match.")
            return render_template("create_user.html")
        user = register_user(request.form.get('name'),
                             request.form.get('email'),
                             request.form.get('username'),
                             request.form.get('password'),
                             request.form.get('url', ""))
        if user:
            print "Created new user: %s" % user.username
            login_user(request.form.get('username'),
                       request.form.get('password'))
            email_subject = "Welcome to the yt Data Hub!"
            email_body = welcome_notice % {'username':user.username,
                                           'name':user.name}
            ses_conn.send_email(
                "hub@yt-project.org", email_subject, email_body,
                user.email)
            email_subject = "New yt Data Hub User"
            email_body = new_user_notice % {'username':user.username,
                                            'name':user.name,
                                            'email':user.email}
            ses_conn.send_email(
                "hub@yt-project.org", 
                 email_subject, email_body, admin_emails)
            flash('Registration successful.')
            return redirect('/')
        else:
            mylog.warning("Failed to register user: %s",
                          request.form.get("username"))
            flash('Failed to register user. Try another username.')
            return make_response("Could not create user.", 400)
    flash('Registrations are currently disabled.')
    return redirect('/')
    return render_template("create_user.html")

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        user = login_user(request.form.get('username'),
                          request.form.get('password'))
        if user:
            flash('Successful login.')
            return redirect('/user')
        else:
            flash('Invalid login.')
    return render_template('login.html', error=error)

@app.route('/reset_pw', methods=['POST'])
def pw_recovery():
    email = request.form.get('email')
    user = User.query.filter(User.email == email).first()
    if not user: return make_response("NOT FOUND", 404)
    chars = string.digits + string.letters
    new_pass = ''.join([choice(chars) for i in range(12)])
    email_body = pw_recovery_template % new_pass
    email_subject = "yt data hub: Password Reset"
    ses_conn.send_email(
        'hub@yt-project.org', email_subject, email_body, [email]
    )
    user.set_password(new_pass)
    db_session.commit()
    flash("Password reset and sent by email.")
    return redirect("/")

@app.route('/user/', methods=['POST'])
@login_required
def update_user(user):
    user.email = request.form.get("email")
    user.url = request.form.get("url")
    user.name = request.form.get("name")
    pw = request.form.get("password")
    if pw != "" and request.form.get('password2') != pw:
        flash("New passwords don't match.")
        return redirect("/user")
    elif pw != "":
        user.set_password(pw)
    db_session.commit()
    flash("Updated!")
    return redirect("/user")

@app.route('/user/')
@login_required
def user_profile(user):
    return render_template('user.html', user = user)

@app.route('/reset_api_key/')
@login_required
def reset_api_key(user):
    user.api_key = uuid.uuid4().hex
    db_session.commit()
    flash("Your API key has been reset")
    return redirect("/user")

@app.route("/users/")
def user_listing():
    users = User.query.all()
    users.sort(key = lambda u: u.username)
    return render_template('users.html', users = users)

@app.route("/users/<user_name>/")
def user_data(user_name):
    show_delete = (session.get(SESSION_USERNAME) == user_name)
    print "SHOW_DELETE", show_delete, session.get(SESSION_USERNAME), user_name
    user = User.query.filter(User.username == user_name).all()
    if len(user) == 0: return make_response("USER NOT FOUND", 404)
    user = user[0]
    dss = data_query()
    data = []
    for ds in dss:
        if hasattr(ds, 'user'):
            u = ds.user
        elif hasattr(ds, 'pf'):
            u = ds.pf.user
        else:
            print "SKIPPING", ds
            continue
        print ds, getattr(ds, 'created', 0)
        if u.username != user_name: continue
        data.append(ds)
    return render_template("user_data.html", user = user, data = data,
                           show_delete = show_delete)

@app.route('/logout/')
def logout():
    flash("Logged out.")
    logout_user()
    return redirect('/')

@app.route("/map_server/<map_id>/")
def mapserver(map_id = None):
    map = obj_query(map_id, "proj") + obj_query(map_id, "slice")
    if len(map) != 1:
        return make_response("MAP NOT FOUND (%s)" % len(map), 404)
    map = map[0]
    container['map_server_%s' % map_id] = MapTileView(map, map.field)
    return render_template("mapserver.html", map_id = map_id), 200

@app.route("/notebooks/<notebook_id>.ipynb")
def notebook_view(notebook_id = None):
    nb = obj_query(notebook_id, "notebook")
    if len(nb) != 1:
        return make_response("NOTEBOOK NOT FOUND (%s)" % len(nb), 404)
    nb = nb[0]
    rv = make_response(nb['notebook'].tostring())
    rv.mimetype = "text/plain"
    return rv

@app.route("/vertices/<vertices_id>/")
def vertices_display(vertices_id):
    vertices = obj_query(vertices_id, "vertices")
    if len(vertices) != 1:
        return make_response("VERTICES NOT FOUND (%s)", 404)
    vertices = vertices[0]
    container['vertex_server_%s' % vertices_id] = vertices
    return render_template("vertices.html",
            vtk_url="/vertices/%s/vert.vtk" % (vertices_id)), 200

@app.route("/vertices/<vertices_id>/vert.vtk")
def vertices_field_display(vertices_id):
    name = 'vertex_server_%s' % vertices_id
    if name not in container: return make_response("VERTICES EXPIRED", 404)
    vertices = container[name] 
    rv = app.make_response(vertices.tovtk(vertices.field))
    rv.mimetype = "text/plain"
    print "RETURNING!"
    return rv

@app.route("/image_collection/<collection_hash>/")
def image_collection(collection_hash = None):
    if collection_hash is None:
        return make_response("IMAGE COLLECTION NOT FOUND", 404)
    collection = obj_query(collection_hash, "image_collection")
    if len(collection) == 0:
        return make_response("IMAGE COLLECTION NOT FOUND", 404)
    elif len(collection) > 1:
        return make_response("WEIRD ERROR!", 404)
    collection = collection[0]
    container['image_server_%s' % collection_hash] = collection
    return render_template("image_collection.html", collection = collection)

@app.route("/image_collection/<collection_hash>/image/<image_num>.png")
def image_view(collection_hash, image_num):
    if image_num is None: return make_response("IMAGE NOT FOUND", 404)
    name = 'image_server_%s' % collection_hash
    if name not in container:
        return redirect("/image_collection/%s" % collection_hash)
    image_num = int(image_num)
    collection = container[name]
    if image_num > len(collection):
        return make_response("IMAGE NUMBER TOO HIGH", 404)
    image_data = collection[image_num]
    rv = app.make_response(image_data)
    rv.mimetype="image/png"
    return rv
    
from yt.utilities.logger import ytLogger as mylog
@app.route("/map_tiles/<map_id>/tiles")
def map_tile(map_id = None):
    x, y, z = (int(request.args[ii]) for ii in 'xyz')
    mylog.error("%s", request.args)
    np = float(256 * 2**z)
    xmi = float(request.args['xmin']) / np
    xma = float(request.args['xmax']) / np
    ymi = float(request.args['ymax']) / np
    yma = float(request.args['ymin']) / np
    data = container['map_server_%s' % map_id].map(
                int(z), int(x), int(y), (xmi, xma, ymi, yma))
    rv = app.make_response(data)
    rv.mimetype="image/png"
    return rv

@app.route("/map_tiles/<map_id>/fov")
def fov(map_id = None):
    z = int(request.args['z'])
    np = float(256 * 2**z)
    xmi = float(request.args['xmin']) / np
    xma = float(request.args['xmax']) / np
    ymi = float(request.args['ymax']) / np
    yma = float(request.args['ymin']) / np
    my_map = container['map_server_%s' % map_id]
    xmi, xma, ymi, yma = my_map.bounds_info((xmi, xma, ymi, yma))
    return jsonify( dict( unit = "native",
                          FOVx = "%0.3e" % (xma - xmi),
                          FOVy = "%0.3e" % (yma - ymi)) )

@app.route("/simulation_outputs/<output_hash>")
def simulation_display(output_hash = None):
    try:
        pf = sim_query(output_hash)
    except Exception as e:
        print e
        return make_response("SORRY NOT FOUND", 404)
    data = data_query(output_hash)
    return render_template("simulation_display.html", s = pf, data=data)

"""
@app.route("/simulation_outputs/<output_hash>/vertices")
def vertices_display(vertices_id):
    # ALL VERTICES HERE!
    vertices = obj_query(vertices_id)
    if len(vertices) == 1:
        return make_response("NO VERTICES TO DISPLAY" % len(map), 404)
    #container['vertex_server_%s' % vertices_id] = VertexView(vertices)
    #return render_template("vertices.html"), 200
"""

@app.route("/upload", methods = ["POST", "GET"])
@require_api_key
def upload_starter(user_id = None):
    # Check that we have a valid API key here ...
    uu = uuid.uuid4().hex
    chunk_data = json.loads(request.form['chunk_info'])
    metadata = json.loads(request.form['metadata'])
    print "UPLOAD STARTED FOR", user_id
    metadata['user_id'] = user_id
    try:
        container['uploader_%s' % uu] = UploadHandler(chunk_data, metadata)
    except KeyError:
        return make_response("Unknown datatype %s" % (metadata['obj_type']))
    return jsonify(handler_uuid = uu, chunk_size = CHUNK_SIZE)

@app.route("/upload/handler/<handler_uuid>", methods=["POST", "GET"])
def upload_handler(handler_uuid = None):
    handler = container.get('uploader_%s' % handler_uuid, None)
    if handler is None: return "SORRY!", 404
    return handler(request)

@app.route("/delete/<hub_id>")
@login_required
def delete_object(user, hub_id):
    data = obj_query(hub_id)
    if len(data) == 0:
        return make_response("OBJECT NOT FOUND", 400)
    elif len(data) > 1:
        return make_response("TOO MANY OBJECTS", 400)
    if data[0].user.id != user.id:
        return make_response("Incorrect user.", 401)
    if data[0].obj_type == "simulation_output":
        data += data_query(data[0].output_hash)
    # Now we either delete one or recursively delete
    for ds in data:
        ds.delete()
    return redirect("/users/%s" % user.username)

@app.route("/go/<shorturl>")
def go_shorturl(shorturl):
    data = shorturl_query(shorturl)
    if len(data) > 1:
        return make_response("Something is not right.", 400)
    elif len(data) == 0:
        return make_response("Short URL not found!", 404)
    return redirect(data[0]._url)

@app.route("/nb/<shorturl>")
def nb_shorturl(shorturl):
    data = shorturl_query(shorturl)
    if len(data) > 1:
        return make_response("Something is not right.", 400)
    elif len(data) == 0:
        return make_response("Short URL not found!", 404)
    elif data[0].obj_type != "notebook":
        return make_response("This is not a notebook.", 404)
    return redirect(data[0]._nburl)

@app.route("/project/<project_descr>")
def project_view(project_descr):
    project = project_query(project_descr)
    if project is None:
        return make_response("Project not found!", 404)
    return render_template("project.html", s=project)

@app.route("/projects/")
def projects():
    projects = [(p.user, p) for p in type_query("project")]
    projects.sort(key = lambda a: -getattr(a[1], "created", 0))
    return render_template("projects.html", projects = projects)