mirror of
https://git.wownero.com/wownero/wownero-funding-system.git
synced 2024-08-15 00:53:45 +00:00
commit
13d00be0e1
8 changed files with 271 additions and 76 deletions
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
BIN
wowfunding/static/doge_head.png
Normal file
BIN
wowfunding/static/doge_head.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
BIN
wowfunding/static/grayarrow.gif
Normal file
BIN
wowfunding/static/grayarrow.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 111 B |
40
wowfunding/templates/comment_reply.html
Normal file
40
wowfunding/templates/comment_reply.html
Normal 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 %}
|
|
@ -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>
|
||||
<!--<!– Comments Form –>-->
|
||||
<!--<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>-->
|
||||
|
||||
<!--<!– Single Comment –>-->
|
||||
<!--<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>-->
|
||||
|
||||
<!--<!– Comment with nested comments –>-->
|
||||
<!--<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>
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue