diff --git a/wowfunding/orm/orm.py b/wowfunding/orm/orm.py index 8173b8f..a5a9700 100644 --- a/wowfunding/orm/orm.py +++ b/wowfunding/orm/orm.py @@ -83,7 +83,7 @@ class Proposal(base): funds_target = sa.Column(sa.Float, nullable=False) # the FFS progress (cached) - funds_progress = sa.Column(sa.Float, nullable=False) + funds_progress = sa.Column(sa.Float, nullable=False, default=0) # the FFS withdrawal amount (paid to the author) funds_withdrew = sa.Column(sa.Float, nullable=False, default=0) @@ -103,6 +103,7 @@ class Proposal(base): user = relationship("User", back_populates="proposals") payouts = relationship("Payout", back_populates="proposal") + comments = relationship("Comment", back_populates="proposal", lazy='select') def __init__(self, headline, content, category, user: User): if not headline or not content: @@ -114,8 +115,6 @@ class Proposal(base): raise Exception('wrong category') self.category = category - - @property def json(self): return { @@ -133,14 +132,30 @@ class Proposal(base): @classmethod def find_by_id(cls, pid: int): + from wowfunding.factory import db_session q = cls.query q = q.filter(Proposal.id == pid) result = q.first() if not result: return + # check if we have a valid addr_donation generated. if not, make one. if not result.addr_donation: Proposal.generate_donation_addr(result) + + q = db_session.query(Comment) + q = q.filter(Comment.proposal_id == result.id) + q = q.filter(Comment.replied_to == None) + comments = q.all() + + for c in comments: + q = db_session.query(Comment) + q = q.filter(Comment.proposal_id == result.id) + q = q.filter(Comment.replied_to == c.id) + _c = q.all() + setattr(c, 'comments', _c) + + setattr(result, '_comments', comments) return result @property @@ -274,14 +289,62 @@ class Comment(base): __tablename__ = "comments" id = sa.Column(sa.Integer, primary_key=True) + proposal_id = sa.Column(sa.Integer, sa.ForeignKey('proposals.id')) + proposal = relationship("Proposal", back_populates="comments") + user_id = sa.Column(sa.Integer, sa.ForeignKey('users.user_id'), nullable=False) user = relationship("User", back_populates="comments") + date_added = sa.Column(sa.TIMESTAMP, default=datetime.now) + message = sa.Column(sa.VARCHAR, nullable=False) + replied_to = sa.Column(sa.ForeignKey("comments.id")) + locked = sa.Column(sa.Boolean, default=False) + ix_comment_replied_to = sa.Index("ix_comment_replied_to", replied_to) + ix_comment_proposal_id = sa.Index("ix_comment_proposal_id", proposal_id) + + @staticmethod + def find_by_id(cid: int): + from wowfunding.factory import db_session + return db_session.query(Comment).filter(Comment.id == cid).first() + + @staticmethod + def remove(cid: int): + from wowfunding.factory import db_session + from flask.ext.login import current_user + if current_user.id != user_id and not current_user.admin: + raise Exception("no rights to remove this comment") + comment = Comment.get(cid=cid) + try: + comment.delete() + db_session.commit() + db_session.flush() + except: + db_session.rollback() + raise + + @staticmethod + def lock(cid: int): + from wowfunding.factory import db_session + from flask.ext.login import current_user + if not current_user.admin: + raise Exception("admin required") + comment = Comment.find_by_id(cid=cid) + if not comment: + raise Exception("comment by that id not found") + comment.locked = True + try: + db_session.commit() + db_session.flush() + return comment + except: + db_session.rollback() + raise + @classmethod - def add_comment(cls, user_id: int, message: str, message_id: int = None): + def add_comment(cls, pid: int, user_id: int, message: str, cid: int = None, message_id: int = None): from flask.ext.login import current_user from wowfunding.factory import db_session if not message: @@ -291,13 +354,21 @@ class Comment(base): raise Exception("no rights to add or modify this comment") if not message_id: - comment = Comment(user_id=self.id) + proposal = Proposal.find_by_id(pid=pid) + if not proposal: + raise Exception("no proposal by that id") + comment = Comment(user_id=user_id, proposal_id=proposal.id) + if cid: + parent = Comment.find_by_id(cid=cid) + if not parent: + raise Exception("cannot reply to a non-existent comment") + comment.replied_to = parent.id else: try: user = db_session.query(User).filter(User.id == user_id).first() if not user: raise Exception("no user by that id") - comment = next(c for c in self.comments if c.id == message_id) + comment = next(c for c in user.comments if c.id == message_id) if comment.locked and not current_user.admin: raise Exception("your comment has been locked/removed") except StopIteration: diff --git a/wowfunding/routes.py b/wowfunding/routes.py index 61f7cad..e5d9bb8 100644 --- a/wowfunding/routes.py +++ b/wowfunding/routes.py @@ -5,7 +5,7 @@ from flask_yoloapi import endpoint, parameter import settings from wowfunding.factory import app, db_session -from wowfunding.orm.orm import Proposal, User +from wowfunding.orm.orm import Proposal, User, Comment @app.route('/') @@ -25,6 +25,44 @@ def proposal_add(): return make_response(render_template(('proposal_edit.html'))) +@app.route('/proposal/comment', methods=['POST']) +@endpoint.api( + parameter('pid', type=int, required=True), + parameter('text', type=str, required=True), + parameter('cid', type=int, required=False) +) +def proposal_comment(pid, text, cid): + if current_user.is_anonymous: + flash('not logged in', 'error') + return redirect(url_for('proposal', pid=pid)) + if len(text) <= 3: + flash('comment too short', 'error') + return redirect(url_for('proposal', pid=pid)) + try: + Comment.add_comment(user_id=current_user.id, message=text, pid=pid, cid=cid) + except Exception as ex: + flash('Could not add comment: %s' % str(ex), 'error') + return redirect(url_for('proposal', pid=pid)) + + flash('Comment posted.') + return redirect(url_for('proposal', pid=pid)) + + +@app.route('/proposal//comment/') +def propsal_comment_reply(cid, pid): + from wowfunding.orm.orm import Comment + c = Comment.find_by_id(cid) + if not c or c.replied_to: + return redirect(url_for('proposal', pid=pid)) + p = Proposal.find_by_id(pid) + if not p: + return redirect(url_for('proposals')) + if c.proposal_id != p.id: + return redirect(url_for('proposals')) + + return make_response(render_template('comment_reply.html', c=c, pid=pid, cid=cid)) + + @app.route('/proposal/') def proposal(pid): p = Proposal.find_by_id(pid=pid) diff --git a/wowfunding/static/css/wow.css b/wowfunding/static/css/wow.css index a005abc..e57b053 100644 --- a/wowfunding/static/css/wow.css +++ b/wowfunding/static/css/wow.css @@ -351,4 +351,56 @@ nav .nav-link .active{ #point-wow-left { display: none; } +} + +textarea.comment{ + padding-left: 6px; + padding-right: 6px; + padding-bottom: 6px; + width: 100%; + max-width: 600px; +} + +.votearrow { + width: 10px; + height: 10px; + border: 0px; + margin: 3px 2px 6px; + margin-right: 10px; + margin-top: 7px; + background: url(/static/grayarrow.gif) no-repeat; +} + +span.username a{ + font-family: Verdana, Geneva, sans-serif; + font-size: 12pt; + color: #828282; +} + +.comment-container a.reply{ + margin-bottom:2px; + font-family: Verdana, Geneva, sans-serif; + font-size: 11pt; + text-decoration: underline; + color: black; +} + +span.date_posted a{ + color: #828282; + font-family: Verdana, Geneva, sans-serif; + font-size: 10pt; + margin-left:2px; +} + +.comment-container .comment-container{ + margin-top: 0.4rem !important; +} + +span.username a.author{ + color: #008926; + font-weight: bold; +} + +:target { + background: linear-gradient(90deg, #ff606008, #ffa93e2b); } \ No newline at end of file diff --git a/wowfunding/static/doge_head.png b/wowfunding/static/doge_head.png new file mode 100644 index 0000000..babdc64 Binary files /dev/null and b/wowfunding/static/doge_head.png differ diff --git a/wowfunding/static/grayarrow.gif b/wowfunding/static/grayarrow.gif new file mode 100644 index 0000000..888485f Binary files /dev/null and b/wowfunding/static/grayarrow.gif differ diff --git a/wowfunding/templates/comment_reply.html b/wowfunding/templates/comment_reply.html new file mode 100644 index 0000000..75901cb --- /dev/null +++ b/wowfunding/templates/comment_reply.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} +{% block content %} + + +
+ {% include 'messages.html' %} + +
+ + +
+
+ +
+
+
+ {{c.user.username}} + {{c.date_added.strftime('%Y-%m-%d %H:%M')}}
+ {{c.message}} +

