when adding moderators do a fuzzy search and present a list of people to add. fixes #248

This commit is contained in:
rimu 2024-07-14 14:01:22 +08:00
parent 8f5af6f14d
commit 6d555c4c54
9 changed files with 126 additions and 77 deletions

View file

@ -65,7 +65,7 @@ class EditCommunityForm(FlaskForm):
class AddModeratorForm(FlaskForm):
user_name = StringField(_l('User name'), validators=[DataRequired()])
submit = SubmitField(_l('Add'))
submit = SubmitField(_l('Find'))
class EscalateReportForm(FlaskForm):

View file

@ -18,7 +18,7 @@ from app.community.forms import SearchRemoteCommunity, CreateDiscussionForm, Cre
EscalateReportForm, ResolveReportForm, CreateVideoForm, CreatePollForm, RetrieveRemotePost
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
delete_post_from_community, delete_post_reply_from_community, community_in_list, find_local_users
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, \
SUBSCRIPTION_PENDING, SUBSCRIPTION_MODERATOR, REPORT_STATE_NEW, REPORT_STATE_ESCALATED, REPORT_STATE_RESOLVED, \
REPORT_STATE_DISCARDED, POST_TYPE_VIDEO, NOTIF_COMMUNITY, POST_TYPE_POLL, MICROBLOG_APPS
@ -988,65 +988,75 @@ def community_mod_list(community_id: int):
)
@bp.route('/community/<int:community_id>/moderators/add', methods=['GET', 'POST'])
@bp.route('/community/<int:community_id>/moderators/add/<int:user_id>', methods=['GET', 'POST'])
@login_required
def community_add_moderator(community_id: int):
def community_add_moderator(community_id: int, user_id: int):
if current_user.banned:
return show_ban_message()
community = Community.query.get_or_404(community_id)
new_moderator = User.query.get_or_404(user_id)
if community.is_owner() or current_user.is_admin() and not new_moderator.banned:
existing_member = CommunityMember.query.filter(CommunityMember.user_id == new_moderator.id,
CommunityMember.community_id == community_id).first()
if existing_member:
existing_member.is_moderator = True
else:
new_member = CommunityMember(community_id=community_id, user_id=new_moderator.id, is_moderator=True)
db.session.add(new_member)
db.session.commit()
flash(_('Moderator added'))
# Notify new mod
if new_moderator.is_local():
notify = Notification(title=_('You are now a moderator of %(name)s', name=community.display_name()),
url='/c/' + community.name, user_id=new_moderator.id,
author_id=current_user.id)
new_moderator.unread_notifications += 1
db.session.add(notify)
db.session.commit()
else:
# for remote users, send a chat message to let them know
existing_conversation = Conversation.find_existing_conversation(recipient=new_moderator,
sender=current_user)
if not existing_conversation:
existing_conversation = Conversation(user_id=current_user.id)
existing_conversation.members.append(new_moderator)
existing_conversation.members.append(current_user)
db.session.add(existing_conversation)
db.session.commit()
server = current_app.config['SERVER_NAME']
send_message(f"Hi there. I've added you as a moderator to the community !{community.name}@{server}.",
existing_conversation.id)
add_to_modlog('add_mod', community_id=community_id, link_text=new_moderator.display_name(),
link=new_moderator.link())
# Flush cache
cache.delete_memoized(moderating_communities, new_moderator.id)
cache.delete_memoized(joined_communities, new_moderator.id)
cache.delete_memoized(community_moderators, community_id)
return redirect(url_for('community.community_mod_list', community_id=community.id))
@bp.route('/community/<int:community_id>/moderators/find', methods=['GET', 'POST'])
@login_required
def community_find_moderator(community_id: int):
if current_user.banned:
return show_ban_message()
community = Community.query.get_or_404(community_id)
if community.is_owner() or current_user.is_admin():
form = AddModeratorForm()
potential_moderators = None
if form.validate_on_submit():
new_moderator = search_for_user(form.user_name.data)
if new_moderator:
existing_member = CommunityMember.query.filter(CommunityMember.user_id == new_moderator.id, CommunityMember.community_id == community_id).first()
if existing_member:
existing_member.is_moderator = True
else:
new_member = CommunityMember(community_id=community_id, user_id=new_moderator.id, is_moderator=True)
db.session.add(new_member)
db.session.commit()
flash(_('Moderator added'))
potential_moderators = find_local_users(form.user_name.data)
# Notify new mod
if new_moderator.is_local():
notify = Notification(title=_('You are now a moderator of %(name)s', name=community.display_name()),
url='/c/' + community.name, user_id=new_moderator.id,
author_id=current_user.id)
new_moderator.unread_notifications += 1
db.session.add(notify)
db.session.commit()
else:
# for remote users, send a chat message to let them know
existing_conversation = Conversation.find_existing_conversation(recipient=new_moderator,
sender=current_user)
if not existing_conversation:
existing_conversation = Conversation(user_id=current_user.id)
existing_conversation.members.append(new_moderator)
existing_conversation.members.append(current_user)
db.session.add(existing_conversation)
db.session.commit()
server = current_app.config['SERVER_NAME']
send_message(f"Hi there. I've added you as a moderator to the community !{community.name}@{server}.",
existing_conversation.id)
add_to_modlog('add_mod', community_id=community_id, link_text=new_moderator.display_name(),
link=new_moderator.link())
# Flush cache
cache.delete_memoized(moderating_communities, new_moderator.id)
cache.delete_memoized(joined_communities, new_moderator.id)
cache.delete_memoized(community_moderators, community_id)
return redirect(url_for('community.community_mod_list', community_id=community.id))
else:
flash(_('Account not found'), 'warning')
return render_template('community/community_add_moderator.html', title=_('Add moderator to %(community)s', community=community.display_name()),
community=community, form=form,
moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()),
menu_topics=menu_topics(), site=g.site
)
return render_template('community/community_find_moderator.html', title=_('Add moderator to %(community)s',
community=community.display_name()),
community=community, form=form, potential_moderators=potential_moderators,
moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()),
menu_topics=menu_topics(), site=g.site
)
@bp.route('/community/<int:community_id>/moderators/remove/<int:user_id>', methods=['GET', 'POST'])

