aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--wqflask/wqflask/collect.py103
-rw-r--r--wqflask/wqflask/model.py44
-rw-r--r--wqflask/wqflask/static/new/javascript/search_results.coffee61
-rw-r--r--wqflask/wqflask/static/new/javascript/search_results.js76
-rw-r--r--wqflask/wqflask/templates/base.html8
-rw-r--r--wqflask/wqflask/templates/collections/add.html10
-rw-r--r--wqflask/wqflask/templates/collections/add_anonymous.html22
-rw-r--r--wqflask/wqflask/templates/collections/view.html13
-rw-r--r--wqflask/wqflask/user_manager.py74
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"""