From 2c3f1763b362b0d9997f2a2da32cf02c0d29beae Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Wed, 17 Jul 2024 22:11:31 +0800 Subject: [PATCH] community wiki #127 --- app/community/forms.py | 13 ++ app/community/routes.py | 184 +++++++++++++++++- app/models.py | 55 +++++- app/post/routes.py | 12 +- .../community/_community_moderation_nav.html | 3 + .../community/community_wiki_edit.html | 18 ++ .../community/community_wiki_list.html | 67 +++++++ .../community/community_wiki_page_view.html | 33 ++++ migrations/versions/b97584a7a10b_wikis.py | 71 +++++++ pyfedi.py | 5 +- 10 files changed, 448 insertions(+), 13 deletions(-) create mode 100644 app/templates/community/community_wiki_edit.html create mode 100644 app/templates/community/community_wiki_list.html create mode 100644 app/templates/community/community_wiki_page_view.html create mode 100644 migrations/versions/b97584a7a10b_wikis.py diff --git a/app/community/forms.py b/app/community/forms.py index f4298838..e963bd13 100644 --- a/app/community/forms.py +++ b/app/community/forms.py @@ -63,6 +63,19 @@ class EditCommunityForm(FlaskForm): submit = SubmitField(_l('Save')) +class EditCommunityWikiPageForm(FlaskForm): + title = StringField(_l('Title'), validators=[DataRequired()]) + slug = StringField(_l('Slug'), validators=[DataRequired()]) + body = TextAreaField(_l('Body'), render_kw={'rows': '10'}) + edit_options = [(0, _l('Mods and admins')), + (1, _l('Trusted accounts')), + (2, _l('Community members')), + (3, _l('Any account')) + ] + who_can_edit = SelectField(_l('Who can edit'), coerce=int, choices=edit_options, validators=[Optional()], render_kw={'class': 'form-select'}) + submit = SubmitField(_l('Save')) + + class AddModeratorForm(FlaskForm): user_name = StringField(_l('User name'), validators=[DataRequired()]) submit = SubmitField(_l('Find')) diff --git a/app/community/routes.py b/app/community/routes.py index 1b1c8610..c0ad7fea 100644 --- a/app/community/routes.py +++ b/app/community/routes.py @@ -15,7 +15,8 @@ from app.chat.util import send_message from app.community.forms import SearchRemoteCommunity, CreateDiscussionForm, CreateImageForm, CreateLinkForm, \ ReportCommunityForm, \ DeleteCommunityForm, AddCommunityForm, EditCommunityForm, AddModeratorForm, BanUserCommunityForm, \ - EscalateReportForm, ResolveReportForm, CreateVideoForm, CreatePollForm, RetrieveRemotePost + EscalateReportForm, ResolveReportForm, CreateVideoForm, CreatePollForm, RetrieveRemotePost, \ + EditCommunityWikiPageForm from app.community.util import search_for_community, actor_to_community, \ save_post, save_icon_file, save_banner_file, send_to_remote_instance, \ delete_post_from_community, delete_post_reply_from_community, community_in_list, find_local_users @@ -25,7 +26,8 @@ from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LIN from app.inoculation import inoculation from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, \ File, PostVote, utcnow, Report, Notification, InstanceBlock, ActivityPubLog, Topic, Conversation, PostReply, \ - NotificationSubscription, UserFollower, Instance, Language, Poll, PollChoice, ModLog + NotificationSubscription, UserFollower, Instance, Language, Poll, PollChoice, ModLog, CommunityWikiPage, \ + CommunityWikiPageRevision from app.community import bp from app.user.utils import search_for_user from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \ @@ -1326,6 +1328,184 @@ def community_moderate_subscribers(actor): abort(404) +@bp.route('//moderate/wiki', methods=['GET']) +@login_required +def community_wiki_list(actor): + community = actor_to_community(actor) + + if community is not None: + if community.is_moderator() or current_user.is_admin(): + low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1' + pages = CommunityWikiPage.query.filter(CommunityWikiPage.community_id == community.id).order_by(CommunityWikiPage.title).all() + return render_template('community/community_wiki_list.html', title=_('Community Wiki'), community=community, + pages=pages, low_bandwidth=low_bandwidth, current='wiki', + moderating_communities=moderating_communities(current_user.get_id()), + joined_communities=joined_communities(current_user.get_id()), + menu_topics=menu_topics(), site=g.site, + inoculation=inoculation[randint(0, len(inoculation) - 1)] if g.site.show_inoculation_block else None + ) + else: + abort(401) + else: + abort(404) + + +@bp.route('//moderate/wiki/add', methods=['GET', 'POST']) +@login_required +def community_wiki_add(actor): + community = actor_to_community(actor) + + if community is not None: + if community.is_moderator() or current_user.is_admin(): + low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1' + form = EditCommunityWikiPageForm() + if form.validate_on_submit(): + new_page = CommunityWikiPage(community_id=community.id, slug=form.slug.data, title=form.title.data, + body=form.body.data, who_can_edit=form.who_can_edit.data) + new_page.body_html = markdown_to_html(new_page.body) + db.session.add(new_page) + db.session.commit() + + initial_revision = CommunityWikiPageRevision(wiki_page_id=new_page.id, user_id=current_user.id, + community_id=community.id, title=form.title.data, + body=form.body.data, body_html=new_page.body_html) + db.session.add(initial_revision) + db.session.commit() + + flash(_('Saved')) + return redirect(url_for('community.community_wiki_list', actor=community.link())) + + return render_template('community/community_wiki_edit.html', title=_('Add wiki page'), community=community, + form=form, low_bandwidth=low_bandwidth, current='wiki', + moderating_communities=moderating_communities(current_user.get_id()), + joined_communities=joined_communities(current_user.get_id()), + menu_topics=menu_topics(), site=g.site, + inoculation=inoculation[randint(0, len(inoculation) - 1)] if g.site.show_inoculation_block else None + ) + else: + abort(401) + else: + abort(404) + + +@bp.route('//wiki/', methods=['GET', 'POST']) +def community_wiki_view(actor, slug): + community = actor_to_community(actor) + + if community is not None: + page: CommunityWikiPage = CommunityWikiPage.query.filter_by(slug=slug, community_id=community.id).first() + if page is None: + abort(404) + else: + # Breadcrumbs + breadcrumbs = [] + breadcrumb = namedtuple("Breadcrumb", ['text', 'url']) + breadcrumb.text = _('Home') + breadcrumb.url = '/' + breadcrumbs.append(breadcrumb) + + if community.topic_id: + topics = [] + previous_topic = Topic.query.get(community.topic_id) + topics.append(previous_topic) + while previous_topic.parent_id: + topic = Topic.query.get(previous_topic.parent_id) + topics.append(topic) + previous_topic = topic + topics = list(reversed(topics)) + + breadcrumb = namedtuple("Breadcrumb", ['text', 'url']) + breadcrumb.text = _('Topics') + breadcrumb.url = '/topics' + breadcrumbs.append(breadcrumb) + + existing_url = '/topic' + for topic in topics: + breadcrumb = namedtuple("Breadcrumb", ['text', 'url']) + breadcrumb.text = topic.name + breadcrumb.url = f"{existing_url}/{topic.machine_name}" + breadcrumbs.append(breadcrumb) + existing_url = breadcrumb.url + else: + breadcrumb = namedtuple("Breadcrumb", ['text', 'url']) + breadcrumb.text = _('Communities') + breadcrumb.url = '/communities' + breadcrumbs.append(breadcrumb) + + return render_template('community/community_wiki_page_view.html', title=page.title, page=page, + community=community, breadcrumbs=breadcrumbs, is_moderator=community.is_moderator(), + is_owner=community.is_owner(), + moderating_communities=moderating_communities(current_user.get_id()), + joined_communities=joined_communities(current_user.get_id()), + menu_topics=menu_topics(), site=g.site, + inoculation=inoculation[ + randint(0, len(inoculation) - 1)] if g.site.show_inoculation_block else None + ) + + +@bp.route('//moderate/wiki//edit', methods=['GET', 'POST']) +@login_required +def community_wiki_edit(actor, page_id): + community = actor_to_community(actor) + + if community is not None: + page: CommunityWikiPage = CommunityWikiPage.query.get_or_404(page_id) + if page.can_edit(current_user, community): + low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1' + + form = EditCommunityWikiPageForm() + if form.validate_on_submit(): + page.title = form.title.data + page.slug = form.slug.data + page.body = form.body.data + page.body_html = markdown_to_html(page.body) + page.who_can_edit = form.who_can_edit.data + page.edited_at = utcnow() + new_revision = CommunityWikiPageRevision(wiki_page_id=page.id, user_id=current_user.id, + community_id=community.id, title=form.title.data, + body=form.body.data, body_html=page.body_html) + db.session.add(new_revision) + db.session.commit() + flash(_('Saved')) + if request.args.get('return') == 'list': + return redirect(url_for('community.community_wiki_list', actor=community.link())) + elif request.args.get('return') == 'page': + return redirect(url_for('community.community_wiki_view', actor=community.link(), slug=page.slug)) + else: + form.title.data = page.title + form.slug.data = page.slug + form.body.data = page.body + form.who_can_edit.data = page.who_can_edit + + return render_template('community/community_wiki_edit.html', title=_('Edit wiki page'), community=community, + form=form, low_bandwidth=low_bandwidth, + moderating_communities=moderating_communities(current_user.get_id()), + joined_communities=joined_communities(current_user.get_id()), + menu_topics=menu_topics(), site=g.site, + inoculation=inoculation[randint(0, len(inoculation) - 1)] if g.site.show_inoculation_block else None + ) + else: + abort(401) + else: + abort(404) + + +@bp.route('//moderate/wiki//delete', methods=['GET']) +@login_required +def community_wiki_delete(actor, page_id): + community = actor_to_community(actor) + + if community is not None: + page: CommunityWikiPage = CommunityWikiPage.query.get_or_404(page_id) + if page.can_edit(current_user, community): + db.session.delete(page) + db.session.commit() + flash(_('Page deleted')) + return redirect(url_for('community.community_wiki_list', actor=community.link())) + else: + abort(404) + + @bp.route('//moderate/modlog', methods=['GET']) @login_required def community_modlog(actor): diff --git a/app/models.py b/app/models.py index 661cebdb..e90ec3d5 100644 --- a/app/models.py +++ b/app/models.py @@ -406,6 +406,7 @@ class Community(db.Model): posts = db.relationship('Post', lazy='dynamic', cascade="all, delete-orphan") replies = db.relationship('PostReply', lazy='dynamic', cascade="all, delete-orphan") + wiki_pages = db.relationship('CommunityWikiPage', lazy='dynamic', backref='community', cascade="all, delete-orphan") icon = db.relationship('File', foreign_keys=[icon_id], single_parent=True, backref='community', cascade="all, delete-orphan") image = db.relationship('File', foreign_keys=[image_id], single_parent=True, cascade="all, delete-orphan") languages = db.relationship('Language', lazy='dynamic', secondary=community_language, backref=db.backref('communities', lazy='dynamic')) @@ -476,15 +477,25 @@ class Community(db.Model): )) ).filter(CommunityMember.is_banned == False).all() + def is_member(self, user): + if user is None: + return CommunityMember.query.filter(CommunityMember.user_id == current_user.get_id(), + CommunityMember.community_id == self.id, + CommunityMember.is_banned == False).all() + else: + return CommunityMember.query.filter(CommunityMember.user_id == user.id, + CommunityMember.community_id == self.id, + CommunityMember.is_banned == False).all() + def is_moderator(self, user=None): if user is None: - return any(moderator.user_id == current_user.id for moderator in self.moderators()) + return any(moderator.user_id == current_user.get_id() for moderator in self.moderators()) else: return any(moderator.user_id == user.id for moderator in self.moderators()) def is_owner(self, user=None): if user is None: - return any(moderator.user_id == current_user.id and moderator.is_owner for moderator in self.moderators()) + return any(moderator.user_id == current_user.get_id() and moderator.is_owner for moderator in self.moderators()) else: return any(moderator.user_id == user.id and moderator.is_owner for moderator in self.moderators()) @@ -1246,6 +1257,46 @@ class CommunityMember(db.Model): created_at = db.Column(db.DateTime, default=utcnow) +class CommunityWikiPage(db.Model): + id = db.Column(db.Integer, primary_key=True) + community_id = db.Column(db.Integer, db.ForeignKey('community.id'), index=True) + slug = db.Column(db.String(100), index=True) + title = db.Column(db.String(255)) + body = db.Column(db.Text) + body_html = db.Column(db.Text) + created_at = db.Column(db.DateTime, default=utcnow) + edited_at = db.Column(db.DateTime, default=utcnow) + who_can_edit = db.Column(db.Integer, default=0) # 0 = mods & admins, 1 = trusted, 2 = community members, 3 = anyone + revisions = db.relationship('CommunityWikiPageRevision', backref=db.backref('page'), cascade='all,delete', + lazy='dynamic') + def can_edit(self, user: User, community: Community): + if user.is_anonymous: + return False + if self.who_can_edit == 0: + if user.is_admin() or user.is_staff() or community.is_moderator(user): + return True + elif self.who_can_edit == 1: + if user.is_admin() or user.is_staff() or community.is_moderator(user) or user.trustworthy(): + return True + elif self.who_can_edit == 2: + if user.is_admin() or user.is_staff() or community.is_moderator(user) or user.trustworthy() or community.is_member(user): + return True + elif self.who_can_edit == 3: + return True + return False + + +class CommunityWikiPageRevision(db.Model): + id = db.Column(db.Integer, primary_key=True) + wiki_page_id = db.Column(db.Integer, db.ForeignKey('community_wiki_page.id'), index=True) + community_id = db.Column(db.Integer, db.ForeignKey('community.id'), index=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id')) + title = db.Column(db.String(255)) + body = db.Column(db.Text) + body_html = db.Column(db.Text) + edited_at = db.Column(db.DateTime, default=utcnow) + + class UserFollower(db.Model): local_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) remote_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) diff --git a/app/post/routes.py b/app/post/routes.py index 10c5160b..5c2197bb 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -55,7 +55,7 @@ def show_post(post_id: int): flash(_('%(name)s has indicated they made a mistake in this post.', name=post.author.user_name), 'warning') mods = community_moderators(community.id) - is_moderator = current_user.is_authenticated and any(mod.user_id == current_user.id for mod in mods) + is_moderator = community.is_moderator() if community.private_mods: mod_list = [] @@ -310,21 +310,19 @@ def show_post(post_id: int): if post.type == POST_TYPE_LINK and body_has_no_archive_link(post.body_html) and url_needs_archive(post.url): archive_link = generate_archive_link(post.url) - response = render_template('post/post.html', title=post.title, post=post, is_moderator=is_moderator, community=post.community, + response = render_template('post/post.html', title=post.title, post=post, is_moderator=is_moderator, is_owner=community.is_owner(), + community=post.community, breadcrumbs=breadcrumbs, related_communities=related_communities, mods=mod_list, poll_form=poll_form, poll_results=poll_results, poll_data=poll_data, poll_choices=poll_choices, poll_total_votes=poll_total_votes, canonical=post.ap_id, form=form, replies=replies, THREAD_CUTOFF_DEPTH=constants.THREAD_CUTOFF_DEPTH, - description=description, og_image=og_image, POST_TYPE_IMAGE=constants.POST_TYPE_IMAGE, - POST_TYPE_LINK=constants.POST_TYPE_LINK, POST_TYPE_ARTICLE=constants.POST_TYPE_ARTICLE, - POST_TYPE_VIDEO=constants.POST_TYPE_VIDEO, POST_TYPE_POLL=constants.POST_TYPE_POLL, + description=description, og_image=og_image, autoplay=request.args.get('autoplay', False), archive_link=archive_link, noindex=not post.author.indexable, preconnect=post.url if post.url else None, recently_upvoted=recently_upvoted, recently_downvoted=recently_downvoted, recently_upvoted_replies=recently_upvoted_replies, recently_downvoted_replies=recently_downvoted_replies, reply_collapse_threshold=reply_collapse_threshold, etag=f"{post.id}{sort}_{hash(post.last_active)}", markdown_editor=current_user.is_authenticated and current_user.markdown_editor, - low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1', SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER, - SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR, + low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1', moderating_communities=moderating_communities(current_user.get_id()), joined_communities=joined_communities(current_user.get_id()), menu_topics=menu_topics(), site=g.site, diff --git a/app/templates/community/_community_moderation_nav.html b/app/templates/community/_community_moderation_nav.html index a3d1e725..fccde4d0 100644 --- a/app/templates/community/_community_moderation_nav.html +++ b/app/templates/community/_community_moderation_nav.html @@ -13,6 +13,9 @@ {{ _('Subscribers') }} + + {{ _('Wiki') }} + {{ _('Appeals') }} diff --git a/app/templates/community/community_wiki_edit.html b/app/templates/community/community_wiki_edit.html new file mode 100644 index 00000000..8f1c4d40 --- /dev/null +++ b/app/templates/community/community_wiki_edit.html @@ -0,0 +1,18 @@ +{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') %} + {% extends 'themes/' + theme() + '/base.html' %} +{% else %} + {% extends "base.html" %} +{% endif %} %} +{% from 'bootstrap/form.html' import render_form, render_field %} + +{% block app_content %} +
+
+ {% block title %}

