Theme selector
This commit is contained in:
parent
6e96c1eee7
commit
3dfbb3272e
|
@ -454,6 +454,24 @@ class SiteForm(CustomForm):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SiteThemeForm(CustomForm):
|
||||||
|
class Meta:
|
||||||
|
model = models.SiteSettings
|
||||||
|
fields = ["default_theme"]
|
||||||
|
|
||||||
|
|
||||||
|
class ThemeForm(CustomForm):
|
||||||
|
class Meta:
|
||||||
|
model = models.Theme
|
||||||
|
fields = ["name", "path"]
|
||||||
|
widgets = {
|
||||||
|
"name": forms.TextInput(attrs={"aria-describedby": "desc_name"}),
|
||||||
|
"path": ClearableFileInputWithWarning(
|
||||||
|
attrs={"aria-describedby": "desc_path"}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AnnouncementForm(CustomForm):
|
class AnnouncementForm(CustomForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Announcement
|
model = models.Announcement
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# Generated by Django 3.2.12 on 2022-02-26 20:47
|
# Generated by Django 3.2.12 on 2022-02-27 17:52
|
||||||
|
|
||||||
import django.core.validators
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -40,19 +39,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
("created_date", models.DateTimeField(auto_now_add=True)),
|
("created_date", models.DateTimeField(auto_now_add=True)),
|
||||||
("name", models.CharField(max_length=50, unique=True)),
|
("name", models.CharField(max_length=50, unique=True)),
|
||||||
(
|
("path", models.CharField(max_length=50, unique=True)),
|
||||||
"theme_file",
|
|
||||||
models.FileField(
|
|
||||||
null=True,
|
|
||||||
upload_to="css/",
|
|
||||||
validators=[
|
|
||||||
django.core.validators.FileExtensionValidator(
|
|
||||||
["scss", "sass"]
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("path", models.CharField(blank=True, max_length=50, null=True)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
|
@ -3,7 +3,6 @@ import datetime
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.core.validators import FileExtensionValidator
|
|
||||||
from django.db import models, IntegrityError
|
from django.db import models, IntegrityError
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -113,22 +112,12 @@ class Theme(models.Model):
|
||||||
|
|
||||||
created_date = models.DateTimeField(auto_now_add=True)
|
created_date = models.DateTimeField(auto_now_add=True)
|
||||||
name = models.CharField(max_length=50, unique=True)
|
name = models.CharField(max_length=50, unique=True)
|
||||||
theme_file = models.FileField(
|
path = models.CharField(max_length=50, unique=True)
|
||||||
upload_to="css/",
|
|
||||||
validators=[FileExtensionValidator(["scss", "sass"])],
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
path = models.CharField(max_length=50, blank=True, null=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
# pylint: disable=invalid-str-returned
|
# pylint: disable=invalid-str-returned
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
|
||||||
def theme_path(self):
|
|
||||||
"""get the theme given the user/site"""
|
|
||||||
return self.theme_file.path if self.theme_file else self.path
|
|
||||||
|
|
||||||
|
|
||||||
class SiteInvite(models.Model):
|
class SiteInvite(models.Model):
|
||||||
"""gives someone access to create an account on the instance"""
|
"""gives someone access to create an account on the instance"""
|
||||||
|
|
|
@ -176,14 +176,14 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
@property
|
@property
|
||||||
def get_theme(self):
|
def get_theme(self):
|
||||||
"""get the theme given the user/site"""
|
"""get the theme given the user/site"""
|
||||||
|
path = "bookwyrm-light.scss"
|
||||||
if self.theme:
|
if self.theme:
|
||||||
path = self.theme.theme_path
|
path = self.theme.path
|
||||||
else:
|
else:
|
||||||
site_model = apps.get_model("bookwyrm", "SiteSettings", require_ready=True)
|
site_model = apps.get_model("bookwyrm", "SiteSettings", require_ready=True)
|
||||||
site = site_model.objects.get()
|
site = site_model.objects.get()
|
||||||
if site.default_theme:
|
if site.default_theme:
|
||||||
path = site.default_theme.theme_path
|
path = site.default_theme.path
|
||||||
path = path or "light.scss"
|
|
||||||
return f"css/{path}"
|
return f"css/{path}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -86,6 +86,10 @@
|
||||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Site Settings" %}</a>
|
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Site Settings" %}</a>
|
||||||
{% block site-subtabs %}{% endblock %}
|
{% block site-subtabs %}{% endblock %}
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -33,7 +33,12 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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 %}
|
{% csrf_token %}
|
||||||
<section class="block" id="instance_info">
|
<section class="block" id="instance_info">
|
||||||
<h2 class="title is-4">{% trans "Instance Info" %}</h2>
|
<h2 class="title is-4">{% trans "Instance Info" %}</h2>
|
||||||
|
@ -95,8 +100,6 @@
|
||||||
<div class="select">
|
<div class="select">
|
||||||
{{ site_form.default_theme }}
|
{{ site_form.default_theme }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="button">{% trans "Upload theme" %}</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
{% 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">
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "Default theme:" %}</strong> {{ site.default_theme.name }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="{% url 'settings-site' %}#display">
|
||||||
|
{% trans "Set default theme" %}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="block content">
|
||||||
|
<h2 class="title is-4">{% trans "Upload 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_theme_file">
|
||||||
|
{% trans "Theme file" %}
|
||||||
|
</label>
|
||||||
|
<div class="control">
|
||||||
|
{{ theme_form.theme_file }}
|
||||||
|
{% include 'snippets/form_errors.html' with errors_list=theme_form.theme_file.errors id="desc_theme_file" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="button">{% trans "Upload 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.theme_path }}</code></td>
|
||||||
|
<td>
|
||||||
|
{% if theme.theme_file %}
|
||||||
|
<form>
|
||||||
|
<button type="submit" class="button is-danger is-light">{% trans "Delete" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -86,6 +86,7 @@ urlpatterns = [
|
||||||
r"^settings/dashboard/?$", views.Dashboard.as_view(), name="settings-dashboard"
|
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/site-settings/?$", views.Site.as_view(), name="settings-site"),
|
||||||
|
re_path(r"^settings/themes/?$", views.Themes.as_view(), name="settings-themes"),
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/announcements/?$",
|
r"^settings/announcements/?$",
|
||||||
views.Announcements.as_view(),
|
views.Announcements.as_view(),
|
||||||
|
|
|
@ -21,6 +21,7 @@ from .admin.reports import (
|
||||||
moderator_delete_user,
|
moderator_delete_user,
|
||||||
)
|
)
|
||||||
from .admin.site import Site
|
from .admin.site import Site
|
||||||
|
from .admin.themes import Themes
|
||||||
from .admin.user_admin import UserAdmin, UserAdminList
|
from .admin.user_admin import UserAdmin, UserAdminList
|
||||||
|
|
||||||
# user preferences
|
# user preferences
|
||||||
|
|
|
@ -29,7 +29,7 @@ class Site(View):
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
data = {"site_form": form}
|
data = {"site_form": form}
|
||||||
return TemplateResponse(request, "settings/site.html", data)
|
return TemplateResponse(request, "settings/site.html", data)
|
||||||
form.save()
|
site = form.save()
|
||||||
|
|
||||||
data = {"site_form": forms.SiteForm(instance=site), "success": True}
|
data = {"site_form": forms.SiteForm(instance=site), "success": True}
|
||||||
return TemplateResponse(request, "settings/site.html", data)
|
return TemplateResponse(request, "settings/site.html", data)
|
||||||
|
|
|
@ -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 New Issue