mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
show replies below posts
This commit is contained in:
parent
8a18573974
commit
fa53128118
14 changed files with 299 additions and 28 deletions
|
@ -1,6 +1,3 @@
|
|||
import markdown2
|
||||
import werkzeug.exceptions
|
||||
|
||||
from app import db
|
||||
from app.activitypub import bp
|
||||
from flask import request, Response, current_app, abort, jsonify, json
|
||||
|
@ -14,7 +11,8 @@ from app.models import User, Community, CommunityJoinRequest, CommunityMember, C
|
|||
from app.activitypub.util import public_key, users_total, active_half_year, active_month, local_posts, local_comments, \
|
||||
post_to_activity, find_actor_or_create, default_context, instance_blocked, find_reply_parent, find_liked_object
|
||||
from app.utils import gibberish, get_setting, is_image_url, allowlist_html, html_to_markdown, render_template, \
|
||||
domain_from_url
|
||||
domain_from_url, markdown_to_html
|
||||
import werkzeug.exceptions
|
||||
|
||||
INBOX = []
|
||||
|
||||
|
@ -134,7 +132,7 @@ def user_profile(actor):
|
|||
"content": user.about,
|
||||
"mediaType": "text/markdown"
|
||||
}
|
||||
actor_data['summary'] = allowlist_html(markdown2.markdown(user.about, safe_mode=True))
|
||||
actor_data['summary'] = markdown_to_html(user.about)
|
||||
resp = jsonify(actor_data)
|
||||
resp.content_type = 'application/activity+json'
|
||||
return resp
|
||||
|
@ -250,7 +248,7 @@ def shared_inbox():
|
|||
if 'source' in request_json['object']['object'] and \
|
||||
request_json['object']['object']['source']['mediaType'] == 'text/markdown':
|
||||
post.body = request_json['object']['object']['source']['content']
|
||||
post.body_html = allowlist_html(markdown2.markdown(post.body, safe_mode=True))
|
||||
post.body_html = markdown_to_html(post.body)
|
||||
elif 'content' in request_json['object']['object']:
|
||||
post.body_html = allowlist_html(request_json['object']['object']['content'])
|
||||
post.body = html_to_markdown(post.body_html)
|
||||
|
@ -293,7 +291,7 @@ def shared_inbox():
|
|||
request_json['object']['object']['source'][
|
||||
'mediaType'] == 'text/markdown':
|
||||
post_reply.body = request_json['object']['object']['source']['content']
|
||||
post_reply.body_html = allowlist_html(markdown2.markdown(post_reply.body, safe_mode=True))
|
||||
post_reply.body_html = markdown_to_html(post_reply.body)
|
||||
elif 'content' in request_json['object']['object']:
|
||||
post_reply.body_html = allowlist_html(
|
||||
request_json['object']['object']['content'])
|
||||
|
|
|
@ -2,7 +2,6 @@ import json
|
|||
import os
|
||||
from datetime import datetime
|
||||
from typing import Union, Tuple
|
||||
import markdown2
|
||||
from flask import current_app
|
||||
from sqlalchemy import text
|
||||
from app import db, cache
|
||||
|
@ -15,7 +14,7 @@ from cryptography.hazmat.primitives.asymmetric import padding
|
|||
from app.constants import *
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from app.utils import get_request, allowlist_html
|
||||
from app.utils import get_request, allowlist_html, html_to_markdown
|
||||
|
||||
|
||||
def public_key():
|
||||
|
@ -301,7 +300,7 @@ def parse_summary(user_json) -> str:
|
|||
if 'source' in user_json and user_json['source'].get('mediaType') == 'text/markdown':
|
||||
# Convert Markdown to HTML
|
||||
markdown_text = user_json['source']['content']
|
||||
html_content = markdown2.markdown(markdown_text)
|
||||
html_content = html_to_markdown(markdown_text)
|
||||
return html_content
|
||||
elif 'summary' in user_json:
|
||||
return allowlist_html(user_json['summary'])
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from datetime import date, datetime, timedelta
|
||||
|
||||
import markdown2
|
||||
from flask import redirect, url_for, flash, request, make_response, session, Markup, current_app, abort
|
||||
from flask_login import login_user, logout_user, current_user, login_required
|
||||
from flask_babel import _
|
||||
|
@ -9,11 +8,12 @@ from sqlalchemy import or_
|
|||
from app import db
|
||||
from app.activitypub.signature import RsaKeys, HttpSignature
|
||||
from app.community.forms import SearchRemoteCommunity, AddLocalCommunity, CreatePost, NewReplyForm
|
||||
from app.community.util import search_for_community, community_url_exists, actor_to_community
|
||||
from app.community.util import search_for_community, community_url_exists, actor_to_community, post_replies
|
||||
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE
|
||||
from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post
|
||||
from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, PostReply, \
|
||||
PostReplyVote
|
||||
from app.community import bp
|
||||
from app.utils import get_setting, render_template, allowlist_html
|
||||
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html
|
||||
|
||||
|
||||
@bp.route('/add_local', methods=['GET', 'POST'])
|
||||
|
@ -80,7 +80,7 @@ def show_community(community: Community):
|
|||
mod_list = User.query.filter(User.id.in_(mod_user_ids)).all()
|
||||
|
||||
if current_user.ignore_bots:
|
||||
posts = community.posts.query.filter(Post.from_bot == False).all()
|
||||
posts = community.posts.filter(Post.from_bot == False).all()
|
||||
else:
|
||||
posts = community.posts
|
||||
|
||||
|
@ -187,7 +187,7 @@ def add_post(actor):
|
|||
if form.type.data == '' or form.type.data == 'discussion':
|
||||
post.title = form.discussion_title.data
|
||||
post.body = form.discussion_body.data
|
||||
post.body_html = allowlist_html(markdown2.markdown(post.body, safe_mode=True))
|
||||
post.body_html = markdown_to_html(post.body)
|
||||
post.type = POST_TYPE_ARTICLE
|
||||
elif form.type.data == 'link':
|
||||
post.title = form.link_title.data
|
||||
|
@ -217,11 +217,28 @@ def add_post(actor):
|
|||
images_disabled=images_disabled)
|
||||
|
||||
|
||||
@bp.route('/post/<int:post_id>')
|
||||
@bp.route('/post/<int:post_id>', methods=['GET', 'POST'])
|
||||
def show_post(post_id: int):
|
||||
post = Post.query.get_or_404(post_id)
|
||||
mods = post.community.moderators()
|
||||
is_moderator = current_user.is_authenticated and any(mod.user_id == current_user.id for mod in mods)
|
||||
form = NewReplyForm()
|
||||
if form.validate_on_submit():
|
||||
reply = PostReply(user_id=current_user.id, post_id=post.id, community_id=post.community.id, body=form.body.data,
|
||||
body_html=markdown_to_html(form.body.data), body_html_safe=True,
|
||||
from_bot=current_user.bot, up_votes=1, nsfw=post.nsfw, nsfl=post.nsfl)
|
||||
db.session.add(reply)
|
||||
db.session.commit()
|
||||
reply_vote = PostReplyVote(user_id=current_user.id, author_id=current_user.id, post_reply_id=reply.id,
|
||||
effect=1.0)
|
||||
db.session.add(reply_vote)
|
||||
db.session.commit()
|
||||
form.body.data = ''
|
||||
flash('Your comment has been added.')
|
||||
# todo: flush cache
|
||||
# todo: federation
|
||||
replies = post_replies(post.id, 'top', show_first=reply.id)
|
||||
else:
|
||||
replies = post_replies(post.id, 'top')
|
||||
return render_template('community/post.html', title=post.title, post=post, is_moderator=is_moderator,
|
||||
canonical=post.ap_id, form=form)
|
||||
canonical=post.ap_id, form=form, replies=replies)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from app import db
|
||||
from app.models import Community, File, BannedInstances
|
||||
from app.models import Community, File, BannedInstances, PostReply
|
||||
from app.utils import get_request
|
||||
from sqlalchemy import desc
|
||||
|
||||
|
||||
def search_for_community(address: str):
|
||||
|
@ -78,3 +80,24 @@ def actor_to_community(actor) -> Community:
|
|||
else:
|
||||
community = Community.query.filter_by(name=actor, banned=False, ap_id=None).first()
|
||||
return community
|
||||
|
||||
|
||||
# replies to a post, in a tree, sorted by a variety of methods
|
||||
def post_replies(post_id: int, sort_by: str, show_first: int = 0) -> List[PostReply]:
|
||||
comments = PostReply.query.filter_by(post_id=post_id)
|
||||
if sort_by == 'hot':
|
||||
comments = comments.order_by(desc(PostReply.ranking))
|
||||
elif sort_by == 'top':
|
||||
comments = comments.order_by(desc(PostReply.score))
|
||||
elif sort_by == 'new':
|
||||
comments = comments.order_by(desc(PostReply.posted_at))
|
||||
|
||||
comments_dict = {comment.id: {'comment': comment, 'replies': []} for comment in comments.all()}
|
||||
|
||||
for comment in comments:
|
||||
if comment.parent_id is not None:
|
||||
parent_comment = comments_dict.get(comment.parent_id)
|
||||
if parent_comment:
|
||||
parent_comment['replies'].append(comments_dict[comment.id])
|
||||
|
||||
return [comment for comment in comments_dict.values() if comment['comment'].parent_id is None]
|
||||
|
|
|
@ -198,6 +198,12 @@ class User(UserMixin, db.Model):
|
|||
return self.cover.source_url
|
||||
return ''
|
||||
|
||||
def link(self) -> str:
|
||||
if self.ap_id is None:
|
||||
return self.user_name
|
||||
else:
|
||||
return self.ap_id
|
||||
|
||||
def get_reset_password_token(self, expires_in=600):
|
||||
return jwt.encode(
|
||||
{'reset_password': self.id, 'exp': time() + expires_in},
|
||||
|
@ -312,9 +318,11 @@ class PostReply(db.Model):
|
|||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
|
||||
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), index=True)
|
||||
community_id = db.Column(db.Integer, db.ForeignKey('community.id'), index=True)
|
||||
domain_id = db.Column(db.Integer, db.ForeignKey('domain.id'), index=True)
|
||||
image_id = db.Column(db.Integer, db.ForeignKey('file.id'), index=True)
|
||||
parent_id = db.Column(db.Integer)
|
||||
root_id = db.Column(db.Integer)
|
||||
depth = db.Column(db.Integer, default=0)
|
||||
body = db.Column(db.Text)
|
||||
body_html = db.Column(db.Text)
|
||||
body_html_safe = db.Column(db.Boolean, default=False)
|
||||
|
@ -327,7 +335,7 @@ class PostReply(db.Model):
|
|||
from_bot = db.Column(db.Boolean, default=False)
|
||||
up_votes = db.Column(db.Integer, default=0)
|
||||
down_votes = db.Column(db.Integer, default=0)
|
||||
ranking = db.Column(db.Integer, default=0)
|
||||
ranking = db.Column(db.Integer, default=0, index=True)
|
||||
language = db.Column(db.String(10))
|
||||
edited_at = db.Column(db.DateTime)
|
||||
|
||||
|
|
|
@ -137,6 +137,14 @@
|
|||
content: "\e9d7";
|
||||
}
|
||||
|
||||
.fe-arrow-up::before {
|
||||
content: "\e914";
|
||||
}
|
||||
|
||||
.fe-arrow-down::before {
|
||||
content: "\e90c";
|
||||
}
|
||||
|
||||
a.no-underline {
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
|
|
|
@ -136,6 +136,14 @@ nav, etc which are used site-wide */
|
|||
content: "\e9d7";
|
||||
}
|
||||
|
||||
.fe-arrow-up::before {
|
||||
content: "\e914";
|
||||
}
|
||||
|
||||
.fe-arrow-down::before {
|
||||
content: "\e90c";
|
||||
}
|
||||
|
||||
a.no-underline {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -316,6 +324,10 @@ fieldset legend {
|
|||
height: auto;
|
||||
}
|
||||
|
||||
.post_reply_form label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post_list .post_teaser {
|
||||
border-bottom: solid 2px #ddd;
|
||||
padding-top: 8px;
|
||||
|
@ -329,4 +341,50 @@ fieldset legend {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.comment {
|
||||
clear: both;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.comment .comment_author img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.comment .hide_button {
|
||||
float: right;
|
||||
display: block;
|
||||
width: 60px;
|
||||
padding: 5px;
|
||||
}
|
||||
.comment .hide_button a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.comment .voting_buttons {
|
||||
float: right;
|
||||
display: block;
|
||||
width: 60px;
|
||||
padding: 5px;
|
||||
}
|
||||
.comment .voting_buttons div {
|
||||
border: solid 1px #0071CE;
|
||||
}
|
||||
.comment .voting_buttons .upvote_button, .comment .voting_buttons .downvote_button {
|
||||
padding-left: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.comment .voting_buttons .upvote_button.digits_4, .comment .voting_buttons .downvote_button.digits_4 {
|
||||
width: 68px;
|
||||
}
|
||||
.comment .voting_buttons .upvote_button.digits_5, .comment .voting_buttons .downvote_button.digits_5 {
|
||||
width: 76px;
|
||||
}
|
||||
.comment .voting_buttons .upvote_button.digits_6, .comment .voting_buttons .downvote_button.digits_6 {
|
||||
width: 84px;
|
||||
}
|
||||
.comment .voting_buttons .downvote_button {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.comment .voting_buttons a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=structure.css.map */
|
||||
|
|
|
@ -111,6 +111,11 @@ nav, etc which are used site-wide */
|
|||
}
|
||||
}
|
||||
|
||||
.post_reply_form {
|
||||
label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.post_list {
|
||||
.post_teaser {
|
||||
|
||||
|
@ -129,4 +134,63 @@ nav, etc which are used site-wide */
|
|||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.comment {
|
||||
clear: both;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.comment_author {
|
||||
img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.hide_button {
|
||||
float: right;
|
||||
display: block;
|
||||
width: 60px;
|
||||
padding: 5px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.voting_buttons {
|
||||
float: right;
|
||||
display: block;
|
||||
width: 60px;
|
||||
padding: 5px;
|
||||
|
||||
div {
|
||||
border: solid 1px $primary-colour;
|
||||
}
|
||||
|
||||
.upvote_button, .downvote_button {
|
||||
padding-left: 3px;
|
||||
border-radius: 3px;
|
||||
|
||||
&.digits_4 {
|
||||
width: 68px;
|
||||
}
|
||||
|
||||
&.digits_5 {
|
||||
width: 76px;
|
||||
}
|
||||
|
||||
&.digits_6 {
|
||||
width: 84px;
|
||||
}
|
||||
}
|
||||
|
||||
.downvote_button {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -135,6 +135,14 @@
|
|||
content: "\e9d7";
|
||||
}
|
||||
|
||||
.fe-arrow-up::before {
|
||||
content: "\e914";
|
||||
}
|
||||
|
||||
.fe-arrow-down::before {
|
||||
content: "\e90c";
|
||||
}
|
||||
|
||||
a.no-underline {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
@ -79,7 +79,42 @@
|
|||
{% endif %}
|
||||
<div class="row post_replies">
|
||||
<div class="col">
|
||||
{% macro render_comment(comment) %}
|
||||
<div class="comment" style="margin-left: {{ comment['comment'].depth * 20 }}px;">
|
||||
<div class="voting_buttons">
|
||||
<div class="upvote_button digits_{{ digits(comment['comment'].up_votes) }}"><a href="#"><span class="fe fe-arrow-up"></span>
|
||||
{{ comment['comment'].up_votes }}</a>
|
||||
</div>
|
||||
<div class="downvote_button digits_{{ digits(comment['comment'].down_votes) }}"><a href="#"><span class="fe fe-arrow-down"></span>
|
||||
{{ comment['comment'].down_votes }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hide_button"><a href="#">[-] hide</a></div>
|
||||
<div class="comment_author">
|
||||
{% if comment['comment'].author.avatar_id %}
|
||||
<a href="/u/{{ comment['comment'].author.link() }}" title="{{ comment['comment'].author.ap_id }}">
|
||||
<img src="{{ comment['comment'].author.avatar_image() }}" alt="Avatar" /></a>
|
||||
{% endif %}
|
||||
<a href="/u/{{ comment['comment'].author.link() }}" title="{{ comment['comment'].author.link() }}">
|
||||
<strong>{{ comment['comment'].author.user_name}}</strong></a>
|
||||
<span class="text-muted small">{{ moment(comment['comment'].posted_at).fromNow(refresh=True) }}</span>
|
||||
</div>
|
||||
{{ comment['comment'].body_html | safe }}
|
||||
</div>
|
||||
{% if comment['replies'] %}
|
||||
<div class="replies">
|
||||
{% for reply in comment['replies'] %}
|
||||
{{ render_comment(reply) | safe }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
<div class="comments">
|
||||
{% for reply in replies %}
|
||||
{{ render_comment(reply) | safe }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import markdown2
|
||||
|
||||
from flask import redirect, url_for, flash, request, make_response, session, Markup, current_app, abort
|
||||
from flask_login import login_user, logout_user, current_user, login_required
|
||||
from flask_babel import _
|
||||
|
@ -8,7 +6,7 @@ from app import db
|
|||
from app.models import Post, Community, CommunityMember, User, PostReply
|
||||
from app.user import bp
|
||||
from app.user.forms import ProfileForm, SettingsForm
|
||||
from app.utils import get_setting, render_template, allowlist_html
|
||||
from app.utils import get_setting, render_template, markdown_to_html
|
||||
from sqlalchemy import desc, or_
|
||||
|
||||
|
||||
|
@ -19,7 +17,7 @@ def show_profile(user):
|
|||
moderates = moderates.filter(Community.private_mods == False)
|
||||
post_replies = PostReply.query.filter_by(user_id=user.id).order_by(desc(PostReply.posted_at)).all()
|
||||
canonical = user.ap_public_url if user.ap_public_url else None
|
||||
user.about_html = allowlist_html(markdown2.markdown(user.about, safe_mode=True))
|
||||
user.about_html = markdown_to_html(user.about)
|
||||
return render_template('user/show_profile.html', user=user, posts=posts, post_replies=post_replies,
|
||||
moderates=moderates.all(), canonical=canonical, title=_('Posts by %(user_name)s',
|
||||
user_name=user.user_name))
|
||||
|
|
20
app/utils.py
20
app/utils.py
|
@ -1,9 +1,10 @@
|
|||
import random
|
||||
import markdown2
|
||||
import math
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import flask
|
||||
from bs4 import BeautifulSoup
|
||||
import html as html_module
|
||||
import requests
|
||||
import os
|
||||
from flask import current_app, json
|
||||
|
@ -84,7 +85,8 @@ def is_image_url(url):
|
|||
|
||||
# sanitise HTML using an allow list
|
||||
def allowlist_html(html: str) -> str:
|
||||
allowed_tags = ['p', 'strong', 'a', 'ul', 'ol', 'li', 'em', 'blockquote', 'cite', 'br', 'h3', 'h4', 'h5']
|
||||
allowed_tags = ['p', 'strong', 'a', 'ul', 'ol', 'li', 'em', 'blockquote', 'cite', 'br', 'h3', 'h4', 'h5', 'pre',
|
||||
'code']
|
||||
# Parse the HTML using BeautifulSoup
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
|
||||
|
@ -100,7 +102,7 @@ def allowlist_html(html: str) -> str:
|
|||
del tag[attr]
|
||||
|
||||
# Encode the HTML to prevent script execution
|
||||
return html_module.escape(str(soup))
|
||||
return str(soup)
|
||||
|
||||
|
||||
# convert basic HTML to Markdown
|
||||
|
@ -138,6 +140,10 @@ def html_to_markdown_worker(element, indent_level=0):
|
|||
return formatted_text
|
||||
|
||||
|
||||
def markdown_to_html(markdown_text) -> str:
|
||||
return allowlist_html(markdown2.markdown(markdown_text, safe_mode=True))
|
||||
|
||||
|
||||
def domain_from_url(url: str) -> Domain:
|
||||
parsed_url = urlparse(url)
|
||||
domain = Domain.query.filter_by(name=parsed_url.hostname.lower()).first()
|
||||
|
@ -153,3 +159,11 @@ def shorten_string(input_str, max_length=50):
|
|||
|
||||
def shorten_url(input: str, max_length=20):
|
||||
return shorten_string(input.replace('https://', '').replace('http://', ''))
|
||||
|
||||
|
||||
# the number of digits in a number. e.g. 1000 would be 4
|
||||
def digits(input: int) -> int:
|
||||
if input == 0:
|
||||
return 1 # Special case: 0 has 1 digit
|
||||
else:
|
||||
return math.floor(math.log10(abs(input))) + 1
|
||||
|
|
40
migrations/versions/e82f86c550ac_reply_depth.py
Normal file
40
migrations/versions/e82f86c550ac_reply_depth.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
"""reply depth
|
||||
|
||||
Revision ID: e82f86c550ac
|
||||
Revises: 8c5cc19e0670
|
||||
Create Date: 2023-10-10 20:51:09.662080
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e82f86c550ac'
|
||||
down_revision = '8c5cc19e0670'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('post_reply', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('domain_id', sa.Integer(), nullable=True))
|
||||
batch_op.add_column(sa.Column('depth', sa.Integer(), nullable=True))
|
||||
batch_op.create_index(batch_op.f('ix_post_reply_domain_id'), ['domain_id'], unique=False)
|
||||
batch_op.create_index(batch_op.f('ix_post_reply_ranking'), ['ranking'], unique=False)
|
||||
batch_op.create_foreign_key(None, 'domain', ['domain_id'], ['id'])
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('post_reply', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='foreignkey')
|
||||
batch_op.drop_index(batch_op.f('ix_post_reply_ranking'))
|
||||
batch_op.drop_index(batch_op.f('ix_post_reply_domain_id'))
|
||||
batch_op.drop_column('depth')
|
||||
batch_op.drop_column('domain_id')
|
||||
|
||||
# ### end Alembic commands ###
|
|
@ -6,7 +6,7 @@ from app import create_app, db, cli
|
|||
import os, click
|
||||
from flask import session, g
|
||||
from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_ARTICLE
|
||||
from app.utils import getmtime, gibberish, shorten_string, shorten_url
|
||||
from app.utils import getmtime, gibberish, shorten_string, shorten_url, digits
|
||||
|
||||
app = create_app()
|
||||
cli.register(app)
|
||||
|
@ -27,6 +27,7 @@ def make_shell_context():
|
|||
with app.app_context():
|
||||
app.jinja_env.globals['getmtime'] = getmtime
|
||||
app.jinja_env.globals['len'] = len
|
||||
app.jinja_env.globals['digits'] = digits
|
||||
app.jinja_env.globals['str'] = str
|
||||
app.jinja_env.filters['shorten'] = shorten_string
|
||||
app.jinja_env.filters['shorten_url'] = shorten_url
|
||||
|
|
Loading…
Reference in a new issue