mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
fcde6e9d26
9 changed files with 238 additions and 16 deletions
|
@ -39,7 +39,7 @@ import arrow
|
||||||
from cryptography.exceptions import InvalidSignature
|
from cryptography.exceptions import InvalidSignature
|
||||||
from cryptography.hazmat.primitives import hashes, serialization
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
||||||
from flask import Request, current_app
|
from flask import Request, current_app, g
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
from pyld import jsonld
|
from pyld import jsonld
|
||||||
|
@ -81,8 +81,9 @@ def post_request(uri: str, body: dict | None, private_key: str, key_id: str, con
|
||||||
if '@context' not in body: # add a default json-ld context if necessary
|
if '@context' not in body: # add a default json-ld context if necessary
|
||||||
body['@context'] = default_context()
|
body['@context'] = default_context()
|
||||||
type = body['type'] if 'type' in body else ''
|
type = body['type'] if 'type' in body else ''
|
||||||
log = ActivityPubLog(direction='out', activity_json=json.dumps(body), activity_type=type,
|
log = ActivityPubLog(direction='out', activity_type=type, result='processing', activity_id=body['id'], exception_message='')
|
||||||
result='processing', activity_id=body['id'], exception_message='')
|
if g.site.log_activitypub_json:
|
||||||
|
log.activity_json=json.dumps(body)
|
||||||
db.session.add(log)
|
db.session.add(log)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
from flask import request, g
|
from flask import request, g
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SubmitField, TextAreaField, BooleanField, HiddenField, SelectField, FileField
|
from validators import Min
|
||||||
|
from wtforms import StringField, SubmitField, TextAreaField, BooleanField, HiddenField, SelectField, FileField, \
|
||||||
|
DateField
|
||||||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
|
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
|
||||||
from flask_babel import _, lazy_gettext as _l
|
from flask_babel import _, lazy_gettext as _l
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import Community
|
from app.models import Community, utcnow
|
||||||
from app.utils import domain_from_url, MultiCheckboxField
|
from app.utils import domain_from_url, MultiCheckboxField
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
@ -65,6 +67,14 @@ class SearchRemoteCommunity(FlaskForm):
|
||||||
submit = SubmitField(_l('Search'))
|
submit = SubmitField(_l('Search'))
|
||||||
|
|
||||||
|
|
||||||
|
class BanUserCommunityForm(FlaskForm):
|
||||||
|
reason = StringField(_l('Reason'), render_kw={'autofocus': True}, validators=[DataRequired()])
|
||||||
|
ban_until = DateField(_l('Ban until'))
|
||||||
|
delete_posts = BooleanField(_l('Also delete all their posts'))
|
||||||
|
delete_post_replies = BooleanField(_l('Also delete all their comments'))
|
||||||
|
submit = SubmitField(_l('Ban'))
|
||||||
|
|
||||||
|
|
||||||
class CreatePostForm(FlaskForm):
|
class CreatePostForm(FlaskForm):
|
||||||
communities = SelectField(_l('Community'), validators=[DataRequired()], coerce=int)
|
communities = SelectField(_l('Community'), validators=[DataRequired()], coerce=int)
|
||||||
post_type = HiddenField() # https://getbootstrap.com/docs/4.6/components/navs/#tabs
|
post_type = HiddenField() # https://getbootstrap.com/docs/4.6/components/navs/#tabs
|
||||||
|
|
|
@ -12,14 +12,15 @@ from app.activitypub.signature import RsaKeys, post_request
|
||||||
from app.activitypub.util import default_context, notify_about_post, find_actor_or_create
|
from app.activitypub.util import default_context, notify_about_post, find_actor_or_create
|
||||||
from app.chat.util import send_message
|
from app.chat.util import send_message
|
||||||
from app.community.forms import SearchRemoteCommunity, CreatePostForm, ReportCommunityForm, \
|
from app.community.forms import SearchRemoteCommunity, CreatePostForm, ReportCommunityForm, \
|
||||||
DeleteCommunityForm, AddCommunityForm, EditCommunityForm, AddModeratorForm
|
DeleteCommunityForm, AddCommunityForm, EditCommunityForm, AddModeratorForm, BanUserCommunityForm
|
||||||
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, \
|
||||||
opengraph_parse, url_to_thumbnail_file, save_post, save_icon_file, save_banner_file, send_to_remote_instance
|
opengraph_parse, url_to_thumbnail_file, save_post, save_icon_file, save_banner_file, send_to_remote_instance, \
|
||||||
|
delete_post_from_community, delete_post_reply_from_community
|
||||||
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, \
|
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, \
|
||||||
SUBSCRIPTION_PENDING, SUBSCRIPTION_MODERATOR
|
SUBSCRIPTION_PENDING, SUBSCRIPTION_MODERATOR
|
||||||
from app.inoculation import inoculation
|
from app.inoculation import inoculation
|
||||||
from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, \
|
from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, \
|
||||||
File, PostVote, utcnow, Report, Notification, InstanceBlock, ActivityPubLog, Topic, Conversation
|
File, PostVote, utcnow, Report, Notification, InstanceBlock, ActivityPubLog, Topic, Conversation, PostReply
|
||||||
from app.community import bp
|
from app.community import bp
|
||||||
from app.user.utils import search_for_user
|
from app.user.utils import search_for_user
|
||||||
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
||||||
|
@ -772,6 +773,62 @@ def community_block_instance(community_id: int):
|
||||||
return redirect(community.local_url())
|
return redirect(community.local_url())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/community/<int:community_id>/<int:user_id>/ban_user_community', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def community_ban_user(community_id: int, user_id: int):
|
||||||
|
community = Community.query.get_or_404(community_id)
|
||||||
|
user = User.query.get_or_404(user_id)
|
||||||
|
existing = CommunityBan.query.filter_by(community_id=community.id, user_id=user.id).first()
|
||||||
|
|
||||||
|
form = BanUserCommunityForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if not existing:
|
||||||
|
new_ban = CommunityBan(community_id=community_id, user_id=user.id, banned_by=current_user.id,
|
||||||
|
reason=form.reason.data)
|
||||||
|
if form.ban_until.data is not None and form.ban_until.data < utcnow().date():
|
||||||
|
new_ban.ban_until = form.ban_until.data
|
||||||
|
db.session.add(new_ban)
|
||||||
|
db.session.commit()
|
||||||
|
flash(_('%(name)s has been banned.', name=user.display_name()))
|
||||||
|
|
||||||
|
if form.delete_posts.data:
|
||||||
|
posts = Post.query.filter(Post.user_id == user.id, Post.community_id == community.id).all()
|
||||||
|
for post in posts:
|
||||||
|
delete_post_from_community(post.id)
|
||||||
|
if posts:
|
||||||
|
flash(_('Posts by %(name)s have been deleted.', name=user.display_name()))
|
||||||
|
if form.delete_post_replies.data:
|
||||||
|
post_replies = PostReply.query.filter(PostReply.user_id == user.id, Post.community_id == community.id).all()
|
||||||
|
for post_reply in post_replies:
|
||||||
|
delete_post_reply_from_community(post_reply.id)
|
||||||
|
if post_replies:
|
||||||
|
flash(_('Comments by %(name)s have been deleted.', name=user.display_name()))
|
||||||
|
|
||||||
|
# todo: federate ban to post author instance
|
||||||
|
|
||||||
|
# notify banned person
|
||||||
|
if user.is_local():
|
||||||
|
notify = Notification(title=shorten_string('You have been banned from ' + community.title),
|
||||||
|
url=f'/', user_id=user.id,
|
||||||
|
author_id=1)
|
||||||
|
db.session.add(notify)
|
||||||
|
user.unread_notifications += 1
|
||||||
|
db.session.commit()
|
||||||
|
else:
|
||||||
|
...
|
||||||
|
# todo: send chatmessage to remote user and federate it
|
||||||
|
|
||||||
|
return redirect(community.local_url())
|
||||||
|
else:
|
||||||
|
return render_template('community/community_ban_user.html', title=_('Ban from community'), form=form, community=community,
|
||||||
|
user=user,
|
||||||
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
|
joined_communities=joined_communities(current_user.get_id()),
|
||||||
|
inoculation=inoculation[randint(0, len(inoculation) - 1)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<int:community_id>/notification', methods=['GET', 'POST'])
|
@bp.route('/<int:community_id>/notification', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def community_notification(community_id: int):
|
def community_notification(community_id: int):
|
||||||
|
|
|
@ -10,12 +10,13 @@ from pillow_heif import register_heif_opener
|
||||||
|
|
||||||
from app import db, cache, celery
|
from app import db, cache, celery
|
||||||
from app.activitypub.signature import post_request
|
from app.activitypub.signature import post_request
|
||||||
from app.activitypub.util import find_actor_or_create, actor_json_to_model, post_json_to_model
|
from app.activitypub.util import find_actor_or_create, actor_json_to_model, post_json_to_model, default_context
|
||||||
from app.constants import POST_TYPE_ARTICLE, POST_TYPE_LINK, POST_TYPE_IMAGE
|
from app.constants import POST_TYPE_ARTICLE, POST_TYPE_LINK, POST_TYPE_IMAGE
|
||||||
from app.models import Community, File, BannedInstances, PostReply, PostVote, Post, utcnow, CommunityMember, Site, \
|
from app.models import Community, File, BannedInstances, PostReply, PostVote, Post, utcnow, CommunityMember, Site, \
|
||||||
Instance, Notification, User
|
Instance, Notification, User
|
||||||
from app.utils import get_request, gibberish, markdown_to_html, domain_from_url, allowlist_html, \
|
from app.utils import get_request, gibberish, markdown_to_html, domain_from_url, allowlist_html, \
|
||||||
html_to_markdown, is_image_url, ensure_directory_exists, inbox_domain, post_ranking, shorten_string, parse_page, remove_tracking_from_link
|
html_to_markdown, is_image_url, ensure_directory_exists, inbox_domain, post_ranking, shorten_string, parse_page, \
|
||||||
|
remove_tracking_from_link, ap_datetime, instance_banned
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -283,6 +284,121 @@ def save_post(form, post: Post):
|
||||||
g.site.last_active = utcnow()
|
g.site.last_active = utcnow()
|
||||||
|
|
||||||
|
|
||||||
|
def delete_post_from_community(post_id):
|
||||||
|
if current_app.debug:
|
||||||
|
delete_post_from_community_task(post_id)
|
||||||
|
else:
|
||||||
|
delete_post_from_community_task.delay(post_id)
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task
|
||||||
|
def delete_post_from_community_task(post_id):
|
||||||
|
post = Post.query.get(post_id)
|
||||||
|
community = post.community
|
||||||
|
post.delete_dependencies()
|
||||||
|
post.flush_cache()
|
||||||
|
db.session.delete(post)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if not community.local_only:
|
||||||
|
delete_json = {
|
||||||
|
'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}",
|
||||||
|
'type': 'Delete',
|
||||||
|
'actor': current_user.profile_id(),
|
||||||
|
'audience': post.community.profile_id(),
|
||||||
|
'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'],
|
||||||
|
'published': ap_datetime(utcnow()),
|
||||||
|
'cc': [
|
||||||
|
current_user.followers_url()
|
||||||
|
],
|
||||||
|
'object': post.ap_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it
|
||||||
|
success = post_request(post.community.ap_inbox_url, delete_json, current_user.private_key,
|
||||||
|
current_user.ap_profile_id + '#main-key')
|
||||||
|
else: # local community - send it to followers on remote instances
|
||||||
|
announce = {
|
||||||
|
"id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}",
|
||||||
|
"type": 'Announce',
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"actor": post.community.ap_profile_id,
|
||||||
|
"cc": [
|
||||||
|
post.community.ap_followers_url
|
||||||
|
],
|
||||||
|
'@context': default_context(),
|
||||||
|
'object': delete_json
|
||||||
|
}
|
||||||
|
|
||||||
|
for instance in post.community.following_instances():
|
||||||
|
if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(
|
||||||
|
instance.domain):
|
||||||
|
send_to_remote_instance(instance.id, post.community.id, announce)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_post_reply_from_community(post_reply_id):
|
||||||
|
if current_app.debug:
|
||||||
|
delete_post_reply_from_community_task(post_reply_id)
|
||||||
|
else:
|
||||||
|
delete_post_reply_from_community_task.delay(post_reply_id)
|
||||||
|
|
||||||
|
|
||||||
|
@celery.task
|
||||||
|
def delete_post_reply_from_community_task(post_reply_id):
|
||||||
|
post_reply = PostReply.query.get(post_reply_id)
|
||||||
|
post = post_reply.post
|
||||||
|
community = post.community
|
||||||
|
if post_reply.user_id == current_user.id or community.is_moderator():
|
||||||
|
if post_reply.has_replies():
|
||||||
|
post_reply.body = 'Deleted by author' if post_reply.author.id == current_user.id else 'Deleted by moderator'
|
||||||
|
post_reply.body_html = markdown_to_html(post_reply.body)
|
||||||
|
else:
|
||||||
|
post_reply.delete_dependencies()
|
||||||
|
db.session.delete(post_reply)
|
||||||
|
db.session.commit()
|
||||||
|
post.flush_cache()
|
||||||
|
# federate delete
|
||||||
|
if not post.community.local_only:
|
||||||
|
delete_json = {
|
||||||
|
'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}",
|
||||||
|
'type': 'Delete',
|
||||||
|
'actor': current_user.profile_id(),
|
||||||
|
'audience': post.community.profile_id(),
|
||||||
|
'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'],
|
||||||
|
'published': ap_datetime(utcnow()),
|
||||||
|
'cc': [
|
||||||
|
current_user.followers_url()
|
||||||
|
],
|
||||||
|
'object': post_reply.ap_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it
|
||||||
|
success = post_request(post.community.ap_inbox_url, delete_json, current_user.private_key,
|
||||||
|
current_user.ap_profile_id + '#main-key')
|
||||||
|
|
||||||
|
else: # local community - send it to followers on remote instances
|
||||||
|
announce = {
|
||||||
|
"id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}",
|
||||||
|
"type": 'Announce',
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"actor": post.community.ap_profile_id,
|
||||||
|
"cc": [
|
||||||
|
post.community.ap_followers_url
|
||||||
|
],
|
||||||
|
'@context': default_context(),
|
||||||
|
'object': delete_json
|
||||||
|
}
|
||||||
|
|
||||||
|
for instance in post.community.following_instances():
|
||||||
|
if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(
|
||||||
|
instance.domain):
|
||||||
|
send_to_remote_instance(instance.id, post.community.id, announce)
|
||||||
|
|
||||||
|
|
||||||
def remove_old_file(file_id):
|
def remove_old_file(file_id):
|
||||||
remove_file = File.query.get(file_id)
|
remove_file = File.query.get(file_id)
|
||||||
remove_file.delete_from_disk()
|
remove_file.delete_from_disk()
|
||||||
|
|
|
@ -374,7 +374,12 @@ class Community(db.Model):
|
||||||
|
|
||||||
def user_is_banned(self, user):
|
def user_is_banned(self, user):
|
||||||
membership = CommunityMember.query.filter(CommunityMember.community_id == self.id, CommunityMember.user_id == user.id).first()
|
membership = CommunityMember.query.filter(CommunityMember.community_id == self.id, CommunityMember.user_id == user.id).first()
|
||||||
return membership.is_banned if membership else False
|
if membership.is_banned:
|
||||||
|
return True
|
||||||
|
banned = CommunityBan.query.filter(CommunityBan.community_id == self.id, CommunityBan.user_id == user.id).first()
|
||||||
|
if banned:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def profile_id(self):
|
def profile_id(self):
|
||||||
|
@ -980,7 +985,7 @@ class CommunityMember(db.Model):
|
||||||
|
|
||||||
# people banned from communities
|
# people banned from communities
|
||||||
class CommunityBan(db.Model):
|
class CommunityBan(db.Model):
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) # person who is banned, not the banner
|
||||||
community_id = db.Column(db.Integer, db.ForeignKey('community.id'), primary_key=True)
|
community_id = db.Column(db.Integer, db.ForeignKey('community.id'), primary_key=True)
|
||||||
banned_by = db.Column(db.Integer, db.ForeignKey('user.id'))
|
banned_by = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
reason = db.Column(db.String(50))
|
reason = db.Column(db.String(50))
|
||||||
|
|
|
@ -17,7 +17,8 @@ from app.community.forms import CreatePostForm
|
||||||
from app.post.util import post_replies, get_comment_branch, post_reply_count
|
from app.post.util import post_replies, get_comment_branch, post_reply_count
|
||||||
from app.constants import SUBSCRIPTION_MEMBER, POST_TYPE_LINK, POST_TYPE_IMAGE
|
from app.constants import SUBSCRIPTION_MEMBER, POST_TYPE_LINK, POST_TYPE_IMAGE
|
||||||
from app.models import Post, PostReply, \
|
from app.models import Post, PostReply, \
|
||||||
PostReplyVote, PostVote, Notification, utcnow, UserBlock, DomainBlock, InstanceBlock, Report, Site, Community, Topic
|
PostReplyVote, PostVote, Notification, utcnow, UserBlock, DomainBlock, InstanceBlock, Report, Site, Community, \
|
||||||
|
Topic
|
||||||
from app.post import bp
|
from app.post import bp
|
||||||
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
||||||
shorten_string, markdown_to_text, gibberish, ap_datetime, return_304, \
|
shorten_string, markdown_to_text, gibberish, ap_datetime, return_304, \
|
||||||
|
@ -585,7 +586,8 @@ def add_reply(post_id: int, comment_id: int):
|
||||||
@bp.route('/post/<int:post_id>/options', methods=['GET'])
|
@bp.route('/post/<int:post_id>/options', methods=['GET'])
|
||||||
def post_options(post_id: int):
|
def post_options(post_id: int):
|
||||||
post = Post.query.get_or_404(post_id)
|
post = Post.query.get_or_404(post_id)
|
||||||
return render_template('post/post_options.html', post=post, moderating_communities=moderating_communities(current_user.get_id()),
|
return render_template('post/post_options.html', post=post,
|
||||||
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
joined_communities=joined_communities(current_user.get_id()))
|
joined_communities=joined_communities(current_user.get_id()))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,13 @@
|
||||||
<td><span style="color: red;">{{ activity.result }}</span></td>
|
<td><span style="color: red;">{{ activity.result }}</span></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>{{ activity.exception_message if activity.exception_message else '' }}</td>
|
<td>{{ activity.exception_message if activity.exception_message else '' }}</td>
|
||||||
<td><a href="{{ url_for('admin.activity_json', activity_id=activity.id) }}">View</a></td>
|
<td>
|
||||||
|
{% if activity.activity_json is none %}
|
||||||
|
None
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('admin.activity_json', activity_id=activity.id) }}">View</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
@ -56,4 +62,4 @@
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
21
app/templates/community/community_ban_user.html
Normal file
21
app/templates/community/community_ban_user.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{% 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 %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-login mx-auto">
|
||||||
|
<div class="card mt-5">
|
||||||
|
<div class="card-body p-6">
|
||||||
|
<div class="card-title">{{ _('Ban "%(user_name)s" from %(community_name)s', user_name=user.display_name(), community_name=community.title) }}</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{{ render_form(form) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -26,6 +26,10 @@
|
||||||
{% if post.user_id != current_user.id %}
|
{% if post.user_id != current_user.id %}
|
||||||
<li><a href="{{ url_for('post.post_block_user', post_id=post.id) }}" class="no-underline"><span class="fe fe-block"></span>
|
<li><a href="{{ url_for('post.post_block_user', post_id=post.id) }}" class="no-underline"><span class="fe fe-block"></span>
|
||||||
{{ _('Block post author @%(author_name)s', author_name=post.author.user_name) }}</a></li>
|
{{ _('Block post author @%(author_name)s', author_name=post.author.user_name) }}</a></li>
|
||||||
|
{% if post.community.is_moderator() or current_user.is_admin() %}
|
||||||
|
<li><a href="{{ url_for('community.community_ban_user', community_id=post.community.id, user_id=post.author.id) }}" class="no-underline"><span class="fe fe-block red"></span>
|
||||||
|
{{ _('Ban post author @%(author_name)s from<br>%(community_name)s', author_name=post.author.user_name, community_name=post.community.title) }}</a></li>
|
||||||
|
{% endif %}
|
||||||
{% if post.domain_id %}
|
{% if post.domain_id %}
|
||||||
<li><a href="{{ url_for('post.post_block_domain', post_id=post.id) }}" class="no-underline"><span class="fe fe-block"></span>
|
<li><a href="{{ url_for('post.post_block_domain', post_id=post.id) }}" class="no-underline"><span class="fe fe-block"></span>
|
||||||
{{ _('Block domain %(domain)s', domain=post.domain.name) }}</a></li>
|
{{ _('Block domain %(domain)s', domain=post.domain.name) }}</a></li>
|
||||||
|
|
Loading…
Reference in a new issue