View file

@ -727,3 +727,8 @@ def community_in_list(community_id, community_list):
if community_id == tup[0]:
return True
return False
def find_local_users(search: str) -> List[User]:
return User.query.filter(User.banned == False, User.deleted == False, User.ap_id == None, User.user_name.ilike(f"%{search}%")).\
order_by(desc(User.reputation)).all()

View file

@ -1,19 +0,0 @@
{% 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">{{ _('Add moderator to %(community)s', community=community.display_name()) }}</div>
{{ render_form(form) }}
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,51 @@
{% 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">{{ _('Add moderator to %(community)s', community=community.display_name()) }}</div>
{{ render_form(form) }}
</div>
</div>
</div>
</div>
{% if potential_moderators != None %}
<div class="row">
<div class="col mx-auto">
<div class="card mt-5">
<div class="card-body p-6">
{% if potential_moderators -%}
<div class="card-title">{{ _('Found some people:') }}</div>
<table class="table table-striped">
{% for user in potential_moderators -%}
<tr>
<td class="align-middle">{{ render_username(user) }}</td>
<td class="text-right">
{% if community_membership(user, community) != SUBSCRIPTION_MODERATOR %}
<a class="btn btn-primary" href="{{ url_for('community.community_add_moderator', community_id=community.id, user_id=user.id) }}">{{ _('Add') }}</a>
{% else %}
{{ _('Already a moderator') }}
{% endif %}
</td>
</tr>
{% endfor -%}
</table>
</div>
{% else -%}
<div class="card-title">{{ _('No people found') }}</div>
<p>{{ _('Only local accounts can be added as moderators.') }}</p>
{% endif -%}
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}

View file

@ -23,7 +23,7 @@
</div>
<p>{{ _('See and change who moderates this community') }}</p>
<div class="col-12 text-right">
<a class="btn btn-primary" href="{{ url_for('community.community_add_moderator', community_id=community.id) }}">{{ _('Add moderator') }}</a>
<a class="btn btn-primary" href="{{ url_for('community.community_find_moderator', community_id=community.id) }}">{{ _('Add moderator') }}</a>
</div>
</div>
<table class="table table-responsive">

View file

@ -21,7 +21,7 @@
<h1 class="mt-2">{{ _('Reports') }}</h1>
</div>
<div class="col-12 col-md-2 text-right">
<!-- <a class="btn btn-primary" href="{{ url_for('community.community_add_moderator', community_id=community.id) }}">{{ _('Add moderator') }}</a> -->
<!-- <a class="btn btn-primary" href="{{ url_for('community.community_find_moderator', community_id=community.id) }}">{{ _('Add moderator') }}</a> -->
</div>
</div>
<p>{{ _('See and handle all reports made about %(community)s', community=community.display_name()) }}.</p>

View file

@ -21,7 +21,7 @@
<h1 class="mt-2">{{ _('Subscribers') }}</h1>
</div>
<div class="col-12 col-md-2 text-right">
<!-- <a class="btn btn-primary" href="{{ url_for('community.community_add_moderator', community_id=community.id) }}">{{ _('Add moderator') }}</a> -->
<!-- <a class="btn btn-primary" href="{{ url_for('community.community_find_moderator', community_id=community.id) }}">{{ _('Add moderator') }}</a> -->
</div>
</div>
<p>{{ _('See who is subscribed to %(community)s', community=community.display_name()) }}</p>

View file

@ -8,7 +8,8 @@ from flask_login import current_user
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
from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_ARTICLE, POST_TYPE_VIDEO, POST_TYPE_POLL, \
SUBSCRIPTION_MODERATOR
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, \
@ -24,7 +25,8 @@ def app_context_processor():
return os.path.getmtime('app/static/' + filename)
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)
POST_TYPE_ARTICLE=POST_TYPE_ARTICLE, POST_TYPE_VIDEO=POST_TYPE_VIDEO, POST_TYPE_POLL=POST_TYPE_POLL,
SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR)
@app.shell_context_processor