+ {% if logged_in %} +
+ + + +

+
+ {% else %} + You need to be logged in to comment. + {% endif %} +
+
+
+
+
+
+ + + +{% endblock %} diff --git a/wowfunding/templates/comments.html b/wowfunding/templates/comments.html index a9d0da7..d7e2cec 100644 --- a/wowfunding/templates/comments.html +++ b/wowfunding/templates/comments.html @@ -1,66 +1,62 @@
-
Comments
+
Comments
- Comment functionality not made yet!
- Press F to pay respects. + {% if logged_in %} +
+ + +

+
+ {% else %} + You need to be logged in to comment. +
+ {% endif %} + +
+ + {% for c in proposal._comments %} + +
+
+
+ + + {{c.user.username}} + + + + + {{c.date_added.strftime('%Y-%m-%d %H:%M')}} + +
+ {{c.message}} +
+ reply + + {% for _c in c.comments %} + + {% endfor %} +
+
+ {% endfor %}
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/wowfunding/templates/messages.html b/wowfunding/templates/messages.html index 53ee556..b20058d 100644 --- a/wowfunding/templates/messages.html +++ b/wowfunding/templates/messages.html @@ -1,17 +1,15 @@ -{% with messages = get_flashed_messages() %} +{% with messages = get_flashed_messages(with_categories=True) %} {% if messages %}
{% for message in messages %} - {% if message.category != 'error' %} -
- {{ message }} -
- {% else %} -
- {{ message }} -
- {% endif %} +
+ + {{ message[1] }} +
{% endfor %}