mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-02-02 16:21:32 -08:00
improve registration and add community topics/categories
This commit is contained in:
parent
276a937799
commit
781694e023
13 changed files with 247 additions and 11 deletions
|
@ -67,6 +67,7 @@ class EditCommunityForm(FlaskForm):
|
|||
(3650, _l('10 years')),
|
||||
]
|
||||
content_retention = SelectField(_l('Retain content'), choices=options, default=1, coerce=int)
|
||||
topic = SelectField(_l('Topic'), coerce=int, validators=[Optional()])
|
||||
submit = SubmitField(_l('Save'))
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
|
@ -82,6 +83,11 @@ class EditCommunityForm(FlaskForm):
|
|||
return True
|
||||
|
||||
|
||||
class EditTopicForm(FlaskForm):
|
||||
name = StringField(_l('Name'), validators=[DataRequired()])
|
||||
submit = SubmitField(_l('Save'))
|
||||
|
||||
|
||||
class EditUserForm(FlaskForm):
|
||||
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)])
|
||||
matrix_user_id = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)])
|
||||
|
|
|
@ -10,11 +10,12 @@ from app import db, celery
|
|||
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
|
||||
from app.admin.forms import FederationForm, SiteMiscForm, SiteProfileForm, EditCommunityForm, EditUserForm, \
|
||||
EditTopicForm
|
||||
from app.admin.util import unsubscribe_from_everything_then_delete, unsubscribe_from_community
|
||||
from app.community.util import save_icon_file, save_banner_file
|
||||
from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow, Site, Community, CommunityMember, \
|
||||
User, Instance, File, Report
|
||||
User, Instance, File, Report, Topic
|
||||
from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html
|
||||
from app.admin import bp
|
||||
|
||||
|
@ -182,12 +183,21 @@ def admin_communities():
|
|||
communities=communities)
|
||||
|
||||
|
||||
def topics_for_form():
|
||||
topics = Topic.query.order_by(Topic.name).all()
|
||||
result = [(0, _('None'))]
|
||||
for topic in topics:
|
||||
result.append((topic.id, topic.name))
|
||||
return result
|
||||
|
||||
|
||||
@bp.route('/community/<int:community_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@permission_required('administer all communities')
|
||||
def admin_community_edit(community_id):
|
||||
form = EditCommunityForm()
|
||||
community = Community.query.get_or_404(community_id)
|
||||
form.topic.choices = topics_for_form()
|
||||
if form.validate_on_submit():
|
||||
community.name = form.url.data
|
||||
community.title = form.title.data
|
||||
|
@ -202,6 +212,7 @@ def admin_community_edit(community_id):
|
|||
community.show_all = form.show_all.data
|
||||
community.low_quality = form.low_quality.data
|
||||
community.content_retention = form.content_retention.data
|
||||
community.topic_id = form.topic.data if form.topic.data != 0 else None
|
||||
icon_file = request.files['icon_file']
|
||||
if icon_file and icon_file.filename != '':
|
||||
if community.icon_id:
|
||||
|
@ -216,6 +227,7 @@ def admin_community_edit(community_id):
|
|||
file = save_banner_file(banner_file)
|
||||
if file:
|
||||
community.image = file
|
||||
|
||||
db.session.commit()
|
||||
flash(_('Saved'))
|
||||
return redirect(url_for('admin.admin_communities'))
|
||||
|
@ -235,6 +247,7 @@ def admin_community_edit(community_id):
|
|||
form.show_all.data = community.show_all
|
||||
form.low_quality.data = community.low_quality
|
||||
form.content_retention.data = community.content_retention
|
||||
form.topic.data = community.topic_id if community.topic_id else None
|
||||
return render_template('admin/edit_community.html', title=_('Edit community'), form=form, community=community)
|
||||
|
||||
|
||||
|
@ -278,6 +291,61 @@ def unsubscribe_everyone_then_delete_task(community_id):
|
|||
db.session.delete(community) # todo: when a remote community is deleted it will be able to be re-created by using the 'Add remote' function. Not ideal. Consider soft-delete.
|
||||
|
||||
|
||||
@bp.route('/topics', methods=['GET'])
|
||||
@login_required
|
||||
@permission_required('administer all communities')
|
||||
def admin_topics():
|
||||
topics = Topic.query.order_by(Topic.name).all()
|
||||
return render_template('admin/topics.html', title=_('Topics'), topics=topics)
|
||||
|
||||
|
||||
@bp.route('/topic/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@permission_required('administer all communities')
|
||||
def admin_topic_add():
|
||||
form = EditTopicForm()
|
||||
if form.validate_on_submit():
|
||||
topic = Topic(name=form.name.data, num_communities=0)
|
||||
db.session.add(topic)
|
||||
db.session.commit()
|
||||
flash(_('Saved'))
|
||||
return redirect(url_for('admin.admin_topics'))
|
||||
|
||||
return render_template('admin/edit_topic.html', title=_('Add topic'), form=form)
|
||||
|
||||
@bp.route('/topic/<int:topic_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@permission_required('administer all communities')
|
||||
def admin_topic_edit(topic_id):
|
||||
form = EditTopicForm()
|
||||
topic = Topic.query.get_or_404(topic_id)
|
||||
if form.validate_on_submit():
|
||||
topic.name = form.name.data
|
||||
topic.num_communities = topic.communities.count()
|
||||
db.session.commit()
|
||||
flash(_('Saved'))
|
||||
return redirect(url_for('admin.admin_topics'))
|
||||
else:
|
||||
form.name.data = topic.name
|
||||
return render_template('admin/edit_topic.html', title=_('Edit topic'), form=form, topic=topic)
|
||||
|
||||
|
||||
@bp.route('/topic/<int:topic_id>/delete', methods=['GET'])
|
||||
@login_required
|
||||
@permission_required('administer all communities')
|
||||
def admin_topic_delete(topic_id):
|
||||
topic = Topic.query.get_or_404(topic_id)
|
||||
topic.num_communities = topic.communities.count()
|
||||
if topic.num_communities == 0:
|
||||
db.session.delete(topic)
|
||||
flash(_('Topic deleted'))
|
||||
else:
|
||||
flash(_('Cannot delete topic with communities assigned to it.', 'error'))
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for('admin.admin_topics'))
|
||||
|
||||
|
||||
@bp.route('/users', methods=['GET'])
|
||||
@login_required
|
||||
@permission_required('administer all users')
|
||||
|
|
|
@ -92,7 +92,7 @@ def register():
|
|||
verification_token = random_token(16)
|
||||
form.user_name.data = form.user_name.data.strip()
|
||||
user = User(user_name=form.user_name.data, title=form.user_name.data, email=form.real_email.data,
|
||||
verification_token=verification_token, instance=1, ip_address=ip_address(),
|
||||
verification_token=verification_token, instance_id=1, ip_address=ip_address(),
|
||||
banned=user_ip_banned() or user_cookie_banned())
|
||||
user.set_password(form.password.data)
|
||||
db.session.add(user)
|
||||
|
@ -104,9 +104,9 @@ def register():
|
|||
if current_app.config['MODE'] == 'development':
|
||||
current_app.logger.info('Verify account:' + url_for('auth.verify_email', token=user.verification_token, _external=True))
|
||||
|
||||
flash(_('Great, you are now a registered user!'))
|
||||
flash(_('Great, you are now a registered user! Choose some communities to subscribe to. Use the topic filter to narrow things down.'))
|
||||
|
||||
resp = make_response(redirect(url_for('main.index')))
|
||||
resp = make_response(redirect(url_for('main.list_communities')))
|
||||
if user_ip_banned():
|
||||
resp.set_cookie('sesion', '17489047567495', expires=datetime(year=2099, month=12, day=30))
|
||||
return resp
|
||||
|
|
|
@ -15,7 +15,7 @@ from sqlalchemy import select, desc
|
|||
from sqlalchemy_searchable import search
|
||||
from app.utils import render_template, get_setting, gibberish, request_etag_matches, return_304, blocked_domains, \
|
||||
ap_datetime, ip_address, retrieve_block_list
|
||||
from app.models import Community, CommunityMember, Post, Site, User, utcnow, Domain
|
||||
from app.models import Community, CommunityMember, Post, Site, User, utcnow, Domain, Topic
|
||||
|
||||
|
||||
@bp.route('/', methods=['HEAD', 'GET', 'POST'])
|
||||
|
@ -133,15 +133,23 @@ def top_posts():
|
|||
def list_communities():
|
||||
verification_warning()
|
||||
search_param = request.args.get('search', '')
|
||||
topic_id = int(request.args.get('topic_id', 0))
|
||||
topics = Topic.query.order_by(Topic.name).all()
|
||||
if search_param == '':
|
||||
communities = Community.query.filter_by(banned=False).all()
|
||||
pass
|
||||
else:
|
||||
query = search(select(Community), search_param, sort=True) # todo: exclude banned communities from search
|
||||
communities = db.session.scalars(query).all()
|
||||
flash('Sorry, no search function yet. Use the topic filter for now.', 'warning')
|
||||
communities = Community.query.filter_by(banned=False).all()
|
||||
#query = search(select(Community), search_param, sort=True) # todo: exclude banned communities from search
|
||||
#communities = db.session.scalars(query).all()
|
||||
|
||||
return render_template('list_communities.html', communities=communities, search=search_param, title=_('Communities'),
|
||||
communities = Community.query.filter_by(banned=False)
|
||||
if topic_id != 0:
|
||||
communities = communities.filter_by(topic_id=topic_id)
|
||||
|
||||
return render_template('list_communities.html', communities=communities.all(), search=search_param, title=_('Communities'),
|
||||
SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
|
||||
SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER)
|
||||
SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, topics=topics, topic_id=topic_id)
|
||||
|
||||
|
||||
@bp.route('/communities/local', methods=['GET'])
|
||||
|
|
|
@ -113,6 +113,13 @@ class File(db.Model):
|
|||
os.unlink(self.thumbnail_path)
|
||||
|
||||
|
||||
class Topic(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(50))
|
||||
num_communities = db.Column(db.Integer, default=0)
|
||||
communities = db.relationship('Community', lazy='dynamic', backref='topic', cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class Community(db.Model):
|
||||
query_class = FullTextSearchQuery
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
@ -138,6 +145,7 @@ class Community(db.Model):
|
|||
public_key = db.Column(db.Text)
|
||||
private_key = db.Column(db.Text)
|
||||
content_retention = db.Column(db.Integer, default=-1)
|
||||
topic_id = db.Column(db.Integer, db.ForeignKey('topic.id'), index=True)
|
||||
|
||||
ap_id = db.Column(db.String(255), index=True)
|
||||
ap_profile_id = db.Column(db.String(255), index=True)
|
||||
|
|
|
@ -3,6 +3,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||
setupCommunityNameInput();
|
||||
setupShowMoreLinks();
|
||||
setupConfirmFirst();
|
||||
setupSubmitOnInputChange();
|
||||
setupTimeTracking();
|
||||
setupMobileNav();
|
||||
});
|
||||
|
@ -88,6 +89,32 @@ function setupConfirmFirst() {
|
|||
});
|
||||
})
|
||||
}
|
||||
|
||||
function setupSubmitOnInputChange() {
|
||||
const inputElements = document.querySelectorAll('.submit_on_change');
|
||||
|
||||
inputElements.forEach(element => {
|
||||
element.addEventListener("change", function() {
|
||||
const form = findParentForm(element);
|
||||
if (form) {
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Find the parent form of an element
|
||||
function findParentForm(element) {
|
||||
let currentElement = element;
|
||||
while (currentElement) {
|
||||
if (currentElement.tagName === 'FORM') {
|
||||
return currentElement;
|
||||
}
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setupShowMoreLinks() {
|
||||
const comments = document.querySelectorAll('.comment');
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<a href="{{ url_for('admin.admin_site') }}">{{ _('Site profile') }}</a> |
|
||||
<a href="{{ url_for('admin.admin_misc') }}">{{ _('Misc settings') }}</a> |
|
||||
<a href="{{ url_for('admin.admin_communities') }}">{{ _('Communities') }}</a> |
|
||||
<a href="{{ url_for('admin.admin_topics') }}">{{ _('Topics') }}</a> |
|
||||
<a href="{{ url_for('admin.admin_users') }}">{{ _('Users') }}</a> |
|
||||
<a href="{{ url_for('admin.admin_reports') }}">{{ _('Moderation') }}</a> |
|
||||
<a href="{{ url_for('admin.admin_federation') }}">{{ _('Federation') }}</a> |
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Title</th>
|
||||
<th>Topic</th>
|
||||
<th># Posts</th>
|
||||
<th>Home</th>
|
||||
<th>Popular</th>
|
||||
|
@ -28,6 +29,7 @@
|
|||
<td>{{ community.name }}</td>
|
||||
<td><img src="{{ community.icon_image('tiny') }}" class="community_icon rounded-circle" loading="lazy" />
|
||||
{{ community.display_name() }}</td>
|
||||
<td>{{ community.topic.name }}</td>
|
||||
<td>{{ community.post_count }}</td>
|
||||
<th>{{ '✓'|safe if community.show_home else '✗'|safe }}</th>
|
||||
<th>{{ '✓'|safe if community.show_popular else '✗'|safe }}</th>
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
{{ render_field(form.show_all) }}
|
||||
{{ render_field(form.low_quality) }}
|
||||
{{ render_field(form.content_retention) }}
|
||||
{{ render_field(form.topic) }}
|
||||
{% if not community.is_local() %}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
|
|
19
app/templates/admin/edit_topic.html
Normal file
19
app/templates/admin/edit_topic.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'bootstrap/form.html' import render_form %}
|
||||
|
||||
{% 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">
|
||||
{% if topic %}
|
||||
<h3>{{ _('Edit %(topic_name)s', topic_name=topic.name) }}</h3>
|
||||
{% endif %}
|
||||
{{ render_form(form) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
32
app/templates/admin/topics.html
Normal file
32
app/templates/admin/topics.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'bootstrap/form.html' import render_form %}
|
||||
|
||||
{% block app_content %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{% include 'admin/_nav.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<p><a href="{{ url_for('admin.admin_topic_add') }}" class="btn btn-primary">{{ _('Add topic') }}</a></p>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th># Communities</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
{% for topic in topics %}
|
||||
<tr>
|
||||
<td>{{ topic.name }}</td>
|
||||
<td>{{ topic.num_communities }}</td>
|
||||
<td><a href="{{ url_for('admin.admin_topic_edit', topic_id=topic.id) }}">Edit</a> |
|
||||
<a href="{{ url_for('admin.admin_topic_delete', topic_id=topic.id) }}" class="confirm_first">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -14,7 +14,20 @@
|
|||
<a href="/communities/subscribed" class="btn {{ 'btn-primary' if request.path == '/communities/subscribed' else 'btn-outline-secondary' }}">
|
||||
{{ _('Subscribed') }}
|
||||
</a>
|
||||
{% if topics %}
|
||||
<span class="mt-1 pl-4">
|
||||
<form method="get">Topic:
|
||||
<select name="topic_id" class="form-control-sm submit_on_change">
|
||||
<option value="0">All</option>
|
||||
{% for topic in topics %}
|
||||
<option value="{{ topic.id }}" {{ 'selected' if topic.id == topic_id }}>{{ topic.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="btn-group">
|
||||
|
|
51
migrations/versions/fd5d3a9cb584_community_topics.py
Normal file
51
migrations/versions/fd5d3a9cb584_community_topics.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
"""community topics
|
||||
|
||||
Revision ID: fd5d3a9cb584
|
||||
Revises: 328c9990e53e
|
||||
Create Date: 2024-01-04 15:13:29.499990
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'fd5d3a9cb584'
|
||||
down_revision = '328c9990e53e'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('topic',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=50), nullable=True),
|
||||
sa.Column('num_communities', sa.Integer(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('community', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('topic_id', sa.Integer(), nullable=True))
|
||||
batch_op.create_index(batch_op.f('ix_community_topic_id'), ['topic_id'], unique=False)
|
||||
batch_op.create_foreign_key(None, 'topic', ['topic_id'], ['id'])
|
||||
|
||||
with op.batch_alter_table('instance', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_instance_dormant'), ['dormant'], unique=False)
|
||||
batch_op.create_index(batch_op.f('ix_instance_gone_forever'), ['gone_forever'], unique=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('instance', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_instance_gone_forever'))
|
||||
batch_op.drop_index(batch_op.f('ix_instance_dormant'))
|
||||
|
||||
with op.batch_alter_table('community', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='foreignkey')
|
||||
batch_op.drop_index(batch_op.f('ix_community_topic_id'))
|
||||
batch_op.drop_column('topic_id')
|
||||
|
||||
op.drop_table('topic')
|
||||
# ### end Alembic commands ###
|
Loading…
Add table
Reference in a new issue