diff options
author | Zachary Sloan | 2014-02-25 21:31:16 +0000 |
---|---|---|
committer | Zachary Sloan | 2014-02-25 21:31:16 +0000 |
commit | f4f4b8a0badba231a72ab41d86d87408c871ac1b (patch) | |
tree | 48cfdaf0204cb75d15b199ebcc09b7d3b8c59b8c | |
parent | d8a6e48b4896d8ce74d9c082c21101a5a288cb80 (diff) | |
parent | 1ab9e95d11da24e087774ac38786ae934f783fa6 (diff) | |
download | genenetwork2-f4f4b8a0badba231a72ab41d86d87408c871ac1b.tar.gz |
Merge /home/sam/gene
-rw-r--r-- | wqflask/wqflask/collect.py | 103 | ||||
-rw-r--r-- | wqflask/wqflask/model.py | 44 | ||||
-rw-r--r-- | wqflask/wqflask/static/new/javascript/search_results.coffee | 61 | ||||
-rw-r--r-- | wqflask/wqflask/static/new/javascript/search_results.js | 76 | ||||
-rw-r--r-- | wqflask/wqflask/templates/base.html | 8 | ||||
-rw-r--r-- | wqflask/wqflask/templates/collections/add.html | 10 | ||||
-rw-r--r-- | wqflask/wqflask/templates/collections/add_anonymous.html | 22 | ||||
-rw-r--r-- | wqflask/wqflask/templates/collections/view.html | 13 | ||||
-rw-r--r-- | wqflask/wqflask/user_manager.py | 74 |
9 files changed, 351 insertions, 60 deletions
diff --git a/wqflask/wqflask/collect.py b/wqflask/wqflask/collect.py index 6ac4abcf..b1ea46d8 100644 --- a/wqflask/wqflask/collect.py +++ b/wqflask/wqflask/collect.py @@ -48,36 +48,51 @@ from base import trait @app.route("/collections/add") def collections_add(): - user_collections = g.user_session.user_ob.user_collections - print("user_collections are:", user_collections) - return render_template("collections/add.html", - traits=request.args['traits'], - user_collections = user_collections, - ) + traits=request.args['traits'], + + if g.user_session.logged_in: + user_collections = g.user_session.user_ob.user_collections + print("user_collections are:", user_collections) + return render_template("collections/add.html", + traits=traits, + user_collections = user_collections, + ) + else: + return render_template("collections/add_anonymous.html", + traits=traits + ) @app.route("/collections/new") def collections_new(): - print("request.args in collections_new are:", request.args) - if "create_new" in request.args: - return create_new() - elif "add_to_existing" in request.args: - return add_to_existing() - elif "continue" in request.args: - return unnamed() + params = request.args + print("request.args in collections_new are:", params) + + collection_name = params['new_collection'] + + if "create_new" in params: + return create_new(collection_name) + elif "add_to_existing" in params: + return add_traits(params, collection_name) + elif "Default" in params: + return add_traits(params, "Default") + else: CauseAnError -def unnamed(): - return "unnamed" -def add_to_existing(): - params = request.args +def add_traits(params, collection_name): print("---> params are:", params.keys()) print(" type(params):", type(params)) - uc = model.UserCollection.query.get(params['existing_collection']) - members = set(json.loads(uc.members)) + if collection_name=="Default": + uc = g.user_session.user_ob.get_collection_by_name("Default") + # Doesn't exist so we'll create it + if not uc: + return create_new("Default") + else: + uc = model.UserCollection.query.get(params['existing_collection']) + members = uc.members_as_set() #set(json.loads(uc.members)) len_before = len(members) traits = process_traits(params['traits']) @@ -103,7 +118,8 @@ def add_to_existing(): def process_traits(unprocessed_traits): print("unprocessed_traits are:", unprocessed_traits) - unprocessed_traits = unprocessed_traits.split(",") + if isinstance(unprocessed_traits, basestring): + unprocessed_traits = unprocessed_traits.split(",") traits = set() for trait in unprocessed_traits: data, _separator, hmac = trait.rpartition(':') @@ -114,10 +130,10 @@ def process_traits(unprocessed_traits): traits.add(str(data)) return traits -def create_new(): +def create_new(collection_name): params = request.args uc = model.UserCollection() - uc.name = params['new_collection'] + uc.name = collection_name print("user_session:", g.user_session.__dict__) uc.user = g.user_session.user_id unprocessed_traits = params['traits'] @@ -136,13 +152,54 @@ def create_new(): @app.route("/collections/list") def list_collections(): params = request.args - user_collections = g.user_session.user_ob.user_collections + user_collections = list(g.user_session.user_ob.user_collections) + print("user_collections are:", user_collections) return render_template("collections/list.html", params = params, user_collections = user_collections, ) +@app.route("/collections/remove", methods=('POST',)) +def remove_traits(): + params = request.form + print("params are:", params) + uc_id = params['uc_id'] + uc = model.UserCollection.query.get(uc_id) + traits_to_remove = params.getlist('traits[]') + print("traits_to_remove are:", traits_to_remove) + traits_to_remove = process_traits(traits_to_remove) + print("\n\n after processing, traits_to_remove:", traits_to_remove) + all_traits = uc.members_as_set() + print(" all_traits:", all_traits) + members_now = all_traits - traits_to_remove + print(" members_now:", members_now) + print("Went from {} to {} members in set.".format(len(all_traits), len(members_now))) + uc.members = json.dumps(list(members_now)) + uc.changed_timestamp = datetime.datetime.utcnow() + db_session.commit() + + # We need to return something so we'll return this...maybe in the future + # we can use it to check the results + return str(len(members_now)) + + + +@app.route("/collections/delete", methods=('POST',)) +def delete_collection(): + params = request.form + uc_id = params['uc_id'] + uc = model.UserCollection.query.get(uc_id) + # Todo: For now having the id is good enough since it's so unique + # But might want to check ownership in the future + collection_name = uc.name + db_session.delete(uc) + db_session.commit() + flash("We've deletet the collection: {}.".format(collection_name), "alert-info") + + return redirect(url_for('list_collections')) + + @app.route("/collections/view") def view_collection(): params = request.args diff --git a/wqflask/wqflask/model.py b/wqflask/wqflask/model.py index b508f18e..71bda550 100644 --- a/wqflask/wqflask/model.py +++ b/wqflask/wqflask/model.py @@ -10,6 +10,8 @@ from flask.ext.sqlalchemy import SQLAlchemy from wqflask import app +import sqlalchemy + from sqlalchemy import (Column, Integer, String, Table, ForeignKey, Unicode, Boolean, DateTime, Text, Index) from sqlalchemy.orm import relationship, backref @@ -57,8 +59,35 @@ class User(Base): user_collections = relationship("UserCollection", order_by="asc(UserCollection.name)", + lazy='dynamic', ) + def display_num_collections(self): + """ + Returns the number of collections or a blank string if there are zero. + + + Because this is so unimportant...we wrap the whole thing in a try/expect...last thing we + want is a webpage not to be displayed because of an error here + + Importand TODO: use redis to cache this, don't want to be constantly computing it + + """ + try: + num = len(list(self.user_collections)) + return display_collapsible(num) + except Exception as why: + print("Couldn't display_num_collections:", why) + return "" + + + def get_collection_by_name(self, collection_name): + try: + collect = self.user_collections.filter_by(name=collection_name).one() + except sqlalchemy.orm.exc.NoResultFound: + collect = None + return collect + @property def name_and_org(self): """Nice shortcut for printing out who the user is""" @@ -145,3 +174,18 @@ class UserCollection(Base): def num_members(self): print("members are:", json.loads(self.members)) return len(json.loads(self.members)) + + #@property + #def display_num_members(self): + # return display_collapsible(self.num_members) + + + def members_as_set(self): + return set(json.loads(self.members)) + + +def display_collapsible(number): + if number: + return number + else: + return "" diff --git a/wqflask/wqflask/static/new/javascript/search_results.coffee b/wqflask/wqflask/static/new/javascript/search_results.coffee index 34989a77..84cf7f0a 100644 --- a/wqflask/wqflask/static/new/javascript/search_results.coffee +++ b/wqflask/wqflask/static/new/javascript/search_results.coffee @@ -1,4 +1,8 @@ $ -> + # These are also used by collections view + # So the name search_results in the filename is misleading + + checked_traits = null select_all = -> console.log("selected_all") @@ -17,6 +21,59 @@ $ -> console.log("checked is:", traits) $.colorbox({href:"/collections/add?traits=#{traits}"}) + removed_traits = -> + # After we've removed the traits from the database we get rid of them in the table + console.log('in removed_traits with checked_traits:', checked_traits) + checked_traits.closest("tr").fadeOut() + + + change_buttons = -> + buttons = ["#add", "#remove"] + num_checked = $('.trait_checkbox:checked').length + console.log("num_checked is:", num_checked) + if (num_checked == 0) + for button in buttons + $(button).prop("disabled", true) + else + for button in buttons + $(button).prop("disabled", false) + + + if (num_checked > 1) + console.log("in loop") + for item in buttons + console.log(" processing item:", item) + text = $(item).html() + if text.indexOf("Records") == -1 + text = text.replace("Record", "Records") + $(item).html(text) + else + console.log("in loop") + for item in buttons + console.log(" processing item:", item) + text = $(item).html() + text = text.replace("Records", "Record") + $(item).html(text) + + + # remove is only used by collections view + remove = -> + checked_traits = $("#trait_table input:checked") + traits = checked_traits.map(-> + return $(this).val()).get() + console.log("checked length is:", traits.length) + console.log("checked is:", traits) + uc_id = $("#uc_id").val() + console.log("uc.id is:", uc_id) + # Todo: Consider adding a failure message + $.ajax( + type: "POST" + url: "/collections/remove" + data: + uc_id: uc_id + traits: traits + success: removed_traits + ) @@ -24,3 +81,7 @@ $ -> $("#deselect_all").click(deselect_all) $("#invert").click(invert) $("#add").click(add) + $("#remove").click(remove) + + $('.trait_checkbox').click(change_buttons) + $('.btn').click(change_buttons) diff --git a/wqflask/wqflask/static/new/javascript/search_results.js b/wqflask/wqflask/static/new/javascript/search_results.js index 3efaca8b..477c9b94 100644 --- a/wqflask/wqflask/static/new/javascript/search_results.js +++ b/wqflask/wqflask/static/new/javascript/search_results.js @@ -2,7 +2,8 @@ (function() { $(function() { - var add, deselect_all, invert, select_all; + var add, change_buttons, checked_traits, deselect_all, invert, remove, removed_traits, select_all; + checked_traits = null; select_all = function() { console.log("selected_all"); return $(".trait_checkbox").prop('checked', true); @@ -24,10 +25,81 @@ href: "/collections/add?traits=" + traits }); }; + removed_traits = function() { + console.log('in removed_traits with checked_traits:', checked_traits); + return checked_traits.closest("tr").fadeOut(); + }; + change_buttons = function() { + var button, buttons, item, num_checked, text, _i, _j, _k, _l, _len, _len1, _len2, _len3, _results, _results1; + buttons = ["#add", "#remove"]; + num_checked = $('.trait_checkbox:checked').length; + console.log("num_checked is:", num_checked); + if (num_checked === 0) { + for (_i = 0, _len = buttons.length; _i < _len; _i++) { + button = buttons[_i]; + $(button).prop("disabled", true); + } + } else { + for (_j = 0, _len1 = buttons.length; _j < _len1; _j++) { + button = buttons[_j]; + $(button).prop("disabled", false); + } + } + if (num_checked > 1) { + console.log("in loop"); + _results = []; + for (_k = 0, _len2 = buttons.length; _k < _len2; _k++) { + item = buttons[_k]; + console.log(" processing item:", item); + text = $(item).html(); + if (text.indexOf("Records") === -1) { + text = text.replace("Record", "Records"); + _results.push($(item).html(text)); + } else { + _results.push(void 0); + } + } + return _results; + } else { + console.log("in loop"); + _results1 = []; + for (_l = 0, _len3 = buttons.length; _l < _len3; _l++) { + item = buttons[_l]; + console.log(" processing item:", item); + text = $(item).html(); + text = text.replace("Records", "Record"); + _results1.push($(item).html(text)); + } + return _results1; + } + }; + remove = function() { + var traits, uc_id; + checked_traits = $("#trait_table input:checked"); + traits = checked_traits.map(function() { + return $(this).val(); + }).get(); + console.log("checked length is:", traits.length); + console.log("checked is:", traits); + uc_id = $("#uc_id").val(); + console.log("uc.id is:", uc_id); + return $.ajax({ + type: "POST", + url: "/collections/remove", + data: { + uc_id: uc_id, + traits: traits + }, + success: removed_traits + }); + }; $("#select_all").click(select_all); $("#deselect_all").click(deselect_all); $("#invert").click(invert); - return $("#add").click(add); + $("#add").click(add); + $("#remove").click(remove); + $('.trait_checkbox').click(change_buttons); + return $('.btn').click(change_buttons); }); }).call(this); diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html index 9b98c955..48078670 100644 --- a/wqflask/wqflask/templates/base.html +++ b/wqflask/wqflask/templates/base.html @@ -104,6 +104,14 @@ <a href="/links">Links</a> </li> <li class=""> + <a href="/collections/list">Collections + {% if g.user_session.user_ob %} + <span class="badge badge-info">{{ g.user_session.user_ob.display_num_collections() }}</span> + {% endif %} + </a> + </li> + + <li class=""> {% if g.user_session.logged_in %} <a id="login_out" title="Signed in as {{ g.user_session.user_ob.name_and_org }}." href="/n/logout">Sign out</a> {% else %} diff --git a/wqflask/wqflask/templates/collections/add.html b/wqflask/wqflask/templates/collections/add.html index 8b6a17a3..faee4f78 100644 --- a/wqflask/wqflask/templates/collections/add.html +++ b/wqflask/wqflask/templates/collections/add.html @@ -1,16 +1,16 @@ <div id="myModal"> <div class="modal-header"> <h3>Add to collection</h3> - <p>You have three choices: Contuine without naming the collection, creating a new named collection, - or adding the traits to an existing collection.</p> + <p>You have three choices: Use your default collection, create a new named collection, + or add the traits to an existing collection.</p> </div> <div class="modal-body"> <form action="/collections/new" data-validate="parsley" id="add_form"> <fieldset> - <legend>Continue without naming collection</legend> - <span class="help-block">Choose this if you don't plan on using the collection again.</span> + <legend>Use your default collection</legend> + <span class="help-block">Choose this if you're in a hurry or don't plan on using the collection again.</span> <span class="help-block"><em></em>If you are unsure this is probably the option you want.</em></span> - <button type="submit" name="continue" class="btn">Continue</button> + <button type="submit" name="Default" class="btn">Continue</button> </fieldset> <hr /> diff --git a/wqflask/wqflask/templates/collections/add_anonymous.html b/wqflask/wqflask/templates/collections/add_anonymous.html new file mode 100644 index 00000000..9259f667 --- /dev/null +++ b/wqflask/wqflask/templates/collections/add_anonymous.html @@ -0,0 +1,22 @@ +<div id="myModal"> + <div class="modal-header"> + <h3>Add to collection</h3> + <br /> + <div> + <p>We can add this to a temporary collection for you.</p> + <p>For the most features you'll want to sign in or create an account.</p> + </div> + </div> + <div class="modal-body"> + <form action="/collections/new" data-validate="parsley" id="add_form"> + <input type="hidden" name="traits" value="{{ traits }}" /> + <button type="submit" name="Default" class="btn btn-large btn-block btn-primary">Continue without signing in</button> + <button type="submit" name="create_new" class="btn btn-large btn-block">Sign in or create an account</button> + + </form> + </div> +</div> + +<script> + $('#add_form').parsley(); +</script> diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index 0ab004d9..7f588e84 100644 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -8,8 +8,18 @@ <div class="container"> <div class="page-header"> <h1>Your Collection</h1> + <h2>{{ uc.name }}</h2> + + <form action="/collections/delete" method="post"> + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc.id }}" /> + <input type="submit" + class="btn btn-small btn-danger" + value="Delete this collection" /> + </form> </div> + + <div class="bs-docs-example"> <table class="table table-hover" id='trait_table'> <thead> @@ -57,7 +67,8 @@ <button class="btn" id="select_all"><i class="icon-ok"></i> Select All</button> <button class="btn" id="deselect_all"><i class="icon-remove"></i> Deselect All</button> <button class="btn" id="invert"><i class="icon-resize-vertical"></i> Invert</button> - <button class="btn" id="add"><i class="icon-plus-sign"></i> Add</button> + <button class="btn" id="add" disabled="disabled"><i class="icon-plus-sign"></i> Add Record to Other Collection</button> + <button class="btn" id="remove" disabled="disabled"><i class="icon-minus-sign"></i> Remove Record</button> <button class="btn btn-primary pull-right"><i class="icon-download icon-white"></i> Download Table</button> </div> </div> diff --git a/wqflask/wqflask/user_manager.py b/wqflask/wqflask/user_manager.py index 1d0d9846..ff4535bb 100644 --- a/wqflask/wqflask/user_manager.py +++ b/wqflask/wqflask/user_manager.py @@ -22,6 +22,7 @@ import urlparse import simplejson as json +import sqlalchemy from sqlalchemy import orm #from redis import StrictRedis @@ -188,7 +189,15 @@ class RegisterUser(object): self.new_user = model.User(**self.user.__dict__) db_session.add(self.new_user) - db_session.commit() + + try: + db_session.commit() + except sqlalchemy.exc.IntegrityError: + # This exception is thrown if the email address is already in the database + # To do: Perhaps put a link to sign in using an existing account here + self.errors.append("An account with this email address already exists. " + "Click the button above to sign in using an existing account.") + return print("Adding verification email to queue") #self.send_email_verification() @@ -372,37 +381,44 @@ class LoginUser(object): if not params: return render_template("new_security/login_user.html") else: - user = model.User.query.filter_by(email_address=params['email_address']).one() - submitted_password = params['password'] - pwfields = Struct(json.loads(user.password)) - encrypted = Password(submitted_password, - pwfields.salt, - pwfields.iterations, - pwfields.keylength, - pwfields.hashfunc) - print("\n\nComparing:\n{}\n{}\n".format(encrypted.password, pwfields.password)) - valid = pbkdf2.safe_str_cmp(encrypted.password, pwfields.password) - print("valid is:", valid) - - if valid and not user.confirmed: - VerificationEmail(user) - return render_template("new_security/verification_still_needed.html", - subject=VerificationEmail.subject) - - - if valid: - if params.get('remember'): - print("I will remember you") - self.remember_me = True - - return self.actual_login(user) - + try: + user = model.User.query.filter_by(email_address=params['email_address']).one() + except sqlalchemy.orm.exc.NoResultFound: + print("No account exists for that email address") + valid = False + user = None else: + submitted_password = params['password'] + pwfields = Struct(json.loads(user.password)) + encrypted = Password(submitted_password, + pwfields.salt, + pwfields.iterations, + pwfields.keylength, + pwfields.hashfunc) + print("\n\nComparing:\n{}\n{}\n".format(encrypted.password, pwfields.password)) + valid = pbkdf2.safe_str_cmp(encrypted.password, pwfields.password) + print("valid is:", valid) + + if valid and not user.confirmed: + VerificationEmail(user) + return render_template("new_security/verification_still_needed.html", + subject=VerificationEmail.subject) + + + if valid: + if params.get('remember'): + print("I will remember you") + self.remember_me = True + + return self.actual_login(user) + + else: + if user: self.unsuccessful_login(user) - flash("Invalid email-address or password. Please try again.", "alert-error") - response = make_response(redirect(url_for('login'))) + flash("Invalid email-address or password. Please try again.", "alert-error") + response = make_response(redirect(url_for('login'))) - return response + return response def actual_login(self, user, assumed_by=None): """The meat of the logging in process""" |