mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
extra fields on profiles ala Mastodon
This commit is contained in:
parent
91a74e9573
commit
3662749188
10 changed files with 146 additions and 11 deletions
|
@ -300,6 +300,12 @@ def user_profile(actor):
|
|||
actor_data['source'] = {'content': user.about, 'mediaType': 'text/markdown'}
|
||||
if user.matrix_user_id and main_user_name:
|
||||
actor_data['matrixUserId'] = user.matrix_user_id
|
||||
if user.extra_fields.count() > 0:
|
||||
actor_data['attachment'] = []
|
||||
for field in user.extra_fields:
|
||||
actor_data['attachment'].append({'type': 'PropertyValue',
|
||||
'name': field.label,
|
||||
'value': field.text})
|
||||
resp = jsonify(actor_data)
|
||||
resp.content_type = 'application/activity+json'
|
||||
resp.headers.set('Link', f'<https://{current_app.config["SERVER_NAME"]}/u/{actor}>; rel="alternate"; type="text/html"')
|
||||
|
|
|
@ -16,7 +16,8 @@ from sqlalchemy.exc import IntegrityError
|
|||
from app import db, cache, constants, celery
|
||||
from app.models import User, Post, Community, BannedInstances, File, PostReply, AllowedInstances, Instance, utcnow, \
|
||||
PostVote, PostReplyVote, ActivityPubLog, Notification, Site, CommunityMember, InstanceRole, Report, Conversation, \
|
||||
Language, Tag, Poll, PollChoice, UserFollower, CommunityBan, CommunityJoinRequest, NotificationSubscription, Licence
|
||||
Language, Tag, Poll, PollChoice, UserFollower, CommunityBan, CommunityJoinRequest, NotificationSubscription, \
|
||||
Licence, UserExtraField
|
||||
from app.activitypub.signature import signed_get_request, post_request
|
||||
import time
|
||||
from app.constants import *
|
||||
|
@ -522,6 +523,11 @@ def refresh_user_profile_task(user_id):
|
|||
user.about_html = markdown_to_html(user.about) # prefer Markdown if provided, overwrite version obtained from HTML
|
||||
else:
|
||||
user.about = html_to_text(user.about_html)
|
||||
if 'attachment' in activity_json and isinstance(activity_json['attachment'], list):
|
||||
user.extra_fields = []
|
||||
for field_data in activity_json['attachment']:
|
||||
if field_data['type'] == 'PropertyValue':
|
||||
user.extra_fields.append(UserExtraField(label=field_data['name'].strip(), text=field_data['value'].strip()))
|
||||
if 'type' in activity_json:
|
||||
user.bot = True if activity_json['type'] == 'Service' else False
|
||||
user.ap_fetched_at = utcnow()
|
||||
|
@ -769,6 +775,11 @@ def actor_json_to_model(activity_json, address, server):
|
|||
cover = File(source_url=activity_json['image']['url'])
|
||||
user.cover = cover
|
||||
db.session.add(cover)
|
||||
if 'attachment' in activity_json and isinstance(activity_json['attachment'], list):
|
||||
user.extra_fields = []
|
||||
for field_data in activity_json['attachment']:
|
||||
if field_data['type'] == 'PropertyValue':
|
||||
user.extra_fields.append(UserExtraField(label=field_data['name'].strip(), text=field_data['value'].strip()))
|
||||
try:
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
|
|
@ -727,6 +727,7 @@ class User(UserMixin, db.Model):
|
|||
activity = db.relationship('ActivityLog', backref='account', lazy='dynamic', cascade="all, delete-orphan")
|
||||
posts = db.relationship('Post', lazy='dynamic', cascade="all, delete-orphan")
|
||||
post_replies = db.relationship('PostReply', lazy='dynamic', cascade="all, delete-orphan")
|
||||
extra_fields = db.relationship('UserExtraField', lazy='dynamic', cascade="all, delete-orphan")
|
||||
|
||||
roles = db.relationship('Role', secondary=user_role, lazy='dynamic', cascade="all, delete")
|
||||
|
||||
|
@ -2053,6 +2054,13 @@ class UserNote(db.Model):
|
|||
created_at = db.Column(db.DateTime, default=utcnow)
|
||||
|
||||
|
||||
class UserExtraField(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
|
||||
label = db.Column(db.String(50))
|
||||
text = db.Column(db.String(256))
|
||||
|
||||
|
||||
class UserBlock(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
blocker_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
|
||||
|
|
|
@ -1397,6 +1397,10 @@ time {
|
|||
border-top: solid 1px #ddd;
|
||||
}
|
||||
|
||||
.hide-labels label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#add_local_community_form #url {
|
||||
width: 297px;
|
||||
display: inline-block;
|
||||
|
@ -1487,10 +1491,6 @@ fieldset legend {
|
|||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.list-group-item:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
top: 0;
|
||||
}
|
||||
|
@ -1708,6 +1708,9 @@ h1 .warning_badge {
|
|||
.side_pane img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.side_pane .list-group-item:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] .main_pane {
|
||||
border-color: #424549;
|
||||
|
|
|
@ -1064,6 +1064,10 @@ time {
|
|||
}
|
||||
}
|
||||
|
||||
.hide-labels label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#add_local_community_form {
|
||||
#url {
|
||||
width: 297px;
|
||||
|
@ -1157,10 +1161,6 @@ fieldset {
|
|||
}
|
||||
}
|
||||
|
||||
.list-group-item:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
top: 0;
|
||||
}
|
||||
|
@ -1397,6 +1397,10 @@ h1 .warning_badge {
|
|||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.list-group-item:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] .main_pane {
|
||||
|
|
|
@ -44,6 +44,28 @@
|
|||
<a href="#" aria-hidden="true" class="markdown_editor_enabler create_post_markdown_editor_enabler" data-id="about">{{ _('Enable markdown editor') }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<fieldset class="coolfieldset mt-2 mb-3">
|
||||
<legend>{{ _('Extra fields') }}</legend>
|
||||
<p>{{ _('Your homepage, pronouns, age, etc.') }}</p>
|
||||
<table class="hide-labels">
|
||||
<tr>
|
||||
<td>{{ render_field(form.extra_label_1) }}</td>
|
||||
<td>{{ render_field(form.extra_text_1) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ render_field(form.extra_label_2) }}</td>
|
||||
<td>{{ render_field(form.extra_text_2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ render_field(form.extra_label_3) }}</td>
|
||||
<td>{{ render_field(form.extra_text_3) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ render_field(form.extra_label_4) }}</td>
|
||||
<td>{{ render_field(form.extra_text_4) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</fieldset>
|
||||
{{ render_field(form.bot) }}
|
||||
{{ render_field(form.matrixuserid) }}
|
||||
<small class="field_hint">e.g. @something:matrix.org. Include leading @ and use : before server</small>
|
||||
|
@ -69,7 +91,8 @@
|
|||
hx-swap="outerHTML">{{ _('Remove image') }}</a></p>
|
||||
<div id="cover_div" class="community_header mb-4" style="display: none; height: 240px; background-image: url({{ user.cover_image() }});"></div>
|
||||
{% endif %}
|
||||
{{ render_field(form.submit) }}
|
||||
|
||||
<p class="mt-4">{{ render_field(form.submit) }}</p>
|
||||
</form>
|
||||
<p class="mt-4 pt-4">
|
||||
<a class="btn btn-warning" href="{{ url_for('user.delete_account') }}">{{ _('Delete account') }}</a>
|
||||
|
|
|
@ -124,6 +124,21 @@
|
|||
<div class="profile_bio">
|
||||
{{ user.about_html|safe }}
|
||||
</div>
|
||||
{% if user.extra_fields -%}
|
||||
<ul class="list-group">
|
||||
{% for field in user.extra_fields -%}
|
||||
<li class="list-group-item">
|
||||
<p class="mb-0"><strong>{{ field.label }}</strong><br>
|
||||
{% if field.text.startswith('http') -%}
|
||||
<a href="{{ field.text }}" rel="nofollow noindex ugc">{{ field.text }}</a>
|
||||
{% else -%}
|
||||
{{ field.text }}
|
||||
{% endif -%}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor -%}
|
||||
</ul>
|
||||
{% endif -%}
|
||||
{% if posts %}
|
||||
<h2 class="mt-4">Posts</h2>
|
||||
<div class="post_list">
|
||||
|
|
|
@ -14,6 +14,14 @@ class ProfileForm(FlaskForm):
|
|||
password_field = PasswordField(_l('Set new password'), validators=[Optional(), Length(min=1, max=50)],
|
||||
render_kw={"autocomplete": 'new-password'})
|
||||
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)], render_kw={'rows': 5})
|
||||
extra_label_1 = StringField(_l('Extra field 1 - label'), validators=[Optional(), Length(max=50)], render_kw={"placeholder": _l('Label')})
|
||||
extra_text_1 = StringField(_l('Extra field 1 - text'), validators=[Optional(), Length(max=256)], render_kw={"placeholder": _l('Content')})
|
||||
extra_label_2 = StringField(_l('Extra field 2 - label'), validators=[Optional(), Length(max=50)], render_kw={"placeholder": _l('Label')})
|
||||
extra_text_2 = StringField(_l('Extra field 2 - text'), validators=[Optional(), Length(max=256)], render_kw={"placeholder": _l('Content')})
|
||||
extra_label_3 = StringField(_l('Extra field 3 - label'), validators=[Optional(), Length(max=50)], render_kw={"placeholder": _l('Label')})
|
||||
extra_text_3 = StringField(_l('Extra field 3 - text'), validators=[Optional(), Length(max=256)], render_kw={"placeholder": _l('Content')})
|
||||
extra_label_4 = StringField(_l('Extra field 4 - label'), validators=[Optional(), Length(max=50)], render_kw={"placeholder": _l('Label')})
|
||||
extra_text_4 = StringField(_l('Extra field 4 - text'), validators=[Optional(), Length(max=256)], render_kw={"placeholder": _l('Content')})
|
||||
matrixuserid = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)],
|
||||
render_kw={'autocomplete': 'off'})
|
||||
profile_file = FileField(_l('Avatar image'), render_kw={'accept': 'image/*'})
|
||||
|
|
|
@ -16,7 +16,8 @@ from app.constants import *
|
|||
from app.email import send_verification_email
|
||||
from app.models import Post, Community, CommunityMember, User, PostReply, PostVote, Notification, utcnow, File, Site, \
|
||||
Instance, Report, UserBlock, CommunityBan, CommunityJoinRequest, CommunityBlock, Filter, Domain, DomainBlock, \
|
||||
InstanceBlock, NotificationSubscription, PostBookmark, PostReplyBookmark, read_posts, Topic, UserNote
|
||||
InstanceBlock, NotificationSubscription, PostBookmark, PostReplyBookmark, read_posts, Topic, UserNote, \
|
||||
UserExtraField
|
||||
from app.user import bp
|
||||
from app.user.forms import ProfileForm, SettingsForm, DeleteAccountForm, ReportUserForm, \
|
||||
FilterForm, KeywordFilterEditForm, RemoteFollowForm, ImportExportForm, UserNoteForm
|
||||
|
@ -129,6 +130,15 @@ def edit_profile(actor):
|
|||
current_user.about = piefed_markdown_to_lemmy_markdown(form.about.data)
|
||||
current_user.about_html = markdown_to_html(form.about.data)
|
||||
current_user.matrix_user_id = form.matrixuserid.data
|
||||
current_user.extra_fields = []
|
||||
if form.extra_label_1.data.strip() != '' and form.extra_text_1.data.strip() != '':
|
||||
current_user.extra_fields.append(UserExtraField(label=form.extra_label_1.data.strip(), text=form.extra_text_1.data.strip()))
|
||||
if form.extra_label_2.data.strip() != '' and form.extra_text_2.data.strip() != '':
|
||||
current_user.extra_fields.append(UserExtraField(label=form.extra_label_2.data.strip(), text=form.extra_text_2.data.strip()))
|
||||
if form.extra_label_3.data.strip() != '' and form.extra_text_3.data.strip() != '':
|
||||
current_user.extra_fields.append(UserExtraField(label=form.extra_label_3.data.strip(), text=form.extra_text_3.data.strip()))
|
||||
if form.extra_label_4.data.strip() != '' and form.extra_text_4.data.strip() != '':
|
||||
current_user.extra_fields.append(UserExtraField(label=form.extra_label_4.data.strip(), text=form.extra_text_4.data.strip()))
|
||||
current_user.bot = form.bot.data
|
||||
profile_file = request.files['profile_file']
|
||||
if profile_file and profile_file.filename != '':
|
||||
|
@ -169,7 +179,13 @@ def edit_profile(actor):
|
|||
form.title.data = current_user.title
|
||||
form.email.data = current_user.email
|
||||
form.about.data = current_user.about
|
||||
i = 1
|
||||
for extra_field in current_user.extra_fields:
|
||||
getattr(form, f"extra_label_{i}").data = extra_field.label
|
||||
getattr(form, f"extra_text_{i}").data = extra_field.text
|
||||
i += 1
|
||||
form.matrixuserid.data = current_user.matrix_user_id
|
||||
form.bot.data = current_user.bot
|
||||
form.password_field.data = ''
|
||||
|
||||
return render_template('user/edit_profile.html', title=_('Edit profile'), form=form, user=current_user,
|
||||
|
|
41
migrations/versions/f961f446ae17_user_extra_fields.py
Normal file
41
migrations/versions/f961f446ae17_user_extra_fields.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
"""user extra fields
|
||||
|
||||
Revision ID: f961f446ae17
|
||||
Revises: 1189f921aca6
|
||||
Create Date: 2024-12-22 14:56:43.714502
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f961f446ae17'
|
||||
down_revision = '1189f921aca6'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('user_extra_field',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('label', sa.String(length=50), nullable=True),
|
||||
sa.Column('text', sa.String(length=256), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('user_extra_field', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_user_extra_field_user_id'), ['user_id'], unique=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('user_extra_field', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_user_extra_field_user_id'))
|
||||
|
||||
op.drop_table('user_extra_field')
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in a new issue