2023-11-03 21:59:48 +13:00
|
|
|
from flask_wtf import FlaskForm
|
2023-12-17 00:12:49 +13:00
|
|
|
from flask_wtf.file import FileRequired, FileAllowed
|
2024-02-28 05:04:07 +13:00
|
|
|
from sqlalchemy import func
|
2024-04-16 12:10:24 +02:00
|
|
|
from wtforms import StringField, PasswordField, SubmitField, EmailField, HiddenField, BooleanField, TextAreaField, SelectField, \
|
2023-12-17 00:12:49 +13:00
|
|
|
FileField, IntegerField
|
2024-05-08 21:07:22 +12:00
|
|
|
from wtforms.fields.choices import SelectMultipleField
|
2024-01-01 14:49:15 +13:00
|
|
|
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional
|
2023-11-03 21:59:48 +13:00
|
|
|
from flask_babel import _, lazy_gettext as _l
|
|
|
|
|
2024-02-28 05:04:07 +13:00
|
|
|
from app.models import Community, User
|
|
|
|
|
2023-11-03 21:59:48 +13:00
|
|
|
|
2023-12-17 00:12:49 +13:00
|
|
|
class SiteProfileForm(FlaskForm):
|
2024-04-22 19:23:26 +02:00
|
|
|
name = StringField(_l('Site Name'))
|
2023-12-17 00:12:49 +13:00
|
|
|
description = StringField(_l('Tagline'))
|
2024-04-30 21:29:49 +12:00
|
|
|
icon = FileField(_l('Icon'), validators=[
|
2023-12-17 00:12:49 +13:00
|
|
|
FileAllowed(['jpg', 'jpeg', 'png', 'webp'], 'Images only!')
|
2024-06-16 14:03:59 +08:00
|
|
|
], render_kw={'accept': 'image/*'})
|
2023-12-17 00:12:49 +13:00
|
|
|
sidebar = TextAreaField(_l('Sidebar'))
|
2024-04-22 19:23:26 +02:00
|
|
|
about = TextAreaField(_l('About'))
|
2024-06-10 19:07:15 +08:00
|
|
|
announcement = TextAreaField(_l('Announcement at top of home page'))
|
2023-12-17 00:12:49 +13:00
|
|
|
legal_information = TextAreaField(_l('Legal information'))
|
2024-04-22 19:23:26 +02:00
|
|
|
contact_email = EmailField(_l('General instance contact email address'), validators=[DataRequired(), Length(min=5, max=255)])
|
2023-12-17 00:12:49 +13:00
|
|
|
submit = SubmitField(_l('Save'))
|
|
|
|
|
|
|
|
|
|
|
|
class SiteMiscForm(FlaskForm):
|
|
|
|
enable_downvotes = BooleanField(_l('Enable downvotes'))
|
|
|
|
allow_local_image_posts = BooleanField(_l('Allow local image posts'))
|
|
|
|
remote_image_cache_days = IntegerField(_l('Days to cache images from remote instances for'))
|
2023-12-31 12:09:20 +13:00
|
|
|
enable_nsfw = BooleanField(_l('Allow NSFW communities'))
|
2023-12-17 00:12:49 +13:00
|
|
|
enable_nsfl = BooleanField(_l('Allow NSFL communities and posts'))
|
|
|
|
community_creation_admin_only = BooleanField(_l('Only admins can create new local communities'))
|
|
|
|
reports_email_admins = BooleanField(_l('Notify admins about reports, not just moderators'))
|
|
|
|
types = [('Open', _l('Open')), ('RequireApplication', _l('Require application')), ('Closed', _l('Closed'))]
|
2024-05-17 21:27:45 +12:00
|
|
|
registration_mode = SelectField(_l('Registration mode'), choices=types, default=1, coerce=str, render_kw={'class': 'form-select'})
|
2023-12-17 00:12:49 +13:00
|
|
|
application_question = TextAreaField(_l('Question to ask people applying for an account'))
|
2024-03-22 14:35:51 +13:00
|
|
|
auto_decline_referrers = TextAreaField(_l('Block registrations from these referrers (one per line)'))
|
2024-01-13 11:12:31 +13:00
|
|
|
log_activitypub_json = BooleanField(_l('Log ActivityPub JSON for debugging'))
|
2024-05-17 21:27:45 +12:00
|
|
|
default_theme = SelectField(_l('Default theme'), coerce=str, render_kw={'class': 'form-select'})
|
2023-12-17 00:12:49 +13:00
|
|
|
submit = SubmitField(_l('Save'))
|
|
|
|
|
|
|
|
|
|
|
|
class FederationForm(FlaskForm):
|
2023-11-03 21:59:48 +13:00
|
|
|
use_allowlist = BooleanField(_l('Allowlist instead of blocklist'))
|
|
|
|
allowlist = TextAreaField(_l('Allow federation with these instances'))
|
|
|
|
use_blocklist = BooleanField(_l('Blocklist instead of allowlist'))
|
|
|
|
blocklist = TextAreaField(_l('Deny federation with these instances'))
|
2024-03-22 12:22:19 +13:00
|
|
|
blocked_phrases = TextAreaField(_l('Discard all posts and comments with these phrases (one per line)'))
|
2024-05-16 15:44:42 +12:00
|
|
|
blocked_actors = TextAreaField(_l('Discard all posts and comments by users with these words in their name (one per line)'))
|
2023-11-03 21:59:48 +13:00
|
|
|
submit = SubmitField(_l('Save'))
|
2023-12-31 12:09:20 +13:00
|
|
|
|
|
|
|
|
|
|
|
class EditCommunityForm(FlaskForm):
|
|
|
|
title = StringField(_l('Title'), validators=[DataRequired()])
|
|
|
|
url = StringField(_l('Url'), validators=[DataRequired()])
|
|
|
|
description = TextAreaField(_l('Description'))
|
2024-04-30 21:29:49 +12:00
|
|
|
icon_file = FileField(_l('Icon image'))
|
|
|
|
banner_file = FileField(_l('Banner image'))
|
2023-12-31 12:09:20 +13:00
|
|
|
rules = TextAreaField(_l('Rules'))
|
2024-01-21 15:44:13 +13:00
|
|
|
nsfw = BooleanField(_l('Porn community'))
|
2024-03-13 16:40:20 +13:00
|
|
|
banned = BooleanField(_l('Banned - no new posts accepted'))
|
2024-01-21 15:44:13 +13:00
|
|
|
local_only = BooleanField(_l('Only accept posts from current instance'))
|
|
|
|
restricted_to_mods = BooleanField(_l('Only moderators can post'))
|
|
|
|
new_mods_wanted = BooleanField(_l('New moderators wanted'))
|
|
|
|
show_home = BooleanField(_l('Posts show on home page'))
|
|
|
|
show_popular = BooleanField(_l('Posts can be popular'))
|
|
|
|
show_all = BooleanField(_l('Posts show in All list'))
|
|
|
|
low_quality = BooleanField(_l("Low quality / toxic - upvotes in here don't add to reputation"))
|
2023-12-31 12:09:20 +13:00
|
|
|
options = [(-1, _l('Forever')),
|
|
|
|
(7, _l('1 week')),
|
|
|
|
(14, _l('2 weeks')),
|
|
|
|
(28, _l('1 month')),
|
|
|
|
(56, _l('2 months')),
|
|
|
|
(84, _l('3 months')),
|
|
|
|
(168, _l('6 months')),
|
|
|
|
(365, _l('1 year')),
|
|
|
|
(730, _l('2 years')),
|
|
|
|
(1825, _l('5 years')),
|
|
|
|
(3650, _l('10 years')),
|
|
|
|
]
|
2024-05-17 21:27:45 +12:00
|
|
|
content_retention = SelectField(_l('Retain content'), choices=options, default=1, coerce=int, render_kw={'class': 'form-select'})
|
|
|
|
topic = SelectField(_l('Topic'), coerce=int, validators=[Optional()], render_kw={'class': 'form-select'})
|
2024-01-21 15:44:13 +13:00
|
|
|
layouts = [('', _l('List')),
|
|
|
|
('masonry', _l('Masonry')),
|
|
|
|
('masonry_wide', _l('Wide masonry'))]
|
2024-05-17 21:27:45 +12:00
|
|
|
default_layout = SelectField(_l('Layout'), coerce=str, choices=layouts, validators=[Optional()], render_kw={'class': 'form-select'})
|
2024-04-30 21:29:49 +12:00
|
|
|
posting_warning = StringField(_l('Posting warning'), validators=[Optional(), Length(min=3, max=512)])
|
2024-05-08 21:07:22 +12:00
|
|
|
languages = SelectMultipleField(_l('Languages'), coerce=int, validators=[Optional()], render_kw={'class': 'form-select'})
|
|
|
|
ignore_remote_language = BooleanField(_l('Override remote language setting'))
|
2023-12-31 12:09:20 +13:00
|
|
|
submit = SubmitField(_l('Save'))
|
|
|
|
|
|
|
|
def validate(self, extra_validators=None):
|
|
|
|
if not super().validate():
|
|
|
|
return False
|
|
|
|
if self.url.data.strip() == '':
|
2024-04-30 21:29:49 +12:00
|
|
|
self.url.errors.append(_l('Url is required.'))
|
2023-12-31 12:09:20 +13:00
|
|
|
return False
|
|
|
|
else:
|
|
|
|
if '-' in self.url.data.strip():
|
2024-04-30 21:29:49 +12:00
|
|
|
self.url.errors.append(_l('- cannot be in Url. Use _ instead?'))
|
2023-12-31 12:09:20 +13:00
|
|
|
return False
|
2024-01-01 14:49:15 +13:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2024-01-04 16:00:19 +13:00
|
|
|
class EditTopicForm(FlaskForm):
|
2024-06-27 14:34:32 +02:00
|
|
|
name = StringField(_l('Name'), validators=[DataRequired()], render_kw={'title': _l('Human readable name for the topic.')})
|
|
|
|
machine_name = StringField(_l('Slug'), validators=[DataRequired()], render_kw={'title': _l('A short and unique identifier that becomes part of the URL.')})
|
2024-05-17 21:27:45 +12:00
|
|
|
parent_id = SelectField(_l('Parent topic'), coerce=int, validators=[Optional()], render_kw={'class': 'form-select'})
|
2024-01-04 16:00:19 +13:00
|
|
|
submit = SubmitField(_l('Save'))
|
|
|
|
|
|
|
|
|
2024-02-28 05:04:07 +13:00
|
|
|
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'))
|
2024-02-28 10:27:31 +13:00
|
|
|
role_options = [(2, _l('User')),
|
|
|
|
(3, _l('Staff')),
|
|
|
|
(4, _l('Admin')),
|
|
|
|
]
|
2024-05-17 21:27:45 +12:00
|
|
|
role = SelectField(_l('Role'), choices=role_options, default=2, coerce=int, render_kw={'class': 'form-select'})
|
2024-02-28 05:04:07 +13:00
|
|
|
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.'))
|
|
|
|
|
|
|
|
|
2024-01-01 14:49:15 +13:00
|
|
|
class EditUserForm(FlaskForm):
|
|
|
|
bot = BooleanField(_l('This profile is a bot'))
|
2024-02-19 15:01:53 +13:00
|
|
|
verified = BooleanField(_l('Email address is verified'))
|
|
|
|
banned = BooleanField(_l('Banned'))
|
2024-02-28 10:27:31 +13:00
|
|
|
role_options = [(2, _l('User')),
|
|
|
|
(3, _l('Staff')),
|
|
|
|
(4, _l('Admin')),
|
|
|
|
]
|
2024-05-17 21:27:45 +12:00
|
|
|
role = SelectField(_l('Role'), choices=role_options, default=2, coerce=int, render_kw={'class': 'form-select'})
|
2024-03-23 07:08:28 +13:00
|
|
|
remove_avatar = BooleanField(_l('Remove avatar'))
|
|
|
|
remove_banner = BooleanField(_l('Remove banner'))
|
2024-01-01 14:49:15 +13:00
|
|
|
submit = SubmitField(_l('Save'))
|
2024-02-24 20:01:38 +13:00
|
|
|
|
|
|
|
|
|
|
|
class SendNewsletterForm(FlaskForm):
|
|
|
|
subject = StringField(_l('Subject'), validators=[DataRequired()])
|
|
|
|
body_text = TextAreaField(_l('Body (text)'), render_kw={"rows": 10}, validators=[DataRequired()])
|
|
|
|
body_html = TextAreaField(_l('Body (html)'), render_kw={"rows": 20}, validators=[DataRequired()])
|
|
|
|
test = BooleanField(_l('Test mode'), render_kw={'checked': True})
|
|
|
|
submit = SubmitField(_l('Send newsletter'))
|