mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
admin add local user page
This commit is contained in:
parent
d1c4400967
commit
ffad1c4b6e
4 changed files with 178 additions and 2 deletions
|
@ -1,10 +1,13 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileRequired, FileAllowed
|
||||
from sqlalchemy import func
|
||||
from wtforms import StringField, PasswordField, SubmitField, HiddenField, BooleanField, TextAreaField, SelectField, \
|
||||
FileField, IntegerField
|
||||
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
|
||||
from flask_babel import _, lazy_gettext as _l
|
||||
|
||||
from app.models import Community, User
|
||||
|
||||
|
||||
class SiteProfileForm(FlaskForm):
|
||||
name = StringField(_l('Name'))
|
||||
|
@ -96,6 +99,65 @@ class EditTopicForm(FlaskForm):
|
|||
submit = SubmitField(_l('Save'))
|
||||
|
||||
|
||||
class AddUserForm(FlaskForm):
|
||||
user_name = StringField(_l('User name'), validators=[DataRequired()],
|
||||
render_kw={'autofocus': True, 'autocomplete': 'off'})
|
||||
email = StringField(_l('Email address'), validators=[Optional(), Length(max=255)])
|
||||
password = PasswordField(_l('Password'), validators=[DataRequired(), Length(min=8, max=50)],
|
||||
render_kw={'autocomplete': 'new-password'})
|
||||
password2 = PasswordField(_l('Repeat password'), validators=[DataRequired(), EqualTo('password')])
|
||||
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)])
|
||||
matrix_user_id = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)])
|
||||
profile_file = FileField(_l('Avatar image'))
|
||||
banner_file = FileField(_l('Top banner image'))
|
||||
bot = BooleanField(_l('This profile is a bot'))
|
||||
verified = BooleanField(_l('Email address is verified'))
|
||||
banned = BooleanField(_l('Banned'))
|
||||
newsletter = BooleanField(_l('Subscribe to email newsletter'))
|
||||
ignore_bots = BooleanField(_l('Hide posts by bots'))
|
||||
nsfw = BooleanField(_l('Show NSFW posts'))
|
||||
nsfl = BooleanField(_l('Show NSFL posts'))
|
||||
submit = SubmitField(_l('Save'))
|
||||
|
||||
def validate_email(self, email):
|
||||
user = User.query.filter(func.lower(User.email) == func.lower(email.data.strip())).first()
|
||||
if user is not None:
|
||||
raise ValidationError(_l('An account with this email address already exists.'))
|
||||
|
||||
def validate_user_name(self, user_name):
|
||||
if '@' in user_name.data:
|
||||
raise ValidationError(_l('User names cannot contain @.'))
|
||||
user = User.query.filter(func.lower(User.user_name) == func.lower(user_name.data.strip())).filter_by(ap_id=None).first()
|
||||
if user is not None:
|
||||
if user.deleted:
|
||||
raise ValidationError(_l('This username was used in the past and cannot be reused.'))
|
||||
else:
|
||||
raise ValidationError(_l('An account with this user name already exists.'))
|
||||
community = Community.query.filter(func.lower(Community.name) == func.lower(user_name.data.strip())).first()
|
||||
if community is not None:
|
||||
raise ValidationError(_l('A community with this name exists so it cannot be used for a user.'))
|
||||
|
||||
def validate_password(self, password):
|
||||
if not password.data:
|
||||
return
|
||||
password.data = password.data.strip()
|
||||
if password.data == 'password' or password.data == '12345678' or password.data == '1234567890':
|
||||
raise ValidationError(_l('This password is too common.'))
|
||||
|
||||
first_char = password.data[0] # the first character in the string
|
||||
|
||||
all_the_same = True
|
||||
# Compare all characters to the first character
|
||||
for char in password.data:
|
||||
if char != first_char:
|
||||
all_the_same = False
|
||||
if all_the_same:
|
||||
raise ValidationError(_l('This password is not secure.'))
|
||||
|
||||
if password.data == 'password' or password.data == '12345678' or password.data == '1234567890':
|
||||
raise ValidationError(_l('This password is too common.'))
|
||||
|
||||
|
||||
class EditUserForm(FlaskForm):
|
||||
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)])
|
||||
email = StringField(_l('Email address'), validators=[Optional(), Length(max=255)])
|
||||
|
|
|
@ -11,7 +11,7 @@ from app.activitypub.routes import process_inbox_request, process_delete_request
|
|||
from app.activitypub.signature import post_request
|
||||
from app.activitypub.util import default_context
|
||||
from app.admin.forms import FederationForm, SiteMiscForm, SiteProfileForm, EditCommunityForm, EditUserForm, \
|
||||
EditTopicForm, SendNewsletterForm
|
||||
EditTopicForm, SendNewsletterForm, AddUserForm
|
||||
from app.admin.util import unsubscribe_from_everything_then_delete, unsubscribe_from_community, send_newsletter
|
||||
from app.community.util import save_icon_file, save_banner_file
|
||||
from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \
|
||||
|
@ -581,6 +581,73 @@ def admin_user_edit(user_id):
|
|||
)
|
||||
|
||||
|
||||
@bp.route('/users/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@permission_required('administer all users')
|
||||
def admin_users_add():
|
||||
form = AddUserForm()
|
||||
user = User()
|
||||
if form.validate_on_submit():
|
||||
user.user_name = form.user_name.data
|
||||
user.set_password(form.password.data)
|
||||
user.about = form.about.data
|
||||
user.email = form.email.data
|
||||
user.about_html = markdown_to_html(form.about.data)
|
||||
user.matrix_user_id = form.matrix_user_id.data
|
||||
user.bot = form.bot.data
|
||||
profile_file = request.files['profile_file']
|
||||
if profile_file and profile_file.filename != '':
|
||||
# remove old avatar
|
||||
if user.avatar_id:
|
||||
file = File.query.get(user.avatar_id)
|
||||
file.delete_from_disk()
|
||||
user.avatar_id = None
|
||||
db.session.delete(file)
|
||||
|
||||
# add new avatar
|
||||
file = save_icon_file(profile_file, 'users')
|
||||
if file:
|
||||
user.avatar = file
|
||||
banner_file = request.files['banner_file']
|
||||
if banner_file and banner_file.filename != '':
|
||||
# remove old cover
|
||||
if user.cover_id:
|
||||
file = File.query.get(user.cover_id)
|
||||
file.delete_from_disk()
|
||||
user.cover_id = None
|
||||
db.session.delete(file)
|
||||
|
||||
# add new cover
|
||||
file = save_banner_file(banner_file, 'users')
|
||||
if file:
|
||||
user.cover = file
|
||||
user.newsletter = form.newsletter.data
|
||||
user.ignore_bots = form.ignore_bots.data
|
||||
user.show_nsfw = form.nsfw.data
|
||||
user.show_nsfl = form.nsfl.data
|
||||
|
||||
from app.activitypub.signature import RsaKeys
|
||||
user.verified = True
|
||||
user.last_seen = utcnow()
|
||||
private_key, public_key = RsaKeys.generate_keypair()
|
||||
user.private_key = private_key
|
||||
user.public_key = public_key
|
||||
user.ap_profile_id = f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}"
|
||||
user.ap_public_url = f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}"
|
||||
user.ap_inbox_url = f"https://{current_app.config['SERVER_NAME']}/u/{user.user_name}/inbox"
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
flash(_('User added'))
|
||||
return redirect(url_for('admin.admin_users', local_remote='local'))
|
||||
|
||||
return render_template('admin/add_user.html', title=_('Add user'), form=form, user=user,
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id()),
|
||||
site=g.site
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/user/<int:user_id>/delete', methods=['GET'])
|
||||
@login_required
|
||||
@permission_required('administer all users')
|
||||
|
|
46
app/templates/admin/add_user.html
Normal file
46
app/templates/admin/add_user.html
Normal file
|
@ -0,0 +1,46 @@
|
|||
{% 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 %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{% include 'admin/_nav.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-login mx-auto">
|
||||
<h3>{{ _('Add new user') }}</h3>
|
||||
<form method="post" enctype="multipart/form-data" id="add_local_user_form">
|
||||
{{ form.csrf_token() }}
|
||||
{{ render_field(form.user_name) }}
|
||||
{{ render_field(form.email) }}
|
||||
{{ render_field(form.password) }}
|
||||
{{ render_field(form.password2) }}
|
||||
{{ render_field(form.about) }}
|
||||
{{ render_field(form.matrix_user_id) }}
|
||||
{% if user.avatar_id %}
|
||||
<img class="user_icon_big rounded-circle" src="{{ user.avatar_image() }}" width="120" height="120" />
|
||||
{% endif %}
|
||||
{{ render_field(form.profile_file) }}
|
||||
<small class="field_hint">Provide a square image that looks good when small.</small>
|
||||
{% if user.cover_id %}
|
||||
<a href="{{ user.cover_image() }}"><img class="user_icon_big" src="{{ user.cover_image() }}" style="width: 300px; height: auto;" /></a>
|
||||
{% endif %}
|
||||
{{ render_field(form.banner_file) }}
|
||||
<small class="field_hint">Provide a wide image - letterbox orientation.</small>
|
||||
{{ render_field(form.bot) }}
|
||||
{{ render_field(form.verified) }}
|
||||
{{ render_field(form.banned) }}
|
||||
{{ render_field(form.newsletter) }}
|
||||
{{ render_field(form.nsfw) }}
|
||||
{{ render_field(form.nsfl) }}
|
||||
{{ render_field(form.submit) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -14,13 +14,14 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a class="btn btn-primary" href="{{ url_for('admin.admin_users_add') }}" style="float: right;">{{ _('Add local user') }}</a>
|
||||
<form method="get">
|
||||
<input type="search" name="search" value="{{ search }}">
|
||||
<input type="radio" name="local_remote" value="local" id="local_remote_local" {{ 'checked' if local_remote == 'local' }}><label for="local_remote_local"> Local</label>
|
||||
<input type="radio" name="local_remote" value="remote" id="local_remote_remote" {{ 'checked' if local_remote == 'remote' }}><label for="local_remote_remote"> Remote</label>
|
||||
<input type="submit" name="submit" value="Search" class="btn btn-primary">
|
||||
</form>
|
||||
<table class="table table-striped">
|
||||
<table class="table table-striped mt-1">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Local/Remote</th>
|
||||
|
|
Loading…
Reference in a new issue