diff --git a/app/admin/routes.py b/app/admin/routes.py index 530fa333..579dc19d 100644 --- a/app/admin/routes.py +++ b/app/admin/routes.py @@ -1147,60 +1147,30 @@ def admin_users(): page = request.args.get('page', 1, type=int) search = request.args.get('search', '') local_remote = request.args.get('local_remote', '') + sort_by = request.args.get('sort_by', 'last_seen DESC') + last_seen = request.args.get('last_seen', 0, type=int) + + sort_by_btn = request.args.get('sort_by_btn', '') + if sort_by_btn: + return redirect(url_for('admin.admin_users', page=page, search=search, local_remote=local_remote, sort_by=sort_by_btn, last_seen=last_seen)) users = User.query.filter_by(deleted=False) if local_remote == 'local': users = users.filter_by(ap_id=None) - if local_remote == 'remote': + elif local_remote == 'remote': users = users.filter(User.ap_id != None) if search: users = users.filter(User.email.ilike(f"%{search}%")) - users = users.order_by(User.user_name).paginate(page=page, per_page=1000, error_out=False) + if last_seen > 0: + users = users.filter(User.last_seen > utcnow() - timedelta(days=last_seen)) + users = users.order_by(text('"user".' + sort_by)) + users = users.paginate(page=page, per_page=1000, error_out=False) - next_url = url_for('admin.admin_users', page=users.next_num) if users.has_next else None - prev_url = url_for('admin.admin_users', page=users.prev_num) if users.has_prev and page != 1 else None + next_url = url_for('admin.admin_users', page=users.next_num, search=search, local_remote=local_remote, sort_by=sort_by, last_seen=last_seen) if users.has_next else None + prev_url = url_for('admin.admin_users', page=users.prev_num, search=search, local_remote=local_remote, sort_by=sort_by, last_seen=last_seen) if users.has_prev and page != 1 else None return render_template('admin/users.html', title=_('Users'), next_url=next_url, prev_url=prev_url, users=users, - local_remote=local_remote, search=search, - 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('/users/trash', methods=['GET']) -@login_required -@permission_required('administer all users') -def admin_users_trash(): - - page = request.args.get('page', 1, type=int) - search = request.args.get('search', '') - local_remote = request.args.get('local_remote', '') - type = request.args.get('type', 'bad_rep') - - users = User.query.filter_by(deleted=False) - if local_remote == 'local': - users = users.filter_by(ap_id=None) - if local_remote == 'remote': - users = users.filter(User.ap_id != None) - if search: - users = users.filter(User.email.ilike(f"%{search}%")) - - if type == '' or type == 'bad_rep': - users = users.filter(User.last_seen > utcnow() - timedelta(days=7)) - users = users.filter(User.reputation < -10) - users = users.order_by(User.reputation).paginate(page=page, per_page=1000, error_out=False) - elif type == 'bad_attitude': - users = users.filter(User.last_seen > utcnow() - timedelta(days=7)) - users = users.filter(User.attitude < 0.0).filter(User.reputation < -10) - users = users.order_by(User.attitude).paginate(page=page, per_page=1000, error_out=False) - - next_url = url_for('admin.admin_users_trash', page=users.next_num, search=search, local_remote=local_remote, type=type) if users.has_next else None - prev_url = url_for('admin.admin_users_trash', page=users.prev_num, search=search, local_remote=local_remote, type=type) if users.has_prev and page != 1 else None - - return render_template('admin/users_trash.html', title=_('Problematic users'), next_url=next_url, prev_url=prev_url, users=users, - local_remote=local_remote, search=search, type=type, + local_remote=local_remote, search=search, sort_by=sort_by, last_seen=last_seen, moderating_communities=moderating_communities(current_user.get_id()), joined_communities=joined_communities(current_user.get_id()), menu_topics=menu_topics(), diff --git a/app/models.py b/app/models.py index 1c8c3f26..7aae3641 100644 --- a/app/models.py +++ b/app/models.py @@ -710,6 +710,7 @@ class User(UserMixin, db.Model): cover = db.relationship('File', lazy='joined', foreign_keys=[cover_id], single_parent=True, cascade="all, delete-orphan") instance = db.relationship('Instance', lazy='joined', foreign_keys=[instance_id]) conversations = db.relationship('Conversation', lazy='dynamic', secondary=conversation_member, backref=db.backref('members', lazy='joined')) + user_notes = db.relationship('UserNote', lazy='dynamic', foreign_keys="UserNote.target_id") ap_id = db.Column(db.String(255), index=True) # e.g. username@server ap_profile_id = db.Column(db.String(255), index=True, unique=True) # e.g. https://server/u/username @@ -1022,6 +1023,7 @@ class User(UserMixin, db.Model): db.session.query(PostBookmark).filter(PostBookmark.user_id == self.id).delete() db.session.query(PostReplyBookmark).filter(PostReplyBookmark.user_id == self.id).delete() db.session.query(ModLog).filter(ModLog.user_id == self.id).delete() + db.session.query(UserNote).filter(or_(UserNote.user_id == self.id, UserNote.target_id == self.id)).delete() def purge_content(self, soft=True): files = File.query.join(Post).filter(Post.user_id == self.id).all() @@ -1077,7 +1079,14 @@ class User(UserMixin, db.Model): # returns true if the post has been read, false if not def has_read_post(self, post): return self.read_post.filter(read_posts.c.read_post_id == post.id).count() > 0 - + + @cache.memoize(timeout=500) + def get_note(self, by_user): + user_note = self.user_notes.filter(UserNote.target_id == self.id, UserNote.user_id == by_user.id).first() + if user_note: + return user_note.body + else: + return None class ActivityLog(db.Model): diff --git a/app/templates/admin/_nav.html b/app/templates/admin/_nav.html index dea6efd2..addf534b 100644 --- a/app/templates/admin/_nav.html +++ b/app/templates/admin/_nav.html @@ -6,7 +6,6 @@ {{ _('Communities') }} | {{ _('Topics') }} | {{ _('Users') }} | - {{ _('Watch') }} | {% if site.registration_mode == 'RequireApplication' %} {{ _('Registration applications') }} | {% endif %} diff --git a/app/templates/admin/users.html b/app/templates/admin/users.html index 17b0e90e..89acc5fa 100644 --- a/app/templates/admin/users.html +++ b/app/templates/admin/users.html @@ -11,44 +11,84 @@

{{ _('Users') }}

{{ _('Add local user') }} -
- - - - + +
+ + +
+
+ + +
+
+ + +
+
- - - - + - - - + + + + {% for user in users.items %} - - - - - - + + + {% endfor %} @@ -74,4 +114,4 @@
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/app/templates/admin/users_trash.html b/app/templates/admin/users_trash.html deleted file mode 100644 index 4eae558c..00000000 --- a/app/templates/admin/users_trash.html +++ /dev/null @@ -1,77 +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 %} -{% set active_child = 'admin_users_trash' %} - -{% block app_content %} -
-
-

{{ _('Users') }}

- {{ _('Add local user') }} -
- - - - - - - -
{{ _('Name') }}{{ _('Seen') }}{{ _('Attitude') }}{{ _('Reputation') }} + + {{ _('Banned') }}{{ _('Reports') }}{{ _('IP and country code') }}{{ _('Source') }} + + + + + + + + {{ _('Actions') }}
{{ render_username(user, add_domain=False) }}
{{ user.user_name }}{% if not user.is_local() %}@{{ user.ap_domain }}{% endif %}
{% if request.args.get('local_remote', '') == 'local' %} - {{ arrow.get(user.last_seen).humanize(locale=locale) }} - {% else %} - {{ user.last_seen }} - {% endif %} - {% if user.attitude != 1 %}{{ (user.attitude * 100) | round | int }}%{% endif %}{% if user.reputation %}R {{ user.reputation | round | int }}{% endif %} {{ 'Banned'|safe if user.banned }} {{ 'Banned posts'|safe if user.ban_posts }} {{ 'Banned comments'|safe if user.ban_comments }} {{ user.reports if user.reports > 0 }} {{ user.ip_address if user.ip_address }}
{{ user.ip_address_country if user.ip_address_country }}
{{ user.referrer if user.referrer }} Edit | - Delete + {% if user.attitude != 1 %}{{ (user.attitude * 100) | round | int }}%{% endif %}{% if user.reputation %}R {{ user.reputation | round | int }}{% endif %}{{ arrow.get(user.last_seen).humanize(locale=locale) }}Edit, + Delete, +
+ {% if user.banned %} + Ban, + {% else %} + Ban, + {% endif %} + Purge
- - - - - - - - - - - - {% for user in users.items %} - - - - - - - - - - - - {% endfor %} -
NameSeenAttitudeRepBannedReportsIPSourceActions
{{ render_username(user, add_domain=False) }}
- {{ user.user_name }}{% if not user.is_local() %}@{{ user.ap_domain }}{% endif %}
{% if request.args.get('local_remote', '') == 'local' %} - {{ arrow.get(user.last_seen).humanize(locale=locale) }} - {% else %} - {{ user.last_seen }} - {% endif %} - {% if user.attitude != 1 %}{{ (user.attitude * 100) | round | int }}%{% endif %}{% if user.reputation %}R {{ user.reputation | round | int }}{% endif %}{{ 'Banned'|safe if user.banned }} {{ user.reports if user.reports > 0 }} {{ user.ip_address if user.ip_address }} {{ user.referrer if user.referrer }} Edit | - Delete -
- -
- -
-
-
- {% include 'admin/_nav.html' %} -
-
-
-{% endblock %} \ No newline at end of file diff --git a/app/templates/auth/register.html b/app/templates/auth/register.html index 49673e1e..542f2567 100644 --- a/app/templates/auth/register.html +++ b/app/templates/auth/register.html @@ -19,7 +19,9 @@
{{ _('Create new account') }}
{{ render_form(form) }} {% else %} - {{ _('Registration is closed. Only admins can create accounts.') }} +

{{ _('Registration is closed. Only admins can create accounts.') }}

+

{{ _('If you would like to sign up for PieFed, choose one of the other instances in our network:') }}

+

{{ _('Try PieFed') }}

{% endif %} diff --git a/app/templates/base.html b/app/templates/base.html index a7467df1..d48d00e8 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -27,6 +27,12 @@ {% endif -%} {% endif -%} + {% if current_user.is_authenticated -%} + {% set user_note = user.get_note(current_user) %} + {% if user_note -%} + [{{ user_note | truncate(12, True) }}] + {% endif -%} + {% endif -%} {% endif -%} {% endmacro -%} @@ -217,7 +223,6 @@
  • {{ _('Communities') }}
  • {{ _('Topics') }}
  • {{ _('Users') }}
  • -
  • {{ _('Monitoring - users') }}
  • {{ _('Monitoring - content') }}
  • {{ _('Monitoring - spammy content') }}
  • {{ _('Deleted content') }}
  • diff --git a/app/templates/user/edit_note.html b/app/templates/user/edit_note.html new file mode 100644 index 00000000..5f003e0e --- /dev/null +++ b/app/templates/user/edit_note.html @@ -0,0 +1,68 @@ +{% 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 %} +
    +
    +
    +
    +
    {{ _('Edit note for "%(user_name)s"', user_name=user.display_name()) }}
    +
    + {{ _('Emoji quick access') }} +
    + + + + + + + + +
    + + + + + + + + +
    + + + + + + + + +
    + {{ render_form(form) }} +
    {{ _('This note appears next to their username. It\'s meant just for you and not displayed to anyone else.') }}
    +
    +
    +
    +
    +
    + +{% endblock %} diff --git a/app/templates/user/show_profile.html b/app/templates/user/show_profile.html index 4d0f33cf..1afbddd2 100644 --- a/app/templates/user/show_profile.html +++ b/app/templates/user/show_profile.html @@ -100,6 +100,7 @@ {% endif -%} {% endif -%}
  • {{ _('Report') }}
  • +
  • {{ _('Edit note') }}
  • {% endif %} @@ -108,6 +109,8 @@ {% if user.is_instance_admin() or (user.is_local() and user.is_admin()) %}({{ _('Admin') }}){% endif %}
    {% if user.is_admin() or user.is_staff() %}{{ _('Role permissions') }}: {% if user.is_admin() %}{{ _('Admin') }}{% endif %} {% if user.is_staff() %}{{ _('Staff') }}{% endif %}
    {% endif %} {{ _('Joined') }}: {{ arrow.get(user.created).humanize(locale=locale) }}
    + {% if current_user.is_authenticated and current_user.is_admin() %}{{ _('Referer') }}: {{ user.referrer if user.referrer }}
    {% endif %} + {% if current_user.is_authenticated and current_user.is_admin() %}{{ _('IP and country code') }}: {{ user.ip_address if user.ip_address }}{% if user.ip_address_country %} ({{ user.ip_address_country }}){% endif %}
    {% endif %} {% if current_user.is_authenticated and current_user.is_admin() and user.last_seen %}{{ _('Active') }}: {{ arrow.get(user.last_seen).humanize(locale=locale) }}
    {% endif %} {% if user.bot %} {{ _('Bot Account') }}
    @@ -116,6 +119,7 @@ {% if current_user.is_authenticated and current_user.is_admin() and user.reputation %}{{ _('Reputation') }}: {{ user.reputation | round | int }}
    {% endif %} {{ _('Posts') }}: {{ user.post_count }}
    {{ _('Comments') }}: {{ user.post_reply_count }}
    + {% if current_user.is_authenticated %}{{ _('User note') }}: {{ user.get_note(current_user) }}
    {% endif %}

    {{ user.about_html|safe }} diff --git a/app/user/forms.py b/app/user/forms.py index dc702ad2..e17407ce 100644 --- a/app/user/forms.py +++ b/app/user/forms.py @@ -147,3 +147,8 @@ class RemoteFollowForm(FlaskForm): instance_type = SelectField(_l('Instance type'), choices=type_choices, render_kw={'class': 'form-select'}) submit = SubmitField(_l('View profile on remote instance')) + + +class UserNoteForm(FlaskForm): + note = StringField(_l('User note'), validators=[Optional(), Length(max=50)]) + submit = SubmitField(_l('Save note')) diff --git a/app/user/routes.py b/app/user/routes.py index 5bc30c38..fbde47f4 100644 --- a/app/user/routes.py +++ b/app/user/routes.py @@ -16,10 +16,10 @@ from app.constants import * from app.email import send_verification_email from app.models import Post, Community, CommunityMember, User, PostReply, PostVote, Notification, utcnow, File, Site, \ Instance, Report, UserBlock, CommunityBan, CommunityJoinRequest, CommunityBlock, Filter, Domain, DomainBlock, \ - InstanceBlock, NotificationSubscription, PostBookmark, PostReplyBookmark, read_posts, Topic + InstanceBlock, NotificationSubscription, PostBookmark, PostReplyBookmark, read_posts, Topic, UserNote from app.user import bp from app.user.forms import ProfileForm, SettingsForm, DeleteAccountForm, ReportUserForm, \ - FilterForm, KeywordFilterEditForm, RemoteFollowForm, ImportExportForm + FilterForm, KeywordFilterEditForm, RemoteFollowForm, ImportExportForm, UserNoteForm from app.user.utils import purge_user_then_delete, unsubscribe_from_community from app.utils import get_setting, render_template, markdown_to_html, user_access, markdown_to_text, shorten_string, \ is_image_url, ensure_directory_exists, gibberish, file_get_contents, community_membership, user_filters_home, \ @@ -1330,3 +1330,36 @@ def user_read_posts_delete(): db.session.commit() flash(_('Reading history has been deleted')) return redirect(url_for('user.user_read_posts')) + + +@bp.route('/u//note', methods=['GET', 'POST']) +@login_required +def edit_user_note(actor): + actor = actor.strip() + if '@' in actor: + user: User = User.query.filter_by(ap_id=actor, deleted=False).first() + else: + user: User = User.query.filter_by(user_name=actor, deleted=False, ap_id=None).first() + if user is None: + abort(404) + form = UserNoteForm() + if form.validate_on_submit() and not current_user.banned: + text = form.note.data.strip() + usernote = UserNote.query.filter(UserNote.target_id == user.id, UserNote.user_id == current_user.id).first() + if usernote: + usernote.body = text + else: + usernote = UserNote(target_id=user.id, user_id=current_user.id, body=text) + db.session.add(usernote) + db.session.commit() + cache.delete_memoized(User.get_note, user, current_user) + + flash(_('Your changes have been saved.'), 'success') + goto = request.args.get('redirect') if 'redirect' in request.args else f'/u/{actor}' + return redirect(goto) + + elif request.method == 'GET': + form.note.data = user.get_note(current_user) + + return render_template('user/edit_note.html', title=_('Edit note'), form=form, user=user, + menu_topics=menu_topics(), site=g.site)