From a430646cc55c3e30c28a3e16676a8d20dda433c7 Mon Sep 17 00:00:00 2001 From: "B. Arman Aksoy" Date: Wed, 2 Sep 2015 14:56:29 -0400 Subject: [PATCH 1/8] associate comments with user ids --- ...1e3054_start_associating_comments_with_.py | 28 +++++++ cycledash/api/comments.py | 14 ++-- .../static/js/comments/components/comment.js | 5 +- .../js/examine/components/CommentBox.js | 77 +++++-------------- package.json | 1 - schema.py | 2 +- 6 files changed, 58 insertions(+), 69 deletions(-) create mode 100644 alembic/versions/1ab5aa1e3054_start_associating_comments_with_.py diff --git a/alembic/versions/1ab5aa1e3054_start_associating_comments_with_.py b/alembic/versions/1ab5aa1e3054_start_associating_comments_with_.py new file mode 100644 index 0000000..fde6462 --- /dev/null +++ b/alembic/versions/1ab5aa1e3054_start_associating_comments_with_.py @@ -0,0 +1,28 @@ +"""Start associating comments with registered users and use user information +as a display name instead of arbitrary author names. + +Revision ID: 1ab5aa1e3054 +Revises: 2c00861f3ff1 +Create Date: 2015-09-01 18:05:35.598253 + +""" + +# revision identifiers, used by Alembic. +revision = '1ab5aa1e3054' +down_revision = '2c00861f3ff1' +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa + +def upgrade(): + op.drop_column('user_comments', 'author_name') + op.add_column('user_comments', + sa.Column('user_id', + sa.BigInteger, + sa.ForeignKey('users.id'))) + +def downgrade(): + op.drop_column('user_comments', 'user_id') + op.add_column('user_comments', sa.Column('author_name', sa.String()),) diff --git a/cycledash/api/comments.py b/cycledash/api/comments.py index addef63..f3eea9f 100644 --- a/cycledash/api/comments.py +++ b/cycledash/api/comments.py @@ -2,6 +2,7 @@ from collections import defaultdict from flask import jsonify, request from flask.ext.restful import abort, fields +from flask.ext.login import current_user from sqlalchemy import select, func, desc from voluptuous import Any, Required, Coerce, Schema @@ -20,8 +21,7 @@ Required("position"): Coerce(int), Required("reference"): basestring, Required("alternates"): basestring, - Required("comment_text"): basestring, - "author_name": basestring, + Required("comment_text"): basestring }) DeleteComment = Schema({ @@ -30,8 +30,7 @@ UpdateComment = Schema({ Required('last_modified'): Coerce(float), - "comment_text": basestring, - "author_name": basestring, + "comment_text": basestring }) CommentFields = Schema({ @@ -52,8 +51,8 @@ basestring, Doc('comment_text', 'The text of the comment.'): Any(basestring, None), - Doc('author_name', 'The name of the author of this comment.'): - Any(basestring, None), + Doc('user_id', 'The ID of the User this comment is associated with.'): + Any(long, None), Doc('created', 'The time at which the comment was created (in epoch time).'): Coerce(to_epoch), @@ -77,9 +76,12 @@ def get(self, run_id): @marshal_with(CommentFields) def post(self, run_id): """Create a comment.""" + print current_user + with tables(db.engine, 'user_comments') as (con, comments): q = comments.insert().values( vcf_id=run_id, + user_id=current_user['id'], **request.validated_body ).returning(*comments.c) return dict(q.execute().fetchone()), 201 diff --git a/cycledash/static/js/comments/components/comment.js b/cycledash/static/js/comments/components/comment.js index c08981f..8785ef4 100644 --- a/cycledash/static/js/comments/components/comment.js +++ b/cycledash/static/js/comments/components/comment.js @@ -59,8 +59,9 @@ var Comment = React.createClass({ // moment uses the local timezone by default (converting the // value, which starts as a UNIX timestamp, to that timezone) var relativeDate = moment.unix(comment.created).fromNow(); - var authorName = comment.authorName ? - comment.authorName.slice(0, 15) : 'Anonymous'; + var authorName = comment.userId + ? "User #" + comment.userId + : "Anonymous"; return (
  • {authorName}
    diff --git a/cycledash/static/js/examine/components/CommentBox.js b/cycledash/static/js/examine/components/CommentBox.js index 52faa4b..8c90628 100644 --- a/cycledash/static/js/examine/components/CommentBox.js +++ b/cycledash/static/js/examine/components/CommentBox.js @@ -11,10 +11,7 @@ var _ = require('underscore'), utils = require('../utils'), React = require('react/addons'), marked = require('marked'), - moment = require('moment'), - store = require('store'); - -// Currently used to write comment author names to local storage. + moment = require('moment'); /** * Use markdown for comments, and set appropriate flags to: @@ -41,7 +38,6 @@ var CommentBox = React.createClass({ handleSetComment: React.PropTypes.func.isRequired, handleDeleteComment: React.PropTypes.func.isRequired }, - LOCAL_STORAGE_AUTHOR_KEY: 'CYCLEDASH_AUTHORNAME', getHandleDelete: function(comment) { var handleDeleteComment = this.props.handleDeleteComment; var record = this.props.record; @@ -55,11 +51,9 @@ var CommentBox = React.createClass({ getHandleSaveForUpdate: function(comment) { var handleSetComment = this.props.handleSetComment; var record = this.props.record; - return function(commentText, authorName) { + return function(commentText) { var newComment = _.clone(comment); newComment.commentText = commentText; - newComment.authorName = authorName; - handleSetComment(newComment, record); }; }, @@ -67,7 +61,7 @@ var CommentBox = React.createClass({ var handleSetComment = this.props.handleSetComment; var getGranularUnixSeconds = this.getGranularUnixSeconds; var record = this.props.record; - return function(commentText, authorName) { + return function(commentText) { // Subtract the offset to get GMT (to match what's in the DB) var newComment = _.extend( _.pick(record, @@ -77,7 +71,6 @@ var CommentBox = React.createClass({ 'alternates', 'sample_name'), {'commentText': commentText, - 'authorName': authorName, // Note: this is a temporary date that does not get persisted // to the DB. Instead, the DB creates its own date, but this // timestamp is used for distinguishing between comments in @@ -90,15 +83,6 @@ var CommentBox = React.createClass({ // moment does not appear to provide this functionality. return momentObject.valueOf() / 1000.0; }, - getLocalAuthorName: function() { - return store.enabled ? store.get(this.LOCAL_STORAGE_AUTHOR_KEY, '') : ''; - }, - saveLocalAuthorName: function(authorName) { - if (store.enabled && - store.get(this.LOCAL_STORAGE_AUTHOR_KEY, '') !== authorName) { - store.set(this.LOCAL_STORAGE_AUTHOR_KEY, authorName); - } - }, render: function() { var comments = this.props.record.comments; var commentNodes = _.sortBy(comments, 'created').map( @@ -114,12 +98,11 @@ var CommentBox = React.createClass({ moment(comment.created))); return ; }); @@ -136,9 +119,7 @@ var CommentBox = React.createClass({ key={utils.getRowKey(this.props.record) + 'newcomment'} handleSave={this.getHandleSaveForCreate()} startInEditState={true} - cancelable={false} - saveLocalAuthorName={this.saveLocalAuthorName} - authorName={this.getLocalAuthorName()}/> + cancelable={false}/> ); @@ -156,10 +137,9 @@ var VCFComment = React.createClass({ handleSave: React.PropTypes.func.isRequired, startInEditState: React.PropTypes.bool.isRequired, cancelable: React.PropTypes.bool.isRequired, - saveLocalAuthorName:React.PropTypes.func.isRequired, // Optional arguments. - authorName: React.PropTypes.string, + userId: React.PropTypes.number, createdString: React.PropTypes.string, handleDelete: React.PropTypes.func, }, @@ -194,17 +174,10 @@ var VCFComment = React.createClass({ }, render: function() { var placeHolder = 'Enter your comment here'; - // Only use "Anonymous" in the viewer; the editor should just be - // blank in that case. - var authorNameOrAnonymous = this.props.authorName || 'Anonymous'; - var authorNameOrBlank = this.props.authorName || ''; - // handleDelete is optional, but not providing it requires the // edit view. var commentElement = (this.state.isEditing || !this.props.handleDelete) ? :
    - {this.props.authorName} + {authorName}
    @@ -289,15 +265,13 @@ var VCFCommentViewer = React.createClass({ }); /** - * VCFCommentEditor represents the active editing (or creating) of a + * VCFCommentEditor represents the active editing (or creating) of a * comment, and it has a separate state variable for updated text that * is not yet saved. */ var VCFCommentEditor = React.createClass({ propTypes: { commentText: React.PropTypes.string.isRequired, - authorName: React.PropTypes.string.isRequired, - saveLocalAuthorName: React.PropTypes.func.isRequired, placeHolder: React.PropTypes.string.isRequired, setCommentTextState: React.PropTypes.func.isRequired, setEditState: React.PropTypes.func.isRequired, @@ -307,18 +281,12 @@ var VCFCommentEditor = React.createClass({ }, handleSaveText: function() { // If non-blank text is entered that differs from what was originally - // in the editor (either text or author), save it. A new comment can + // in the editor, save it. A new comment can // never be blank, though. var newCommentText = this.state.newCommentText; - var newAuthorName = this.state.newAuthorName; if (newCommentText !== '') { - if ((newCommentText !== this.props.commentText) || - (newAuthorName !== '' && - newAuthorName !== this.props.authorName)) { - // Store the author name in local storage. - this.props.saveLocalAuthorName(newAuthorName); - - this.props.handleSave(newCommentText, newAuthorName); + if ((newCommentText !== this.props.commentText)) { + this.props.handleSave(newCommentText); this.props.setCommentTextState(newCommentText); this.props.setEditState(false); @@ -344,11 +312,7 @@ var VCFCommentEditor = React.createClass({ } }, getInitialState: function() { - return {newCommentText: this.props.commentText, - newAuthorName: this.props.authorName}; - }, - handleAuthorChange: function(event) { - this.setState({newAuthorName: event.target.value}); + return {newCommentText: this.props.commentText}; }, handleTextChange: function(event) { this.setState({newCommentText: event.target.value}); @@ -374,11 +338,6 @@ var VCFCommentEditor = React.createClass({ return (
    -