add user exile to Mod Tools
This commit is contained in:
parent
2214863496
commit
f97e613f7a
6 changed files with 117 additions and 8 deletions
|
|
@ -8,6 +8,7 @@
|
||||||
- Added user strikes: a strike logs the content of a removed message for future use
|
- Added user strikes: a strike logs the content of a removed message for future use
|
||||||
- Posts may now be deleted by author. If it has comments, comments are not spared
|
- Posts may now be deleted by author. If it has comments, comments are not spared
|
||||||
- Moderators (and admins) have now access to mod tools
|
- Moderators (and admins) have now access to mod tools
|
||||||
|
+ Allowed operations: change display name, description, restriction status, and exile (guild-local ban) members
|
||||||
- Implemented guild subscriptions
|
- Implemented guild subscriptions
|
||||||
- Added ✨color themes✨
|
- Added ✨color themes✨
|
||||||
- Users can now set their display name, biography and color theme in `/settings`
|
- Users can now set their display name, biography and color theme in `/settings`
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from suou.configparse import ConfigOptions, ConfigValue
|
||||||
|
|
||||||
from .colors import color_themes, theme_classes
|
from .colors import color_themes, theme_classes
|
||||||
|
|
||||||
__version__ = '0.4.0-dev27'
|
__version__ = '0.4.0-dev28'
|
||||||
|
|
||||||
APP_BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
APP_BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from functools import partial
|
||||||
from operator import or_
|
from operator import or_
|
||||||
import re
|
import re
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, text, \
|
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, insert, text, \
|
||||||
CheckConstraint, Date, DateTime, Boolean, func, BigInteger, \
|
CheckConstraint, Date, DateTime, Boolean, func, BigInteger, \
|
||||||
SmallInteger, select, update, Table
|
SmallInteger, select, update, Table
|
||||||
from sqlalchemy.orm import Relationship, relationship
|
from sqlalchemy.orm import Relationship, relationship
|
||||||
|
|
@ -78,7 +78,7 @@ ILLEGAL_USERNAMES = (
|
||||||
'pedophile', 'lolicon', 'giphy', 'tenor', 'csam', 'cp', 'pedobear', 'lolita',
|
'pedophile', 'lolicon', 'giphy', 'tenor', 'csam', 'cp', 'pedobear', 'lolita',
|
||||||
'loli', 'kkk', 'pnf', 'adl', 'cop', 'tranny', 'google', 'trustandsafety', 'safety', 'ice',
|
'loli', 'kkk', 'pnf', 'adl', 'cop', 'tranny', 'google', 'trustandsafety', 'safety', 'ice',
|
||||||
## VVVVIP
|
## VVVVIP
|
||||||
'potus', 'realdonaldtrump', 'elonmusk', 'teddysphotos', 'mrbeast', 'jkrowling'
|
'potus', 'realdonaldtrump', 'elonmusk', 'teddysphotos', 'mrbeast', 'jkrowling', 'pewdiepie'
|
||||||
)
|
)
|
||||||
|
|
||||||
def username_is_legal(username: str) -> bool:
|
def username_is_legal(username: str) -> bool:
|
||||||
|
|
@ -308,6 +308,8 @@ class User(Base):
|
||||||
|
|
||||||
## END User
|
## END User
|
||||||
|
|
||||||
|
ModeratorInfo = namedtuple('ModeratorInfo', 'user is_owner')
|
||||||
|
|
||||||
class Guild(Base):
|
class Guild(Base):
|
||||||
__tablename__ = 'freak_topic'
|
__tablename__ = 'freak_topic'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
|
|
@ -340,6 +342,7 @@ class Guild(Base):
|
||||||
return db.session.execute(select(func.count('*')).select_from(Member).where(Member.guild == self, Member.is_subscribed == True)).scalar()
|
return db.session.execute(select(func.count('*')).select_from(Member).where(Member.guild == self, Member.is_subscribed == True)).scalar()
|
||||||
|
|
||||||
# utilities
|
# utilities
|
||||||
|
owner = relationship(User, foreign_keys=owner_id)
|
||||||
posts = relationship('Post', back_populates='guild')
|
posts = relationship('Post', back_populates='guild')
|
||||||
|
|
||||||
def has_subscriber(self, other: User) -> bool:
|
def has_subscriber(self, other: User) -> bool:
|
||||||
|
|
@ -347,6 +350,35 @@ class Guild(Base):
|
||||||
return False
|
return False
|
||||||
return bool(db.session.execute(select(Member).where(Member.user_id == other.id, Member.guild_id == self.id, Member.is_subscribed == True)).scalar())
|
return bool(db.session.execute(select(Member).where(Member.user_id == other.id, Member.guild_id == self.id, Member.is_subscribed == True)).scalar())
|
||||||
|
|
||||||
|
def has_exiled(self, other: User) -> bool:
|
||||||
|
if other is None or not other.is_authenticated:
|
||||||
|
return False
|
||||||
|
u = db.session.execute(select(Member).where(Member.user_id == other.id, Member.guild_id == self.id)).scalar()
|
||||||
|
return u.is_banned if u else False
|
||||||
|
|
||||||
|
def moderators(self):
|
||||||
|
if self.owner:
|
||||||
|
yield ModeratorInfo(self.owner, True)
|
||||||
|
for mem in db.session.execute(select(Member).where(Member.guild_id == self.id, Member.is_moderator == True)).scalars():
|
||||||
|
if mem.user != self.owner and not mem.user.is_banned:
|
||||||
|
yield ModeratorInfo(mem.user, False)
|
||||||
|
|
||||||
|
def update_member(self, u: User | Member, /, **values):
|
||||||
|
if isinstance(u, User):
|
||||||
|
m = db.session.execute(select(Member).where(Member.user_id == u.id, Member.guild_id == self.id)).scalar()
|
||||||
|
if m is None:
|
||||||
|
return db.session.execute(insert(Member).values(
|
||||||
|
guild_id = self.id,
|
||||||
|
user_id = u.id,
|
||||||
|
**values
|
||||||
|
).returning(Member)).scalar()
|
||||||
|
else:
|
||||||
|
m = u
|
||||||
|
if len(values):
|
||||||
|
db.session.execute(update(Member).where(Member.user_id == u.id, Member.guild_id == self.id).values(**values))
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
Topic = deprecated('renamed to Guild')(Guild)
|
Topic = deprecated('renamed to Guild')(Guild)
|
||||||
|
|
||||||
## END Guild
|
## END Guild
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,42 @@
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<h2>Community Identity</h2>
|
<h2>Community Identity</h2>
|
||||||
<div>
|
<div>
|
||||||
<label for="GS__display_name">Display name:</label>
|
<label>Display name:
|
||||||
<input type="text" name="display_name" id="GS__display_name" value="{{ gu.display_name or '' }}" />
|
<input type="text" name="display_name" value="{{ gu.display_name or '' }}" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="GS__description">Description:</label>
|
<label>Description:
|
||||||
<textarea name="description" id="GS__description">{{ gu.description or '' }}</textarea>
|
<textarea name="description">{{ gu.description or '' }}</textarea>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="primary">Save</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="card">
|
||||||
|
<h2>Safety</h2>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="restricted" value="1" {{ checked_if(gu.is_restricted) }} />
|
||||||
|
Allow only approved members to post and comment
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
Ban user from participating in {{ gu.handle() }}:
|
||||||
|
<input type="text" name="exile_name" placeholder="username" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="exile_reverse" value="1" />
|
||||||
|
Remove ban on given user
|
||||||
|
</label>
|
||||||
|
<small class="faint">
|
||||||
|
Bans (aka: exiles) are permanent and reversible.<br />
|
||||||
|
Banned (exiled) users are not allowed to post or comment on {{ gu.handle() }}.<br />
|
||||||
|
Reverse the ban by checking “Remove ban on given user”.
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button type="submit" class="primary">Save</button>
|
<button type="submit" class="primary">Save</button>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,31 @@
|
||||||
<a href="{{ gu.url() }}/settings"><button class="card">{{ icon('settings') }} Mod Tools</button></a>
|
<a href="{{ gu.url() }}/settings"><button class="card">{{ icon('settings') }} Mod Tools</button></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ subscribe_button(gu, gu.has_subscriber(current_user)) }}
|
{{ subscribe_button(gu, gu.has_subscriber(current_user)) }}
|
||||||
|
{% if not gu.owner %}
|
||||||
|
<aside class="card">
|
||||||
|
<p class="centered">{{ gu.handle() }} is currently unmoderated</p>
|
||||||
|
</aside>
|
||||||
|
{% elif gu.has_exiled(current_user) %}
|
||||||
|
<aside class="card">
|
||||||
|
<p class="centered">Moderator list is hidden because you are banned.</p>
|
||||||
|
<!-- TODO appeal button -->
|
||||||
|
</aside>
|
||||||
|
{% else %}
|
||||||
|
<aside class="card">
|
||||||
|
<h3>Moderators of {{ gu.handle() }}</h3>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
{% for moder in gu.moderators() %}
|
||||||
|
<li><a href="{{ moder.user.url() }}">{{ moder.user.handle() }}</a>
|
||||||
|
{% if moder.is_owner %}
|
||||||
|
<span>{{ icon('mod_mode') }} <small>Owner</small></span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
from flask import Blueprint, abort, flash, render_template, request
|
from flask import Blueprint, abort, flash, render_template, request
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
import datetime
|
||||||
|
|
||||||
from ..models import db, User, Guild
|
from ..models import Member, db, User, Guild
|
||||||
|
|
||||||
current_user: User
|
current_user: User
|
||||||
|
|
||||||
|
|
@ -21,11 +22,31 @@ def guild_settings(name: str):
|
||||||
changes = False
|
changes = False
|
||||||
display_name = request.form.get('display_name')
|
display_name = request.form.get('display_name')
|
||||||
description = request.form.get('description')
|
description = request.form.get('description')
|
||||||
|
exile_name = request.form.get('exile_name')
|
||||||
|
exile_reverse = 'exile_reverse' in request.form
|
||||||
|
restricted = 'restricted' in request.form
|
||||||
|
|
||||||
if description and description != gu.description:
|
if description and description != gu.description:
|
||||||
changes, gu.description = True, description.strip()
|
changes, gu.description = True, description.strip()
|
||||||
if display_name and display_name != gu.display_name:
|
if display_name and display_name != gu.display_name:
|
||||||
changes, gu.display_name = True, display_name.strip()
|
changes, gu.display_name = True, display_name.strip()
|
||||||
|
if exile_name:
|
||||||
|
exile_user = db.session.execute(select(User).where(User.username == exile_name)).scalar()
|
||||||
|
if exile_user:
|
||||||
|
if exile_reverse:
|
||||||
|
mem = gu.update_member(exile_user, banned_at = None, banned_by_id = None)
|
||||||
|
if mem.banned_at == None:
|
||||||
|
flash(f'Removed ban on {exile_user.handle()}')
|
||||||
|
changes = True
|
||||||
|
else:
|
||||||
|
mem = gu.update_member(exile_user, banned_at = datetime.datetime.now(), banned_by_id = current_user.id)
|
||||||
|
if mem.banned_at != None:
|
||||||
|
flash(f'{exile_user.handle()} has been exiled')
|
||||||
|
changes = True
|
||||||
|
else:
|
||||||
|
flash(f'User \'{exile_name}\' not found, can\'t exile')
|
||||||
|
if restricted and restricted != gu.is_restricted:
|
||||||
|
changes, gu.is_restricted = True, restricted
|
||||||
|
|
||||||
if changes:
|
if changes:
|
||||||
db.session.add(gu)
|
db.session.add(gu)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue