mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-26 11:31:08 +00:00
Merge branch 'themes' into dark-theme
This commit is contained in:
commit
005b69177c
16 changed files with 323 additions and 19 deletions
|
@ -4,6 +4,8 @@ from collections import defaultdict
|
|||
from urllib.parse import urlparse
|
||||
|
||||
from django import forms
|
||||
from django.contrib.staticfiles.utils import get_files
|
||||
from django.contrib.staticfiles.storage import StaticFilesStorage
|
||||
from django.forms import ModelForm, PasswordInput, widgets, ChoiceField
|
||||
from django.forms.widgets import Textarea
|
||||
from django.utils import timezone
|
||||
|
@ -454,6 +456,31 @@ class SiteForm(CustomForm):
|
|||
}
|
||||
|
||||
|
||||
class SiteThemeForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.SiteSettings
|
||||
fields = ["default_theme"]
|
||||
|
||||
|
||||
def get_theme_choices():
|
||||
"""static files"""
|
||||
choices = list(get_files(StaticFilesStorage(), location="css/themes"))
|
||||
current = models.Theme.objects.values_list("path", flat=True)
|
||||
return [(c, c) for c in choices if c not in current and c[-5:] == ".scss"]
|
||||
|
||||
|
||||
class ThemeForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.Theme
|
||||
fields = ["name", "path"]
|
||||
widgets = {
|
||||
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
|
||||
"path": forms.Select(
|
||||
attrs={"aria-describedby": "desc_path"}, choices=get_theme_choices()
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class AnnouncementForm(CustomForm):
|
||||
class Meta:
|
||||
model = models.Announcement
|
||||
|
|
68
bookwyrm/migrations/0142_auto_20220227_1752.py
Normal file
68
bookwyrm/migrations/0142_auto_20220227_1752.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
# Generated by Django 3.2.12 on 2022-02-27 17:52
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def add_default_themes(apps, schema_editor):
|
||||
"""add light and dark themes"""
|
||||
db_alias = schema_editor.connection.alias
|
||||
theme_model = apps.get_model("bookwyrm", "Theme")
|
||||
theme_model.objects.using(db_alias).create(
|
||||
name="BookWyrm Light",
|
||||
path="css/themes/bookwyrm-light.scss",
|
||||
)
|
||||
theme_model.objects.using(db_alias).create(
|
||||
name="BookWyrm Dark",
|
||||
path="css/themes/bookwyrm-dark.scss",
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0141_alter_report_status"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Theme",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created_date", models.DateTimeField(auto_now_add=True)),
|
||||
("name", models.CharField(max_length=50, unique=True)),
|
||||
("path", models.CharField(max_length=50, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="sitesettings",
|
||||
name="default_theme",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="bookwyrm.theme",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="theme",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="bookwyrm.theme",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
add_default_themes, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
|
@ -26,7 +26,7 @@ from .group import Group, GroupMember, GroupMemberInvitation
|
|||
|
||||
from .import_job import ImportJob, ImportItem
|
||||
|
||||
from .site import SiteSettings, SiteInvite
|
||||
from .site import SiteSettings, Theme, SiteInvite
|
||||
from .site import PasswordReset, InviteRequest
|
||||
from .announcement import Announcement
|
||||
from .antispam import EmailBlocklist, IPBlocklist, AutoMod, automod_task
|
||||
|
|
|
@ -24,6 +24,9 @@ class SiteSettings(models.Model):
|
|||
)
|
||||
instance_description = models.TextField(default="This instance has no description.")
|
||||
instance_short_description = models.CharField(max_length=255, blank=True, null=True)
|
||||
default_theme = models.ForeignKey(
|
||||
"Theme", null=True, blank=True, on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
# admin setup options
|
||||
install_mode = models.BooleanField(default=False)
|
||||
|
@ -104,6 +107,18 @@ class SiteSettings(models.Model):
|
|||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Theme(models.Model):
|
||||
"""Theme files"""
|
||||
|
||||
created_date = models.DateTimeField(auto_now_add=True)
|
||||
name = models.CharField(max_length=50, unique=True)
|
||||
path = models.CharField(max_length=50, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
# pylint: disable=invalid-str-returned
|
||||
return self.name
|
||||
|
||||
|
||||
class SiteInvite(models.Model):
|
||||
"""gives someone access to create an account on the instance"""
|
||||
|
||||
|
|
|
@ -136,6 +136,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
updated_date = models.DateTimeField(auto_now=True)
|
||||
last_active_date = models.DateTimeField(default=timezone.now)
|
||||
manually_approves_followers = fields.BooleanField(default=False)
|
||||
theme = models.ForeignKey("Theme", null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
||||
# options to turn features on and off
|
||||
show_goal = models.BooleanField(default=True)
|
||||
|
@ -172,6 +173,17 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
property_fields = [("following_link", "following")]
|
||||
field_tracker = FieldTracker(fields=["name", "avatar"])
|
||||
|
||||
@property
|
||||
def get_theme(self):
|
||||
"""get the theme given the user/site"""
|
||||
if self.theme:
|
||||
return self.theme.path
|
||||
site_model = apps.get_model("bookwyrm", "SiteSettings", require_ready=True)
|
||||
site = site_model.objects.get()
|
||||
if site.default_theme:
|
||||
return site.default_theme.path
|
||||
return "css/themes/bookwyrm-light.scss"
|
||||
|
||||
@property
|
||||
def confirmation_link(self):
|
||||
"""helper for generating confirmation links"""
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
@charset "utf-8";
|
||||
|
||||
@import "instance-settings";
|
||||
@import "themes/light.scss";
|
||||
@import "vendor/bulma/bulma.sass";
|
||||
@import "vendor/icons.css";
|
||||
@import "bookwyrm/all.scss";
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@import "../vendor/bulma/sass/utilities/initial-variables.sass";
|
||||
|
||||
|
||||
/* Colors
|
||||
******************************************************************************/
|
||||
|
||||
|
@ -77,3 +78,6 @@ $progress-value-background-color: $border-light;
|
|||
******************************************************************************/
|
||||
$family-primary: $family-sans-serif;
|
||||
$family-secondary: $family-sans-serif;
|
||||
|
||||
|
||||
@import "../bookwyrm.scss";
|
|
@ -54,3 +54,6 @@ $invisible-overlay-background-color: rgba($scheme-invert, 0.66);
|
|||
******************************************************************************/
|
||||
$family-primary: $family-sans-serif;
|
||||
$family-secondary: $family-sans-serif;
|
||||
|
||||
|
||||
@import "../bookwyrm.scss";
|
|
@ -8,7 +8,9 @@
|
|||
<head>
|
||||
<title>{% block title %}BookWyrm{% endblock %} - {{ site.name }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="{% sass_src 'css/bookwyrm.scss' %}" rel="stylesheet" type="text/css" />
|
||||
{% with theme_path=user.get_theme %}
|
||||
<link href="{% sass_src 'css/themes/bookwyrm-light.scss' %}" rel="stylesheet" type="text/css" />
|
||||
{% endwith %}
|
||||
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="{% url 'opensearch' %}" title="{% blocktrans with site_name=site.name %}{{ site_name }} search{% endblocktrans %}" />
|
||||
|
||||
|
|
|
@ -86,6 +86,10 @@
|
|||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Site Settings" %}</a>
|
||||
{% block site-subtabs %}{% endblock %}
|
||||
</li>
|
||||
<li>
|
||||
{% url 'settings-themes' as url %}
|
||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Themes" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{% block site-subtabs %}
|
||||
<ul class="menu-list">
|
||||
<li><a href="#instance-info">{% trans "Instance Info" %}</a></li>
|
||||
<li><a href="#images">{% trans "Images" %}</a></li>
|
||||
<li><a href="#display">{% trans "Display" %}</a></li>
|
||||
<li><a href="#footer">{% trans "Footer Content" %}</a></li>
|
||||
<li><a href="#registration">{% trans "Registration" %}</a></li>
|
||||
</ul>
|
||||
|
@ -33,7 +33,12 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="{% url 'settings-site' %}" method="POST" class="content" enctype="multipart/form-data">
|
||||
<form
|
||||
action="{% url 'settings-site' %}"
|
||||
method="POST"
|
||||
class="content"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<section class="block" id="instance_info">
|
||||
<h2 class="title is-4">{% trans "Instance Info" %}</h2>
|
||||
|
@ -68,20 +73,33 @@
|
|||
|
||||
<hr aria-hidden="true">
|
||||
|
||||
<section class="block" id="images">
|
||||
<h2 class="title is-4">{% trans "Images" %}</h2>
|
||||
<div class="box is-flex">
|
||||
<div>
|
||||
<label class="label" for="id_logo">{% trans "Logo:" %}</label>
|
||||
{{ site_form.logo }}
|
||||
<section class="block" id="display">
|
||||
<h2 class="title is-4">{% trans "Display" %}</h2>
|
||||
<div class="box">
|
||||
<h3 class="title is-5">{% trans "Images" %}</h3>
|
||||
<div class="block is-flex">
|
||||
<div>
|
||||
<label class="label" for="id_logo">{% trans "Logo:" %}</label>
|
||||
{{ site_form.logo }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="id_logo_small">{% trans "Logo small:" %}</label>
|
||||
{{ site_form.logo_small }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="id_favicon">{% trans "Favicon:" %}</label>
|
||||
{{ site_form.favicon }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="id_logo_small">{% trans "Logo small:" %}</label>
|
||||
{{ site_form.logo_small }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="label" for="id_favicon">{% trans "Favicon:" %}</label>
|
||||
{{ site_form.favicon }}
|
||||
|
||||
<h3 class="title is-5">{% trans "Themes" %}</h3>
|
||||
<div class="block">
|
||||
<label class="label" for="id_default_theme">
|
||||
{% trans "Default theme:" %}
|
||||
</label>
|
||||
<div class="select">
|
||||
{{ site_form.default_theme }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
112
bookwyrm/templates/settings/themes.html
Normal file
112
bookwyrm/templates/settings/themes.html
Normal file
|
@ -0,0 +1,112 @@
|
|||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Themes" %}{% endblock %}
|
||||
|
||||
{% block header %}{% trans "Themes" %}{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
{% if success %}
|
||||
<div class="notification is-success is-light">
|
||||
<span class="icon icon-check" aria-hidden="true"></span>
|
||||
<span>
|
||||
{% trans "Successfully added theme" %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<section class="block">
|
||||
<div class="notification content">
|
||||
<h2 class="title is-5">{% trans "How to add a theme" %}</h2>
|
||||
<ol>
|
||||
<li>
|
||||
{% trans "Copy the theme file into the <code>bookwyrm/static/css/themes</code> directory on your server from the command line." %}
|
||||
</li>
|
||||
<li>
|
||||
{% trans "Run <code>./bw-dev compilescss</code>." %}
|
||||
</li>
|
||||
<li>
|
||||
{% trans "Add the file name using the form below to make it available in the application interface." %}
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block content">
|
||||
<h2 class="title is-4">{% trans "Add theme" %}</h2>
|
||||
|
||||
{% if theme_form.errors %}
|
||||
<div class="notification is-danger is-light">
|
||||
<span class="icon icon-x" aria-hidden="true"></span>
|
||||
<span>
|
||||
{% trans "Unable to save theme" %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form
|
||||
method="POST"
|
||||
action="{% url 'settings-themes' %}"
|
||||
class="box"
|
||||
enctype="multipart/form-data"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<div class="columns">
|
||||
<div class="column is-half">
|
||||
<label class="label" for="id_name">
|
||||
{% trans "Theme name" %}
|
||||
</label>
|
||||
<div class="control">
|
||||
{{ theme_form.name }}
|
||||
{% include 'snippets/form_errors.html' with errors_list=theme_form.name.errors id="desc_name" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<label class="label" for="id_path">
|
||||
{% trans "Theme filename" %}
|
||||
</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
{{ theme_form.path }}
|
||||
</div>
|
||||
{% include 'snippets/form_errors.html' with errors_list=theme_form.path.errors id="desc_path" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="button">{% trans "Add theme" %}</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="block content">
|
||||
<h2 class="title is-4">{% trans "Available Themes" %}</h2>
|
||||
<div class="table-container">
|
||||
<table class="table is-striped">
|
||||
<tr>
|
||||
<th>
|
||||
{% trans "Theme name" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "File" %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Actions" %}
|
||||
</th>
|
||||
</tr>
|
||||
{% for theme in themes %}
|
||||
<tr>
|
||||
<td>{{ theme.name }}</td>
|
||||
<td><code>{{ theme.path }}</code></td>
|
||||
<td>
|
||||
<form>
|
||||
<button type="submit" class="button is-danger is-light">{% trans "Remove theme" %}</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
|
@ -86,6 +86,7 @@ urlpatterns = [
|
|||
r"^settings/dashboard/?$", views.Dashboard.as_view(), name="settings-dashboard"
|
||||
),
|
||||
re_path(r"^settings/site-settings/?$", views.Site.as_view(), name="settings-site"),
|
||||
re_path(r"^settings/themes/?$", views.Themes.as_view(), name="settings-themes"),
|
||||
re_path(
|
||||
r"^settings/announcements/?$",
|
||||
views.Announcements.as_view(),
|
||||
|
|
|
@ -21,6 +21,7 @@ from .admin.reports import (
|
|||
moderator_delete_user,
|
||||
)
|
||||
from .admin.site import Site
|
||||
from .admin.themes import Themes
|
||||
from .admin.user_admin import UserAdmin, UserAdminList
|
||||
|
||||
# user preferences
|
||||
|
|
|
@ -29,7 +29,7 @@ class Site(View):
|
|||
if not form.is_valid():
|
||||
data = {"site_form": form}
|
||||
return TemplateResponse(request, "settings/site.html", data)
|
||||
form.save()
|
||||
site = form.save()
|
||||
|
||||
data = {"site_form": forms.SiteForm(instance=site), "success": True}
|
||||
return TemplateResponse(request, "settings/site.html", data)
|
||||
|
|
38
bookwyrm/views/admin/themes.py
Normal file
38
bookwyrm/views/admin/themes.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
""" manage themes """
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import forms, models
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
permission_required("bookwyrm.edit_instance_settings", raise_exception=True),
|
||||
name="dispatch",
|
||||
)
|
||||
class Themes(View):
|
||||
"""manage things like the instance name"""
|
||||
|
||||
def get(self, request):
|
||||
"""view existing themes and set defaults"""
|
||||
data = {
|
||||
"themes": models.Theme.objects.all(),
|
||||
"theme_form": forms.ThemeForm(),
|
||||
}
|
||||
return TemplateResponse(request, "settings/themes.html", data)
|
||||
|
||||
def post(self, request):
|
||||
"""edit the site settings"""
|
||||
form = forms.ThemeForm(request.POST, request.FILES)
|
||||
data = {
|
||||
"themes": models.Theme.objects.all(),
|
||||
"theme_form": form,
|
||||
}
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
data["success"] = True
|
||||
data["theme_form"] = forms.ThemeForm()
|
||||
return TemplateResponse(request, "settings/themes.html", data)
|
Loading…
Reference in a new issue