{{ title }}

{% endblock %} + {{ render_form(form) }} +
+ + {% include "_side_pane.html" %} + +
+{% endblock %} diff --git a/app/templates/community/community_wiki_list.html b/app/templates/community/community_wiki_list.html new file mode 100644 index 00000000..bfbc7632 --- /dev/null +++ b/app/templates/community/community_wiki_list.html @@ -0,0 +1,67 @@ +{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') %} + {% extends 'themes/' + theme() + '/base.html' %} +{% else %} + {% extends "base.html" %} +{% endif %} %} +{% from 'bootstrap/form.html' import render_field %} + +{% block app_content %} +
+
+ + {% include "community/_community_moderation_nav.html" %} +
+
+

{{ _('Wiki pages for %(community)s', community=community.display_name()) }}

+
+ +
+ {% if pages -%} + + + + + + + + + + {% for page in pages %} + + + + + + {% endfor %} + +
{{ _('Name') }}{{ _('Url') }}
{{ page.title }}{{ page.slug }}{% if page.can_edit(current_user, community) %} + + {% endif %}
+ {% else -%} +

{{ _('There are no wiki pages in this community.') }}

+ {% endif -%} +
+
+{% endblock %} \ No newline at end of file diff --git a/app/templates/community/community_wiki_page_view.html b/app/templates/community/community_wiki_page_view.html new file mode 100644 index 00000000..c4b0f4a3 --- /dev/null +++ b/app/templates/community/community_wiki_page_view.html @@ -0,0 +1,33 @@ +{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') -%} + {% extends 'themes/' + theme() + '/base.html' -%} +{% else -%} + {% extends "base.html" -%} +{% endif -%} -%} +{% from 'bootstrap/form.html' import render_form -%} + +{% block app_content -%} + +
+
+
+
+ +

{{ page.title }}

+ {{ page.body_html | safe }} + {% if page.can_edit(current_user, community) -%} +

{{ _('Edit') }}

+ {% endif %} +
+
+
+ {% include "_side_pane.html" %} +
+{% endblock -%} diff --git a/migrations/versions/b97584a7a10b_wikis.py b/migrations/versions/b97584a7a10b_wikis.py new file mode 100644 index 00000000..75d89dcd --- /dev/null +++ b/migrations/versions/b97584a7a10b_wikis.py @@ -0,0 +1,71 @@ +"""wikis + +Revision ID: b97584a7a10b +Revises: ea5a07acf23c +Create Date: 2024-07-17 20:54:20.626562 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b97584a7a10b' +down_revision = 'ea5a07acf23c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('community_wiki_page', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('community_id', sa.Integer(), nullable=True), + sa.Column('slug', sa.String(length=100), nullable=True), + sa.Column('title', sa.String(length=255), nullable=True), + sa.Column('body', sa.Text(), nullable=True), + sa.Column('body_html', sa.Text(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('edited_at', sa.DateTime(), nullable=True), + sa.Column('who_can_edit', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['community_id'], ['community.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('community_wiki_page', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_community_wiki_page_community_id'), ['community_id'], unique=False) + batch_op.create_index(batch_op.f('ix_community_wiki_page_slug'), ['slug'], unique=False) + + op.create_table('community_wiki_page_revision', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('wiki_page_id', sa.Integer(), nullable=True), + sa.Column('community_id', sa.Integer(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('title', sa.String(length=255), nullable=True), + sa.Column('body', sa.Text(), nullable=True), + sa.Column('body_html', sa.Text(), nullable=True), + sa.Column('edited_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['community_id'], ['community.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.ForeignKeyConstraint(['wiki_page_id'], ['community_wiki_page.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('community_wiki_page_revision', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_community_wiki_page_revision_community_id'), ['community_id'], unique=False) + batch_op.create_index(batch_op.f('ix_community_wiki_page_revision_wiki_page_id'), ['wiki_page_id'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('community_wiki_page_revision', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_community_wiki_page_revision_wiki_page_id')) + batch_op.drop_index(batch_op.f('ix_community_wiki_page_revision_community_id')) + + op.drop_table('community_wiki_page_revision') + with op.batch_alter_table('community_wiki_page', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_community_wiki_page_slug')) + batch_op.drop_index(batch_op.f('ix_community_wiki_page_community_id')) + + op.drop_table('community_wiki_page') + # ### end Alembic commands ### diff --git a/pyfedi.py b/pyfedi.py index ae4c8ba6..b446271a 100644 --- a/pyfedi.py +++ b/pyfedi.py @@ -9,7 +9,7 @@ from app import create_app, db, cli import os, click from flask import session, g, json, request, current_app from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_ARTICLE, POST_TYPE_VIDEO, POST_TYPE_POLL, \ - SUBSCRIPTION_MODERATOR + SUBSCRIPTION_MODERATOR, SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, SUBSCRIPTION_PENDING from app.models import Site from app.utils import getmtime, gibberish, shorten_string, shorten_url, digits, user_access, community_membership, \ can_create_post, can_upvote, can_downvote, shorten_number, ap_datetime, current_theme, community_link_to_href, \ @@ -26,7 +26,8 @@ def app_context_processor(): return dict(getmtime=getmtime, instance_domain=current_app.config['SERVER_NAME'], POST_TYPE_LINK=POST_TYPE_LINK, POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_ARTICLE=POST_TYPE_ARTICLE, POST_TYPE_VIDEO=POST_TYPE_VIDEO, POST_TYPE_POLL=POST_TYPE_POLL, - SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR) + SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER, + SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING) @app.shell_context_processor