mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
user profiles and settings
This commit is contained in:
parent
43957873de
commit
8a18573974
21 changed files with 391 additions and 45 deletions
|
@ -62,6 +62,9 @@ def create_app(config_class=Config):
|
|||
from app.community import bp as community_bp
|
||||
app.register_blueprint(community_bp, url_prefix='/community')
|
||||
|
||||
from app.user import bp as user_bp
|
||||
app.register_blueprint(user_bp)
|
||||
|
||||
def get_resource_as_string(name, charset='utf-8'):
|
||||
with app.open_resource(name) as f:
|
||||
return f.read().decode(charset)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import markdown2
|
||||
import werkzeug.exceptions
|
||||
from sqlalchemy import text
|
||||
|
||||
from app import db
|
||||
from app.activitypub import bp
|
||||
|
@ -8,6 +7,7 @@ from flask import request, Response, current_app, abort, jsonify, json
|
|||
|
||||
from app.activitypub.signature import HttpSignature
|
||||
from app.community.routes import show_community
|
||||
from app.user.routes import show_profile
|
||||
from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE
|
||||
from app.models import User, Community, CommunityJoinRequest, CommunityMember, CommunityBan, ActivityPubLog, Post, \
|
||||
PostReply, Instance, PostVote, PostReplyVote, File
|
||||
|
@ -106,7 +106,7 @@ def user_profile(actor):
|
|||
actor = actor.strip()
|
||||
user = User.query.filter_by(user_name=actor, deleted=False, banned=False, ap_id=None).first()
|
||||
if user is not None:
|
||||
if 'application/ld+json' in request.headers.get('Accept', '') or request.accept_mimetypes.accept_json:
|
||||
if 'application/ld+json' in request.headers.get('Accept', ''):
|
||||
server = current_app.config['SERVER_NAME']
|
||||
actor_data = { "@context": default_context(),
|
||||
"type": "Person",
|
||||
|
@ -122,18 +122,24 @@ def user_profile(actor):
|
|||
"endpoints": {
|
||||
"sharedInbox": f"https://{server}/inbox"
|
||||
},
|
||||
"published": user.created.isoformat()
|
||||
"published": user.created.isoformat(),
|
||||
}
|
||||
if user.avatar_id is not None:
|
||||
actor_data["icon"] = {
|
||||
"type": "Image",
|
||||
"url": f"https://{server}/avatars/{user.avatar.file_path}"
|
||||
}
|
||||
if user.about:
|
||||
actor_data['source'] = {
|
||||
"content": user.about,
|
||||
"mediaType": "text/markdown"
|
||||
}
|
||||
actor_data['summary'] = allowlist_html(markdown2.markdown(user.about, safe_mode=True))
|
||||
resp = jsonify(actor_data)
|
||||
resp.content_type = 'application/activity+json'
|
||||
return resp
|
||||
else:
|
||||
return render_template('user_profile.html', user=user)
|
||||
return show_profile(user)
|
||||
|
||||
|
||||
@bp.route('/c/<actor>', methods=['GET'])
|
||||
|
|
|
@ -79,8 +79,13 @@ def show_community(community: Community):
|
|||
mod_user_ids = [mod.user_id for mod in mods]
|
||||
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()
|
||||
else:
|
||||
posts = community.posts
|
||||
|
||||
return render_template('community/community.html', community=community, title=community.title,
|
||||
is_moderator=is_moderator, is_owner=is_owner, mods=mod_list, posts=community.posts)
|
||||
is_moderator=is_moderator, is_owner=is_owner, mods=mod_list, posts=posts)
|
||||
|
||||
|
||||
@bp.route('/<actor>/subscribe', methods=['GET'])
|
||||
|
|
|
@ -25,21 +25,21 @@ def index():
|
|||
def list_communities():
|
||||
search_param = request.args.get('search', '')
|
||||
if search_param == '':
|
||||
communities = Community.query.all()
|
||||
communities = Community.query.filter_by(banned=False).all()
|
||||
else:
|
||||
query = search(select(Community), search_param, sort=True)
|
||||
communities = db.session.scalars(query).all()
|
||||
|
||||
return render_template('list_communities.html', communities=communities, search=search_param)
|
||||
return render_template('list_communities.html', communities=communities, search=search_param, title=_('Communities'))
|
||||
|
||||
|
||||
@bp.route('/communities/local', methods=['GET'])
|
||||
def list_local_communities():
|
||||
communities = Community.query.filter_by(ap_id=None).all()
|
||||
return render_template('list_communities.html', communities=communities)
|
||||
communities = Community.query.filter_by(ap_id=None, banned=False).all()
|
||||
return render_template('list_communities.html', communities=communities, title=_('Local communities'))
|
||||
|
||||
|
||||
@bp.route('/communities/subscribed', methods=['GET'])
|
||||
def list_subscribed_communities():
|
||||
communities = Community.query.join(CommunityMember).filter(CommunityMember.user_id == current_user.id).all()
|
||||
return render_template('list_communities.html', communities=communities)
|
||||
communities = Community.query.filter_by(banned=False).join(CommunityMember).filter(CommunityMember.user_id == current_user.id).all()
|
||||
return render_template('list_communities.html', communities=communities, title=_('Subscribed communities'))
|
||||
|
|
|
@ -142,6 +142,8 @@ class User(UserMixin, db.Model):
|
|||
stripe_subscription_id = db.Column(db.String(50))
|
||||
searchable = db.Column(db.Boolean, default=True)
|
||||
indexable = db.Column(db.Boolean, default=False)
|
||||
bot = db.Column(db.Boolean, default=False)
|
||||
ignore_bots = db.Column(db.Boolean, default=False)
|
||||
|
||||
avatar = db.relationship('File', foreign_keys=[avatar_id], single_parent=True, cascade="all, delete-orphan")
|
||||
cover = db.relationship('File', foreign_keys=[cover_id], single_parent=True, cascade="all, delete-orphan")
|
||||
|
@ -180,6 +182,22 @@ class User(UserMixin, db.Model):
|
|||
return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(
|
||||
digest, size)
|
||||
|
||||
def avatar_image(self) -> str:
|
||||
if self.avatar_id is not None:
|
||||
if self.avatar.file_path is not None:
|
||||
return self.avatar.file_path
|
||||
if self.avatar.source_url is not None:
|
||||
return self.avatar.source_url
|
||||
return ''
|
||||
|
||||
def cover_image(self) -> str:
|
||||
if self.cover_id is not None:
|
||||
if self.cover.file_path is not None:
|
||||
return self.cover.file_path
|
||||
if self.cover.source_url is not None:
|
||||
return self.cover.source_url
|
||||
return ''
|
||||
|
||||
def get_reset_password_token(self, expires_in=600):
|
||||
return jwt.encode(
|
||||
{'reset_password': self.id, 'exp': time() + expires_in},
|
||||
|
@ -264,6 +282,7 @@ class Post(db.Model):
|
|||
nsfl = db.Column(db.Boolean, default=False)
|
||||
sticky = db.Column(db.Boolean, default=False)
|
||||
indexable = db.Column(db.Boolean, default=False)
|
||||
from_bot = db.Column(db.Boolean, default=False)
|
||||
created_at = db.Column(db.DateTime, index=True, default=datetime.utcnow) # this is when the content arrived here
|
||||
posted_at = db.Column(db.DateTime, index=True, default=datetime.utcnow) # this is when the original server created it
|
||||
last_active = db.Column(db.DateTime, index=True, default=datetime.utcnow)
|
||||
|
@ -305,6 +324,7 @@ class PostReply(db.Model):
|
|||
created_at = db.Column(db.DateTime, index=True, default=datetime.utcnow)
|
||||
posted_at = db.Column(db.DateTime, index=True, default=datetime.utcnow)
|
||||
ip = db.Column(db.String(50))
|
||||
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)
|
||||
|
|
|
@ -144,8 +144,10 @@ a.no-underline {
|
|||
}
|
||||
}
|
||||
|
||||
$small_text: 87%;
|
||||
|
||||
.small, small {
|
||||
font-size: 87%;
|
||||
font-size: $small_text;
|
||||
}
|
||||
|
||||
fieldset legend {
|
||||
|
|
|
@ -325,7 +325,7 @@ fieldset legend {
|
|||
font-size: 120%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.post_list .post_teaser h3 a {
|
||||
.post_list .post_teaser .meta_row a, .post_list .post_teaser .main_row a, .post_list .post_teaser .utilities_row a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,9 @@ nav, etc which are used site-wide */
|
|||
h3 {
|
||||
font-size: 120%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.meta_row, .main_row, .utilities_row {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
{% else %}
|
||||
<li class="nav-item"><a class="nav-link" href="/">{{ _('Home') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/communities">{{ _('Communities') }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/u/{{ current_user.user_name }}">{{ current_user.user_name }}</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/auth/logout">{{ _('Log out') }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
|
3
app/templates/community/_post_reply_teaser.html
Normal file
3
app/templates/community/_post_reply_teaser.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="post_reply_teaser">
|
||||
{{ post_reply.body_html }}
|
||||
</div>
|
31
app/templates/community/_post_teaser.html
Normal file
31
app/templates/community/_post_teaser.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<div class="post_teaser">
|
||||
<div class="row meta_row small">
|
||||
<div class="col"><a href="{{ url_for('activitypub.user_profile', actor=post.author.user_name) }}">{{ post.author.user_name }}</a> · {{ moment(post.posted_at).fromNow() }}</div>
|
||||
</div>
|
||||
<div class="row main_row">
|
||||
<div class="col{% if post.image_id %}-8{% endif %}">
|
||||
<h3>
|
||||
<a href="{{ url_for('community.show_post', post_id=post.id) }}">{{ post.title }}</a>
|
||||
{% if post.type == post_type_link %}
|
||||
<span class="domain_link">(<a href="/d/{{ post.domain_id }}">domain</a>)</span>
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
{% if post.image_id %}
|
||||
<div class="col-4">
|
||||
<img src="{{ post.image.source_url}}" alt="{{ post.image.alt_text }}"
|
||||
width="100" />
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="row utilities_row">
|
||||
<div class="col-4">
|
||||
up {{ post.score }} down
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a href="{{ url_for('community.show_post', post_id=post.id, _anchor='replies') }}">{{post.reply_count}}</a> additional tools
|
||||
</div>
|
||||
<div class="col-2">...</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -37,37 +37,7 @@
|
|||
{% endif %}
|
||||
<div class="post_list">
|
||||
{% for post in posts %}
|
||||
<div class="post_teaser">
|
||||
<div class="row">
|
||||
<div class="col">{{ post.author.user_name }} · {{ moment(post.posted_at).fromNow() }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col{% if post.image_id %}-8{% endif %}">
|
||||
<h3>
|
||||
<a href="{{ url_for('community.show_post', post_id=post.id) }}">{{ post.title }}</a>
|
||||
{% if post.type == post_type_link %}
|
||||
<span class="domain_link">(<a href="/d/{{ post.domain_id }}">domain</a>)</span>
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
{% if post.image_id %}
|
||||
<div class="col-4">
|
||||
<img src="{{ post.image.source_url}}" alt="{{ post.image.alt_text }}"
|
||||
width="100" />
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
up {{ post.score }} down
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a href="{{ url_for('community.show_post', post_id=post.id, _anchor='replies') }}">{{post.reply_count}}</a> additional tools
|
||||
</div>
|
||||
<div class="col-2">...</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% include 'community/_post_teaser.html' %}
|
||||
{% else %}
|
||||
<p>{{ _('No posts in this community yet.') }}</p>
|
||||
{% endfor %}
|
||||
|
|
|
@ -52,7 +52,9 @@
|
|||
{% endif %}
|
||||
</small></p>
|
||||
{% endif %}
|
||||
<p><small>submitted {{ moment(post.posted_at).fromNow() }} by {{ post.author.user_name }}</small></p>
|
||||
<p class="small">submitted {{ moment(post.posted_at).fromNow() }} by
|
||||
<a href="{{ url_for('activitypub.user_profile', actor=post.author.user_name) }}">{{ post.author.user_name }}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
|
20
app/templates/user/edit_profile.html
Normal file
20
app/templates/user/edit_profile.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'bootstrap/form.html' import render_form %}
|
||||
|
||||
{% block app_content %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<nav aria-label="breadcrumb" id="breadcrumb_nav" title="Navigation">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">{{ _('Home') }}</a></li>
|
||||
<li class="breadcrumb-item"><a href="/u/{{ user.user_name }}">{{ user.user_name }}</a></li>
|
||||
<li class="breadcrumb-item active">{{ _('Edit profile') }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h1 class="mt-2">{{ _('Edit profile') }}</h1>
|
||||
<form method='post'>
|
||||
{{ render_form(form) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
20
app/templates/user/edit_settings.html
Normal file
20
app/templates/user/edit_settings.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'bootstrap/form.html' import render_form %}
|
||||
|
||||
{% block app_content %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<nav aria-label="breadcrumb" id="breadcrumb_nav" title="Navigation">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">{{ _('Home') }}</a></li>
|
||||
<li class="breadcrumb-item"><a href="/u/{{ user.user_name }}">{{ user.user_name }}</a></li>
|
||||
<li class="breadcrumb-item active">{{ _('Change settings') }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h1 class="mt-2">{{ _('Change settings') }}</h1>
|
||||
<form method='post'>
|
||||
{{ render_form(form) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
98
app/templates/user/show_profile.html
Normal file
98
app/templates/user/show_profile.html
Normal file
|
@ -0,0 +1,98 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'bootstrap/form.html' import render_form %}
|
||||
|
||||
{% block app_content %}
|
||||
<div class="row">
|
||||
<div class="col-8 position-relative">
|
||||
{% if user.cover_image() != '' %}
|
||||
<div class="community_header" style="height: 240px; background-image: url({{ user.cover_image() }});">
|
||||
<nav aria-label="breadcrumb" id="breadcrumb_nav" title="Navigation">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">{{ _('Home') }}</a></li>
|
||||
<li class="breadcrumb-item"><a href="/users">{{ _('People') }}</a></li>
|
||||
<li class="breadcrumb-item active">{{ user.user_name|shorten }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
<img class="community_icon_big bump_up rounded-circle" src="{{ user.avatar_image() }}" />
|
||||
<h1 class="mt-2">{{ user.user_name }}</h1>
|
||||
{% elif user.avatar_image() != '' %}
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<img class="community_icon_big rounded-circle" src="{{ user.avatar_image() }}" />
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<h1 class="mt-3">{{ user.user_name }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<nav aria-label="breadcrumb" id="breadcrumb_nav" title="Navigation">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">{{ _('Home') }}</a></li>
|
||||
<li class="breadcrumb-item"><a href="/users">{{ _('People') }}</a></li>
|
||||
<li class="breadcrumb-item active">{{ user.user_name|shorten }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h1 class="mt-2">{{ user.user_name }}</h1>
|
||||
{{ user.about_html|safe }}
|
||||
{% endif %}
|
||||
|
||||
{% if len(posts) > 0 %}
|
||||
<h2 class="mt-4">Posts</h2>
|
||||
<div class="post_list">
|
||||
{% for post in posts %}
|
||||
{% include 'community/_post_teaser.html' %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if len(post_replies) > 0 %}
|
||||
<h2 class="mt-4">Comments</h2>
|
||||
<div class="post_list">
|
||||
{% for post_reply in post_replies %}
|
||||
{% include 'community/_post_reply_teaser.html' %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
{% if current_user.id == user.id %}
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h2>{{ _('Manage') }}</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<a class="w-100 btn btn-primary" href="/u/{{ user.user_name }}/profile">{{ _('Profile') }}</a>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a class="w-100 btn btn-primary" href="/u/{{ user.user_name }}/settings">{{ _('Settings') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if len(moderates) > 0 %}
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<h2>{{ _('Moderates') }}</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ol>
|
||||
{% for community in moderates %}
|
||||
<li>
|
||||
<a href="/c/{{ community.link() }}">
|
||||
<img src="{{ community.icon_image() }}" class="community_icon rounded-circle" loading="lazy" />
|
||||
{{ community.display_name() }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
5
app/user/__init__.py
Normal file
5
app/user/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from flask import Blueprint
|
||||
|
||||
bp = Blueprint('user', __name__)
|
||||
|
||||
from app.user import routes
|
30
app/user/forms.py
Normal file
30
app/user/forms.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from flask import session
|
||||
from flask_login import current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, PasswordField, BooleanField, EmailField, TextAreaField, FileField
|
||||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
|
||||
from flask_babel import _, lazy_gettext as _l
|
||||
|
||||
|
||||
class ProfileForm(FlaskForm):
|
||||
email = EmailField(_l('Email address'), validators=[Email(), DataRequired(), Length(min=5, max=255)])
|
||||
password_field = PasswordField(_l('Set new password'), validators=[Optional(), Length(min=1, max=50)],
|
||||
render_kw={"autocomplete": 'Off'})
|
||||
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)])
|
||||
submit = SubmitField(_l('Save profile'))
|
||||
|
||||
def validate_email(self, field):
|
||||
if current_user.another_account_using_email(field.data):
|
||||
raise ValidationError(_l('That email address is already in use by another account'))
|
||||
|
||||
|
||||
class SettingsForm(FlaskForm):
|
||||
newsletter = BooleanField(_l('Subscribe to email newsletter'))
|
||||
bot = BooleanField(_l('This profile is a bot'))
|
||||
ignore_bots = BooleanField(_l('Hide posts by bots'))
|
||||
nsfw = BooleanField(_l('Show NSFW posts'))
|
||||
nsfl = BooleanField(_l('Show NSFL posts'))
|
||||
searchable = BooleanField(_l('Show profile in fediverse searches'))
|
||||
indexable = BooleanField(_l('Allow search engines to index this profile'))
|
||||
manually_approves_followers = BooleanField(_l('Manually approve followers'))
|
||||
submit = SubmitField(_l('Save settings'))
|
82
app/user/routes.py
Normal file
82
app/user/routes.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
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 _
|
||||
|
||||
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 sqlalchemy import desc, or_
|
||||
|
||||
|
||||
def show_profile(user):
|
||||
posts = Post.query.filter_by(user_id=user.id).order_by(desc(Post.posted_at)).all()
|
||||
moderates = Community.query.filter_by(banned=False).join(CommunityMember).filter(or_(CommunityMember.is_moderator, CommunityMember.is_owner))
|
||||
if user.id != current_user.id:
|
||||
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))
|
||||
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))
|
||||
|
||||
|
||||
@bp.route('/u/<actor>/profile', methods=['GET', 'POST'])
|
||||
def edit_profile(actor):
|
||||
actor = actor.strip()
|
||||
user = User.query.filter_by(user_name=actor, deleted=False, banned=False, ap_id=None).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
form = ProfileForm()
|
||||
if form.validate_on_submit():
|
||||
current_user.email = form.email.data
|
||||
if form.password_field.data.strip() != '':
|
||||
current_user.set_password(form.password_field.data)
|
||||
current_user.about = form.about.data
|
||||
db.session.commit()
|
||||
|
||||
flash(_('Your changes have been saved.'), 'success')
|
||||
return redirect(url_for('user.edit_profile', actor=actor))
|
||||
elif request.method == 'GET':
|
||||
form.email.data = current_user.email
|
||||
form.about.data = current_user.about
|
||||
form.password_field.data = ''
|
||||
|
||||
return render_template('user/edit_profile.html', title=_('Edit profile'), form=form, user=current_user)
|
||||
|
||||
|
||||
@bp.route('/u/<actor>/settings', methods=['GET', 'POST'])
|
||||
def change_settings(actor):
|
||||
actor = actor.strip()
|
||||
user = User.query.filter_by(user_name=actor, deleted=False, banned=False, ap_id=None).first()
|
||||
if user is None:
|
||||
abort(404)
|
||||
form = SettingsForm()
|
||||
if form.validate_on_submit():
|
||||
current_user.newsletter = form.newsletter.data
|
||||
current_user.bot = form.bot.data
|
||||
current_user.ignore_bots = form.ignore_bots.data
|
||||
current_user.show_nsfw = form.nsfw.data
|
||||
current_user.show_nsfl = form.nsfl.data
|
||||
current_user.searchable = form.searchable.data
|
||||
current_user.indexable = form.indexable.data
|
||||
current_user.ap_manually_approves_followers = form.manually_approves_followers.data
|
||||
db.session.commit()
|
||||
|
||||
flash(_('Your changes have been saved.'), 'success')
|
||||
return redirect(url_for('user.change_settings', actor=actor))
|
||||
elif request.method == 'GET':
|
||||
form.newsletter.data = current_user.newsletter
|
||||
form.bot.data = current_user.bot
|
||||
form.ignore_bots.data = current_user.ignore_bots
|
||||
form.nsfw.data = current_user.show_nsfw
|
||||
form.nsfl.data = current_user.show_nsfl
|
||||
form.searchable.data = current_user.searchable
|
||||
form.indexable.data = current_user.indexable
|
||||
form.manually_approves_followers.data = current_user.ap_manually_approves_followers
|
||||
|
||||
return render_template('user/edit_settings.html', title=_('Edit profile'), form=form, user=current_user)
|
46
migrations/versions/8c5cc19e0670_bots.py
Normal file
46
migrations/versions/8c5cc19e0670_bots.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
"""bots
|
||||
|
||||
Revision ID: 8c5cc19e0670
|
||||
Revises: b1d3fc38b1f7
|
||||
Create Date: 2023-10-07 18:13:32.600126
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '8c5cc19e0670'
|
||||
down_revision = 'b1d3fc38b1f7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('post', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('from_bot', sa.Boolean(), nullable=True))
|
||||
|
||||
with op.batch_alter_table('post_reply', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('from_bot', sa.Boolean(), nullable=True))
|
||||
|
||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('bot', sa.Boolean(), nullable=True))
|
||||
batch_op.add_column(sa.Column('ignore_bots', sa.Boolean(), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||
batch_op.drop_column('ignore_bots')
|
||||
batch_op.drop_column('bot')
|
||||
|
||||
with op.batch_alter_table('post_reply', schema=None) as batch_op:
|
||||
batch_op.drop_column('from_bot')
|
||||
|
||||
with op.batch_alter_table('post', schema=None) as batch_op:
|
||||
batch_op.drop_column('from_bot')
|
||||
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in a new issue