Finishing up on comment functionality
This commit is contained in:
parent
863e91e3f2
commit
40b95ad57d
|
@ -103,6 +103,7 @@ class Proposal(base):
|
||||||
user = relationship("User", back_populates="proposals")
|
user = relationship("User", back_populates="proposals")
|
||||||
|
|
||||||
payouts = relationship("Payout", back_populates="proposal")
|
payouts = relationship("Payout", back_populates="proposal")
|
||||||
|
comments = relationship("Comment", back_populates="proposal", lazy='select')
|
||||||
|
|
||||||
def __init__(self, headline, content, category, user: User):
|
def __init__(self, headline, content, category, user: User):
|
||||||
if not headline or not content:
|
if not headline or not content:
|
||||||
|
@ -114,8 +115,6 @@ class Proposal(base):
|
||||||
raise Exception('wrong category')
|
raise Exception('wrong category')
|
||||||
self.category = category
|
self.category = category
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def json(self):
|
def json(self):
|
||||||
return {
|
return {
|
||||||
|
@ -133,14 +132,30 @@ class Proposal(base):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_by_id(cls, pid: int):
|
def find_by_id(cls, pid: int):
|
||||||
|
from wowfunding.factory import db_session
|
||||||
q = cls.query
|
q = cls.query
|
||||||
q = q.filter(Proposal.id == pid)
|
q = q.filter(Proposal.id == pid)
|
||||||
result = q.first()
|
result = q.first()
|
||||||
if not result:
|
if not result:
|
||||||
return
|
return
|
||||||
|
|
||||||
# check if we have a valid addr_donation generated. if not, make one.
|
# check if we have a valid addr_donation generated. if not, make one.
|
||||||
if not result.addr_donation:
|
if not result.addr_donation:
|
||||||
Proposal.generate_donation_addr(result)
|
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
|
return result
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -274,26 +289,34 @@ class Comment(base):
|
||||||
__tablename__ = "comments"
|
__tablename__ = "comments"
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
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_id = sa.Column(sa.Integer, sa.ForeignKey('users.user_id'), nullable=False)
|
||||||
user = relationship("User", back_populates="comments")
|
user = relationship("User", back_populates="comments")
|
||||||
|
|
||||||
|
date_added = sa.Column(sa.TIMESTAMP, default=datetime.now)
|
||||||
|
|
||||||
message = sa.Column(sa.VARCHAR, nullable=False)
|
message = sa.Column(sa.VARCHAR, nullable=False)
|
||||||
replied_to = sa.Column(sa.ForeignKey("comments.id"))
|
replied_to = sa.Column(sa.ForeignKey("comments.id"))
|
||||||
|
|
||||||
locked = sa.Column(sa.Boolean, default=False)
|
locked = sa.Column(sa.Boolean, default=False)
|
||||||
|
|
||||||
@staticmethod
|
ix_comment_replied_to = sa.Index("ix_comment_replied_to", replied_to)
|
||||||
def get(comment_id: int):
|
ix_comment_proposal_id = sa.Index("ix_comment_proposal_id", proposal_id)
|
||||||
from wowfunding.factory import db_session
|
|
||||||
return db_session.query(Comment).filter(Comment.id == comment_id).first()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remove(comment_id: int):
|
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 wowfunding.factory import db_session
|
||||||
from flask.ext.login import current_user
|
from flask.ext.login import current_user
|
||||||
if current_user.id != user_id and not current_user.admin:
|
if current_user.id != user_id and not current_user.admin:
|
||||||
raise Exception("no rights to remove this comment")
|
raise Exception("no rights to remove this comment")
|
||||||
comment = Comment.get(comment_id=comment_id)
|
comment = Comment.get(cid=cid)
|
||||||
try:
|
try:
|
||||||
comment.delete()
|
comment.delete()
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
@ -303,12 +326,12 @@ class Comment(base):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def lock(comment_id: int):
|
def lock(cid: int):
|
||||||
from wowfunding.factory import db_session
|
from wowfunding.factory import db_session
|
||||||
from flask.ext.login import current_user
|
from flask.ext.login import current_user
|
||||||
if not current_user.admin:
|
if not current_user.admin:
|
||||||
raise Exception("admin required")
|
raise Exception("admin required")
|
||||||
comment = Comment.get(comment_id=comment_id)
|
comment = Comment.find_by_id(cid=cid)
|
||||||
if not comment:
|
if not comment:
|
||||||
raise Exception("comment by that id not found")
|
raise Exception("comment by that id not found")
|
||||||
comment.locked = True
|
comment.locked = True
|
||||||
|
@ -321,7 +344,7 @@ class Comment(base):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_comment(cls, user_id: int, message: str, replied_to: int, 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 flask.ext.login import current_user
|
||||||
from wowfunding.factory import db_session
|
from wowfunding.factory import db_session
|
||||||
if not message:
|
if not message:
|
||||||
|
@ -331,9 +354,12 @@ class Comment(base):
|
||||||
raise Exception("no rights to add or modify this comment")
|
raise Exception("no rights to add or modify this comment")
|
||||||
|
|
||||||
if not message_id:
|
if not message_id:
|
||||||
comment = Comment(user_id=self.id)
|
proposal = Proposal.find_by_id(pid=pid)
|
||||||
if replied_to:
|
if not proposal:
|
||||||
parent = Comment.get(comment_id=comment_id)
|
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:
|
if not parent:
|
||||||
raise Exception("cannot reply to a non-existent comment")
|
raise Exception("cannot reply to a non-existent comment")
|
||||||
comment.replied_to = parent.id
|
comment.replied_to = parent.id
|
||||||
|
@ -342,7 +368,7 @@ class Comment(base):
|
||||||
user = db_session.query(User).filter(User.id == user_id).first()
|
user = db_session.query(User).filter(User.id == user_id).first()
|
||||||
if not user:
|
if not user:
|
||||||
raise Exception("no user by that id")
|
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:
|
if comment.locked and not current_user.admin:
|
||||||
raise Exception("your comment has been locked/removed")
|
raise Exception("your comment has been locked/removed")
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
|
|
|
@ -5,7 +5,7 @@ from flask_yoloapi import endpoint, parameter
|
||||||
|
|
||||||
import settings
|
import settings
|
||||||
from wowfunding.factory import app, db_session
|
from wowfunding.factory import app, db_session
|
||||||
from wowfunding.orm.orm import Proposal, User
|
from wowfunding.orm.orm import Proposal, User, Comment
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
@ -25,6 +25,44 @@ def proposal_add():
|
||||||
return make_response(render_template(('proposal_edit.html')))
|
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>')
|
@app.route('/proposal/<int:pid>')
|
||||||
def proposal(pid):
|
def proposal(pid):
|
||||||
p = Proposal.find_by_id(pid=pid)
|
p = Proposal.find_by_id(pid=pid)
|
||||||
|
|
|
@ -360,3 +360,47 @@ textarea.comment{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
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 |
|
@ -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 %}
|
|
@ -1,63 +1,61 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="card my-6" id="incoming_txs">
|
<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">
|
<div class="card-body">
|
||||||
{% if logged_in %}
|
{% if logged_in %}
|
||||||
<form method="post" action="comment">
|
<form method="post" action="comment">
|
||||||
<input type="hidden" name="proposal_id" value="{{proposal.id}}">
|
<input type="hidden" name="pid" value="{{proposal.id}}">
|
||||||
<textarea class="comment" name="text" rows="6" cols="60"></textarea>
|
<textarea class="comment" name="text" rows="6" cols="60"></textarea>
|
||||||
<br><br><input type="submit" value="add comment">
|
<br><br><input type="submit" value="add comment">
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
You need to be logged in to comment.<br>
|
You need to be logged in to comment.
|
||||||
Press <b>F</b> to pay respects.
|
<br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
{% for c in proposal._comments %}
|
||||||
<!-- Single Comment -->
|
<!-- Single Comment -->
|
||||||
<div class="media mb-4">
|
<div class="media mb-4 comment-container" id="comment-{{c.id}}">
|
||||||
<img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">
|
<div class="votearrow" title="upvote"></div>
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<h5 class="mt-0">Commenter Name</h5>
|
<span class="username">
|
||||||
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin. Cras
|
<a class="{% if c.user.username == proposal.user.username %}author{% endif %}" href="/user/{{ c.user.username }}">
|
||||||
purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi
|
{{c.user.username}}
|
||||||
vulputate fringilla. Donec lacinia congue felis in faucibus.
|
</a>
|
||||||
</div>
|
</span>
|
||||||
</div>
|
<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>
|
||||||
|
|
||||||
<!-- Comment with nested comments -->
|
{% for _c in c.comments %}
|
||||||
<div class="media mb-4">
|
<div class="media mt-4 comment-container" id="comment-{{_c.id}}">
|
||||||
<img class="d-flex mr-3 rounded-circle" src="http://placehold.it/50x50" alt="">
|
<div class="votearrow" title="upvote"></div>
|
||||||
<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">
|
<div class="media-body">
|
||||||
<h5 class="mt-0">Commenter Name</h5>
|
<span class="username">
|
||||||
Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin.
|
<a class="{% if c.user.username == proposal.user.username %}author{% endif %}" href="/user/{{ c.user.username }}">
|
||||||
Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc
|
{{_c.user.username}}
|
||||||
ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
<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>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
{% with messages = get_flashed_messages() %}
|
{% with messages = get_flashed_messages(with_categories=True) %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
{% if message.category != 'error' %}
|
<div class="alert {% if message[0] != 'error' %}alert-success{% else %}alert-danger{% endif %}">
|
||||||
<div class="alert alert-success">
|
<img src="/static/doge_head.png" style="
|
||||||
{{ message }}
|
width: 64px;
|
||||||
</div>
|
margin-right: 8px;
|
||||||
{% else %}
|
">
|
||||||
<div class="alert alert-danger">
|
{{ message[1] }}
|
||||||
{{ message }}
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue