let admin change site logo through UI #197

This commit is contained in:
rimu 2024-06-14 18:03:47 +08:00
parent be0544a372
commit 4b3844bbae
6 changed files with 112 additions and 14 deletions

View file

@ -2183,6 +2183,7 @@ def parse_redis_socket_string(connection_string: str):
def lemmy_site_data(): def lemmy_site_data():
site = g.site site = g.site
logo = site.logo if site.logo else '/static/images/logo2.png'
data = { data = {
"site_view": { "site_view": {
"site": { "site": {
@ -2191,7 +2192,7 @@ def lemmy_site_data():
"sidebar": site.sidebar, "sidebar": site.sidebar,
"published": site.created_at.isoformat(), "published": site.created_at.isoformat(),
"updated": site.updated.isoformat(), "updated": site.updated.isoformat(),
"icon": f"https://{current_app.config['SERVER_NAME']}/static/images/logo2.png", "icon": f"https://{current_app.config['SERVER_NAME']}{logo}",
"banner": "", "banner": "",
"description": site.description, "description": site.description,
"actor_id": f"https://{current_app.config['SERVER_NAME']}/", "actor_id": f"https://{current_app.config['SERVER_NAME']}/",

View file

@ -1,11 +1,14 @@
import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from io import BytesIO
from time import sleep from time import sleep
from flask import request, flash, json, url_for, current_app, redirect, g from flask import request, flash, json, url_for, current_app, redirect, g, abort
from flask_login import login_required, current_user from flask_login import login_required, current_user
from flask_babel import _ from flask_babel import _
from slugify import slugify from slugify import slugify
from sqlalchemy import text, desc, or_ from sqlalchemy import text, desc, or_
from PIL import Image
from app import db, celery, cache from app import db, celery, cache
from app.activitypub.routes import process_inbox_request, process_delete_request from app.activitypub.routes import process_inbox_request, process_delete_request
@ -21,7 +24,7 @@ from app.models import AllowedInstances, BannedInstances, ActivityPubLog, utcnow
User, Instance, File, Report, Topic, UserRegistration, Role, Post, PostReply, Language User, Instance, File, Report, Topic, UserRegistration, Role, Post, PostReply, Language
from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html, \ from app.utils import render_template, permission_required, set_setting, get_setting, gibberish, markdown_to_html, \
moderating_communities, joined_communities, finalize_user_setup, theme_list, blocked_phrases, blocked_referrers, \ moderating_communities, joined_communities, finalize_user_setup, theme_list, blocked_phrases, blocked_referrers, \
topic_tree, languages_for_form, menu_topics topic_tree, languages_for_form, menu_topics, ensure_directory_exists
from app.admin import bp from app.admin import bp
@ -53,6 +56,58 @@ def admin_site():
site.contact_email = form.contact_email.data site.contact_email = form.contact_email.data
if site.id is None: if site.id is None:
db.session.add(site) db.session.add(site)
# Save site icon
uploaded_icon = request.files['icon']
if uploaded_icon and uploaded_icon.filename != '':
allowed_extensions = ['.gif', '.jpg', '.jpeg', '.png', '.webp']
file_ext = os.path.splitext(uploaded_icon.filename)[1]
if file_ext.lower() not in allowed_extensions:
abort(400)
directory = 'app/static/media'
ensure_directory_exists(directory)
# Remove existing logo files
if os.path.isfile(f'app{site.logo}'):
os.unlink(f'app{site.logo}')
if os.path.isfile(f'app{site.logo_152}'):
os.unlink(f'app{site.logo_152}')
if os.path.isfile(f'app{site.logo_32}'):
os.unlink(f'app{site.logo_32}')
if os.path.isfile(f'app{site.logo_16}'):
os.unlink(f'app{site.logo_16}')
# Save logo file
base_filename = f'logo_{gibberish(5)}'
uploaded_icon.save(f'{directory}/{base_filename}{file_ext}')
img = Image.open(f'{directory}/{base_filename}{file_ext}')
if img.width > 100:
img.thumbnail((100, 100))
img.save(f'{directory}/{base_filename}_100{file_ext}')
site.logo = f'/static/media/{base_filename}_100{file_ext}'
delete_original = True
else:
site.logo = f'/static/media/{base_filename}{file_ext}'
delete_original = False
# Save multiple copies of the logo - different sizes
img = Image.open(f'{directory}/{base_filename}{file_ext}')
img.thumbnail((152, 152))
img.save(f'{directory}/{base_filename}_152{file_ext}')
site.logo_152 = f'/static/media/{base_filename}_152{file_ext}'
img = Image.open(f'{directory}/{base_filename}{file_ext}')
img.thumbnail((32, 32))
img.save(f'{directory}/{base_filename}_32{file_ext}')
site.logo_32 = f'/static/media/{base_filename}_32{file_ext}'
img = Image.open(f'{directory}/{base_filename}{file_ext}')
img.thumbnail((16, 16))
img.save(f'{directory}/{base_filename}_16{file_ext}')
site.logo_16 = f'/static/media/{base_filename}_16{file_ext}'
if delete_original:
os.unlink(f'app/static/media/{base_filename}{file_ext}')
db.session.commit() db.session.commit()
set_setting('announcement', form.announcement.data) set_setting('announcement', form.announcement.data)
flash('Settings saved.') flash('Settings saved.')

View file

@ -1479,6 +1479,10 @@ class Site(db.Model):
default_theme = db.Column(db.String(20), default='') default_theme = db.Column(db.String(20), default='')
contact_email = db.Column(db.String(255), default='') contact_email = db.Column(db.String(255), default='')
about = db.Column(db.Text, default='') about = db.Column(db.Text, default='')
logo = db.Column(db.String(40), default='')
logo_152 = db.Column(db.String(40), default='')
logo_32 = db.Column(db.String(40), default='')
logo_16 = db.Column(db.String(40), default='')
@staticmethod @staticmethod
def admins() -> List[User]: def admins() -> List[User]:

View file

@ -17,17 +17,17 @@
{{ render_field(form.name) }} {{ render_field(form.name) }}
{{ render_field(form.description) }} {{ render_field(form.description) }}
{{ render_field(form.sidebar) }} {{ render_field(form.sidebar) }}
<p class="small field_hint">HTML is allowed in this field.</p> <p class="small field_hint">{{ _('HTML is allowed in this field.') }}</p>
{{ render_field(form.announcement) }} {{ render_field(form.announcement) }}
<p class="small field_hint">HTML is allowed in this field.</p> <p class="small field_hint">{{ _('HTML is allowed in this field.') }}</p>
{{ render_field(form.icon) }} {{ render_field(form.icon) }}
<p class="small field_hint">{{ _('Provide a square image, minimum 50 pixels wide but ideally 152+ pixels.') }}
<h2>{{ _('About this instance') }}</h2> <h2>{{ _('About this instance') }}</h2>
<p>{{ _('Provide a more extensive description of the instance, set a contact address and provide legal information. This information appears on the <a href="/about">about</a> page.') }}</p> <p>{{ _('Provide a more extensive description of the instance, set a contact address and provide legal information. This information appears on the <a href="/about">about</a> page.') }}</p>
{{ render_field(form.about) }} {{ render_field(form.about) }}
<p class="small field_hint">HTML is allowed in this field.</p> <p class="small field_hint">{{ _('HTML is allowed in this field.') }}</p>
{{ render_field(form.legal_information) }} {{ render_field(form.legal_information) }}
<p class="small field_hint">HTML is allowed in this field.</p> <p class="small field_hint">{{ _('HTML is allowed in this field.') }}</p>
{{ render_field(form.contact_email) }} {{ render_field(form.contact_email) }}
{{ render_field(form.submit) }} {{ render_field(form.submit) }}
</form> </form>

View file

@ -53,12 +53,12 @@
{% endif -%} {% endif -%}
{% endblock -%} {% endblock -%}
<title>{% if title %}{{ title }}{% else %}{{ _('PieFed') }}{% endif %}</title> <title>{% if title %}{{ title }}{% else %}{{ _('PieFed') }}{% endif %}</title>
<link rel="apple-touch-icon" sizes="152x152" href="/static/images/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="152x152" href="{{ g.site.logo_152 if g.site.logo_152 else '/static/images/apple-touch-icon.png' }}">
<link rel="icon" type="image/png" sizes="32x32" href="/static/images/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="{{ g.site.logo_32 if g.site.logo_32 else '/static/images/favicon-32x32.png' }}">
<link rel="icon" type="image/png" sizes="16x16" href="/static/images/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="{{ g.site.logo_16 if g.site.logo_16 else '/static/images/favicon-16x16.png' }}">
<link rel="manifest" href="/static/manifest.json"> <link rel="manifest" href="/static/manifest.json">
<link rel="shortcut icon" type="image/png" href="/static/images/favicon-32x32.png"> <link rel="shortcut icon" type="image/png" href="{{ g.site.logo_32 if g.site.logo_32 else '/static/images/favicon-32x32.png' }}">
<link href='/static/images/favicon.ico' rel='icon' type='image/x-icon'> <link href="{{ g.site.logo_16 if g.site.logo_16 else '/static/images/favicon.ico' }}" rel='icon' type='image/x-icon'>
<meta name="msapplication-TileColor" content="#da532c"> <meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="/static/browserconfig.xml"> <meta name="msapplication-config" content="/static/browserconfig.xml">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
@ -111,7 +111,7 @@
{% block navbar -%} {% block navbar -%}
<div class="navbar navbar-expand-lg sticky-md-top"> <div class="navbar navbar-expand-lg sticky-md-top">
<div class="{{ 'container-lg' if post_layout != 'masonry_wide' else 'container-fluid' }}" role="banner"> <div class="{{ 'container-lg' if post_layout != 'masonry_wide' else 'container-fluid' }}" role="banner">
<a class="navbar-brand" href="/">{% if not low_bandwidth %}<img src="/static/images/logo2.png" alt="Logo" width="50" height="50" />{% endif %}{{ g.site.name }}</a> <a class="navbar-brand" href="/">{% if not low_bandwidth %}<img src="{{ g.site.logo if g.site.logo else '/static/images/logo2.png' }}" alt="Logo" width="50" height="50" />{% endif %}{{ g.site.name }}</a>
{% if current_user.is_authenticated -%} {% if current_user.is_authenticated -%}
<a class="nav-link d-lg-none" href="/notifications" aria-label="{{ _('Notifications') }}"> <a class="nav-link d-lg-none" href="/notifications" aria-label="{{ _('Notifications') }}">
{% if current_user.unread_notifications -%} {% if current_user.unread_notifications -%}

View file

@ -0,0 +1,38 @@
"""site logo
Revision ID: 1c51c3df2770
Revises: 5e84668d279e
Create Date: 2024-06-14 17:50:12.371773
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '1c51c3df2770'
down_revision = '5e84668d279e'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('site', schema=None) as batch_op:
batch_op.add_column(sa.Column('logo', sa.String(length=40), nullable=True))
batch_op.add_column(sa.Column('logo_152', sa.String(length=40), nullable=True))
batch_op.add_column(sa.Column('logo_32', sa.String(length=40), nullable=True))
batch_op.add_column(sa.Column('logo_16', sa.String(length=40), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('site', schema=None) as batch_op:
batch_op.drop_column('logo_16')
batch_op.drop_column('logo_32')
batch_op.drop_column('logo_152')
batch_op.drop_column('logo')
# ### end Alembic commands ###