Merge pull request #1 from skftn/comments

Comments
This commit is contained in:
Sander Ferdinand 2018-07-02 23:22:25 +02:00 committed by GitHub
commit 13d00be0e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 271 additions and 76 deletions

View file

@ -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:

View file

@ -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/<int:pid>/comment/<int:cid>')
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/<int:pid>')
def proposal(pid):
p = Proposal.find_by_id(pid=pid)

View file

@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

View file

@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block content %}
<!-- Page Content -->
<div class="container" style="margin-bottom:140px;">
{% include 'messages.html' %}
<div class="row">
<!-- Post Content Column -->
<div class="col-lg-12">
<div class="proposal_content">
<!-- Post Content -->
<div class="media mb-4">
<div class="votearrow" title="upvote"></div>
<div class="media-body">
<span class="username"><a href="/user/{{ c.user.username }}">{{c.user.username}}</a></span>
<span class="date_posted">{{c.date_added.strftime('%Y-%m-%d %H:%M')}}</span><br>
{{c.message}}
<br><br>
{% if logged_in %}
<form method="post" action="{{url_for('proposal_comment')}}">
<input type="hidden" name="pid" value="{{pid}}">
<input type="hidden" name="cid" value="{{cid}}">
<textarea class="comment" name="text" rows="6" cols="60"></textarea>
<br><br><input type="submit" value="add comment">
</form>
{% else %}
You need to be logged in to comment.
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /.container -->
{% endblock %}

View file

@ -1,66 +1,62 @@
<div class="row">
<div class="col-md-12">
<div class="card my-6" id="incoming_txs">
<h5 class="card-header">Comments</h5>
<h5 id="comments" class="card-header">Comments</h5>
<div class="card-body">
Comment functionality not made yet!<br>
Press <b>F</b> to pay respects.
{% if logged_in %}
<form method="post" action="comment">
<input type="hidden" name="pid" value="{{proposal.id}}">
<textarea class="comment" name="text" rows="6" cols="60"></textarea>
<br><br><input type="submit" value="add comment">
</form>
{% else %}
You need to be logged in to comment.
<br>
{% endif %}
<br>
{% for c in proposal._comments %}
<!-- Single Comment -->
<div class="media mb-4 comment-container" id="comment-{{c.id}}">
<div class="votearrow" title="upvote"></div>
<div class="media-body">
<span class="username">
<a class="{% if c.user.username == proposal.user.username %}author{% endif %}" href="/user/{{ c.user.username }}">
{{c.user.username}}
</a>
</span>
<span class="date_posted">
<a href="/proposal/{{proposal.id}}#comment-{{c.id}}">
{{c.date_added.strftime('%Y-%m-%d %H:%M')}}
</a>
</span><br>
{{c.message}}
<br>
<a class="reply" href="{{url_for('propsal_comment_reply', cid=c.id, pid=proposal.id)}}">reply</a>
{% for _c in c.comments %}
<div class="media mt-4 comment-container" id="comment-{{_c.id}}">
<div class="votearrow" title="upvote"></div>
<div class="media-body">
<span class="username">
<a class="{% if c.user.username == proposal.user.username %}author{% endif %}" href="/user/{{ c.user.username }}">
{{_c.user.username}}
</a>
</span>
<span class="date_posted">
<a href="/proposal/{{proposal.id}}#comment-{{_c.id}}">
{{c.date_added.strftime('%Y-%m-%d %H:%M')}}
</a>
</span><br>
{{_c.message}}
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<!--&lt;!&ndash; Comments Form &ndash;&gt;-->
<!--<div class="card my-4">-->
<!--<h5 class="card-header">Leave a Comment:</h5>-->
<!--<div class="card-body">-->
<!--<form>-->
<!--<div class="form-group">-->
<!--<textarea class="form-control" rows="3"></textarea>-->
<!--</div>-->
<!--<button type="submit" class="btn btn-primary">Submit</button>-->
<!--</form>-->
<!--</div>-->
<!--</div>-->
<!--&lt;!&ndash; Single Comment &ndash;&gt;-->
<!--<div class="media mb-4">-->
<!--<img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">-->
<!--<div class="media-body">-->
<!--<h5 class="mt-0">Commenter Name</h5>-->
<!--Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras-->
<!--purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi-->
<!--vulputate fringilla. Donec lacinia congue felis in faucibus.-->
<!--</div>-->
<!--</div>-->
<!--&lt;!&ndash; Comment with nested comments &ndash;&gt;-->
<!--<div class="media mb-4">-->
<!--<img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">-->
<!--<div class="media-body">-->
<!--<h5 class="mt-0">Commenter Name</h5>-->
<!--Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras-->
<!--purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi-->
<!--vulputate fringilla. Donec lacinia congue felis in faucibus.-->
<!--<div class="media mt-4">-->
<!--<img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">-->
<!--<div class="media-body">-->
<!--<h5 class="mt-0">Commenter Name</h5>-->
<!--Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin.-->
<!--Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc-->
<!--ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.-->
<!--</div>-->
<!--</div>-->
<!--<div class="media mt-4">-->
<!--<img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">-->
<!--<div class="media-body">-->
<!--<h5 class="mt-0">Commenter Name</h5>-->
<!--Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin.-->
<!--Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc-->
<!--ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.-->
<!--</div>-->
<!--</div>-->
<!--</div>-->
<!--</div>-->
</div>

View file

@ -1,17 +1,15 @@
{% with messages = get_flashed_messages() %}
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
<div class="row">
<div class="col-lg-12">
{% for message in messages %}
{% if message.category != 'error' %}
<div class="alert alert-success">
{{ message }}
</div>
{% else %}
<div class="alert alert-danger">
{{ message }}
</div>
{% endif %}
<div class="alert {% if message[0] != 'error' %}alert-success{% else %}alert-danger{% endif %}">
<img src="/static/doge_head.png" style="
width: 64px;
margin-right: 8px;
">
{{ message[1] }}
</div>
{% endfor %}
</div>
</div>