forked from mirrors/bookwyrm
Merge branch 'themes' into dark-theme
This commit is contained in:
commit
2c8fa5cd9b
35 changed files with 289 additions and 109 deletions
|
@ -1,3 +0,0 @@
|
||||||
@charset "utf-8";
|
|
||||||
|
|
||||||
// Copy this file to bookwyrm/static/css/ and set your instance custom styles.
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -17,7 +17,8 @@
|
||||||
.env
|
.env
|
||||||
/images/
|
/images/
|
||||||
bookwyrm/static/css/bookwyrm.css
|
bookwyrm/static/css/bookwyrm.css
|
||||||
bookwyrm/static/css/_instance-settings.scss
|
bookwyrm/static/css/themes/
|
||||||
|
!bookwyrm/static/css/themes/bookwyrm-*.scss
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
.coverage
|
.coverage
|
||||||
|
|
|
@ -13,7 +13,7 @@ Social reading and reviewing, decentralized with ActivityPub
|
||||||
- [Set up Bookwyrm](#set-up-bookwyrm)
|
- [Set up Bookwyrm](#set-up-bookwyrm)
|
||||||
|
|
||||||
## Joining BookWyrm
|
## Joining BookWyrm
|
||||||
BookWyrm is still a young piece of software, and isn't at the level of stability and feature-richness that you'd find in a production-ready application. But it does what it says on the box! If you'd like to join an instance, you can check out the [instances](https://docs.joinbookwyrm.com/instances.html) list.
|
BookWyrm is still a young piece of software, and isn't at the level of stability and feature-richness that you'd find in a production-ready application. But it does what it says on the box! If you'd like to join an instance, you can check out the [instances](https://joinbookwyrm.com/instances/) list.
|
||||||
|
|
||||||
You can request an invite by entering your email address at https://bookwyrm.social.
|
You can request an invite by entering your email address at https://bookwyrm.social.
|
||||||
|
|
||||||
|
|
|
@ -39,4 +39,5 @@ class Person(ActivityObject):
|
||||||
bookwyrmUser: bool = False
|
bookwyrmUser: bool = False
|
||||||
manuallyApprovesFollowers: str = False
|
manuallyApprovesFollowers: str = False
|
||||||
discoverable: str = False
|
discoverable: str = False
|
||||||
|
hideFollows: str = False
|
||||||
type: str = "Person"
|
type: str = "Person"
|
||||||
|
|
|
@ -153,8 +153,10 @@ class EditUserForm(CustomForm):
|
||||||
"manually_approves_followers",
|
"manually_approves_followers",
|
||||||
"default_post_privacy",
|
"default_post_privacy",
|
||||||
"discoverable",
|
"discoverable",
|
||||||
|
"hide_follows",
|
||||||
"preferred_timezone",
|
"preferred_timezone",
|
||||||
"preferred_language",
|
"preferred_language",
|
||||||
|
"theme",
|
||||||
]
|
]
|
||||||
help_texts = {f: None for f in fields}
|
help_texts = {f: None for f in fields}
|
||||||
widgets = {
|
widgets = {
|
||||||
|
|
19
bookwyrm/migrations/0142_user_hide_follows.py
Normal file
19
bookwyrm/migrations/0142_user_hide_follows.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 3.2.12 on 2022-02-28 19:44
|
||||||
|
|
||||||
|
import bookwyrm.models.fields
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0141_alter_report_status"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="hide_follows",
|
||||||
|
field=bookwyrm.models.fields.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Generated by Django 3.2.12 on 2022-02-28 21:28
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0142_auto_20220227_1752"),
|
||||||
|
("bookwyrm", "0142_user_hide_follows"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
|
@ -227,7 +227,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
||||||
@classmethod
|
@classmethod
|
||||||
def privacy_filter(cls, viewer, privacy_levels=None):
|
def privacy_filter(cls, viewer, privacy_levels=None):
|
||||||
queryset = super().privacy_filter(viewer, privacy_levels=privacy_levels)
|
queryset = super().privacy_filter(viewer, privacy_levels=privacy_levels)
|
||||||
return queryset.filter(deleted=False)
|
return queryset.filter(deleted=False, user__is_active=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def direct_filter(cls, queryset, viewer):
|
def direct_filter(cls, queryset, viewer):
|
||||||
|
|
|
@ -137,6 +137,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
last_active_date = models.DateTimeField(default=timezone.now)
|
last_active_date = models.DateTimeField(default=timezone.now)
|
||||||
manually_approves_followers = fields.BooleanField(default=False)
|
manually_approves_followers = fields.BooleanField(default=False)
|
||||||
theme = models.ForeignKey("Theme", null=True, blank=True, on_delete=models.SET_NULL)
|
theme = models.ForeignKey("Theme", null=True, blank=True, on_delete=models.SET_NULL)
|
||||||
|
hide_follows = fields.BooleanField(default=False)
|
||||||
|
|
||||||
# options to turn features on and off
|
# options to turn features on and off
|
||||||
show_goal = models.BooleanField(default=True)
|
show_goal = models.BooleanField(default=True)
|
||||||
|
@ -490,10 +491,13 @@ def set_remote_server(user_id):
|
||||||
get_remote_reviews.delay(user.outbox)
|
get_remote_reviews.delay(user.outbox)
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_remote_server(domain):
|
def get_or_create_remote_server(domain, refresh=False):
|
||||||
"""get info on a remote server"""
|
"""get info on a remote server"""
|
||||||
|
server = FederatedServer()
|
||||||
try:
|
try:
|
||||||
return FederatedServer.objects.get(server_name=domain)
|
server = FederatedServer.objects.get(server_name=domain)
|
||||||
|
if not refresh:
|
||||||
|
return server
|
||||||
except FederatedServer.DoesNotExist:
|
except FederatedServer.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -508,13 +512,15 @@ def get_or_create_remote_server(domain):
|
||||||
application_type = data.get("software", {}).get("name")
|
application_type = data.get("software", {}).get("name")
|
||||||
application_version = data.get("software", {}).get("version")
|
application_version = data.get("software", {}).get("version")
|
||||||
except ConnectorException:
|
except ConnectorException:
|
||||||
|
if server.id:
|
||||||
|
return server
|
||||||
application_type = application_version = None
|
application_type = application_version = None
|
||||||
|
|
||||||
server = FederatedServer.objects.create(
|
server.server_name = domain
|
||||||
server_name=domain,
|
server.application_type = application_type
|
||||||
application_type=application_type,
|
server.application_version = application_version
|
||||||
application_version=application_version,
|
|
||||||
)
|
server.save()
|
||||||
return server
|
return server
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
@charset "utf-8";
|
@charset "utf-8";
|
||||||
|
|
||||||
@import "instance-settings";
|
|
||||||
@import "vendor/bulma/bulma.sass";
|
@import "vendor/bulma/bulma.sass";
|
||||||
@import "vendor/icons.css";
|
|
||||||
@import "bookwyrm/all.scss";
|
@import "bookwyrm/all.scss";
|
||||||
|
|
|
@ -80,4 +80,5 @@ $family-primary: $family-sans-serif;
|
||||||
$family-secondary: $family-sans-serif;
|
$family-secondary: $family-sans-serif;
|
||||||
|
|
||||||
|
|
||||||
@import "../bookwyrm.scss";
|
@import "../bookwyrm.scss";
|
||||||
|
@import "../vendor/icons.css";
|
||||||
|
|
|
@ -55,5 +55,5 @@ $invisible-overlay-background-color: rgba($scheme-invert, 0.66);
|
||||||
$family-primary: $family-sans-serif;
|
$family-primary: $family-sans-serif;
|
||||||
$family-secondary: $family-sans-serif;
|
$family-secondary: $family-sans-serif;
|
||||||
|
|
||||||
|
@import "../bookwyrm.scss";
|
||||||
@import "../bookwyrm.scss";
|
@import "../vendor/icons.css";
|
||||||
|
|
|
@ -352,7 +352,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% if request.user.list_set.exists %}
|
{% if list_options.exists %}
|
||||||
<form name="list-add" method="post" action="{% url 'list-add-book' %}">
|
<form name="list-add" method="post" action="{% url 'list-add-book' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="book" value="{{ book.id }}">
|
<input type="hidden" name="book" value="{{ book.id }}">
|
||||||
|
@ -361,7 +361,7 @@
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<div class="select control is-clipped">
|
<div class="select control is-clipped">
|
||||||
<select name="book_list" id="id_list">
|
<select name="book_list" id="id_list">
|
||||||
{% for list in user.list_set.all %}
|
{% for list in list_options %}
|
||||||
<option value="{{ list.id }}">{{ list.name }}</option>
|
<option value="{{ list.id }}">{{ list.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</div>
|
</div>
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
{% if user != request.user %}
|
{% if user != request.user %}
|
||||||
{% if user.mutuals %}
|
{% if user.mutuals and not user.hide_follows %}
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
<div class="has-text-centered">
|
<div class="has-text-centered">
|
||||||
<p class="title is-6 mb-0">{{ user.mutuals }}</p>
|
<p class="title is-6 mb-0">{{ user.mutuals }}</p>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<span title="@{{ user|username }}" class="is-block pb-3">@{{ user|username|truncatechars:8 }}</span>
|
<span title="@{{ user|username }}" class="is-block pb-3">@{{ user|username|truncatechars:8 }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% include 'snippets/add_to_group_button.html' with user=user group=group %}
|
{% include 'snippets/add_to_group_button.html' with user=user group=group %}
|
||||||
{% if user.mutuals %}
|
{% if user.mutuals and not user.hide_follows %}
|
||||||
<p class="help">
|
<p class="help">
|
||||||
{% blocktrans trimmed with mutuals=user.mutuals|intcomma count counter=user.mutuals %}
|
{% blocktrans trimmed with mutuals=user.mutuals|intcomma count counter=user.mutuals %}
|
||||||
{{ mutuals }} follower you follow
|
{{ mutuals }} follower you follow
|
||||||
|
|
|
@ -30,13 +30,23 @@
|
||||||
|
|
||||||
<div class="columns mt-3">
|
<div class="columns mt-3">
|
||||||
<section class="column is-three-quarters">
|
<section class="column is-three-quarters">
|
||||||
{% if request.GET.updated %}
|
{% if add_failed %}
|
||||||
<div class="notification is-primary">
|
<div class="notification is-danger is-light">
|
||||||
{% if list.curation != "open" and request.user != list.user and not list.group|is_member:request.user %}
|
<span class="icon icon-x" aria-hidden="true"></span>
|
||||||
{% trans "You successfully suggested a book for this list!" %}
|
<span>
|
||||||
{% else %}
|
{% trans "That book is already on this list." %}
|
||||||
{% trans "You successfully added a book to this list!" %}
|
</span>
|
||||||
{% endif %}
|
</div>
|
||||||
|
{% elif add_succeeded %}
|
||||||
|
<div class="notification is-success is-light">
|
||||||
|
<span class="icon icon-check" aria-hidden="true"></span>
|
||||||
|
<span>
|
||||||
|
{% if list.curation != "open" and request.user != list.user and not list.group|is_member:request.user %}
|
||||||
|
{% trans "You successfully suggested a book for this list!" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "You successfully added a book to this list!" %}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,12 @@
|
||||||
{{ form.preferred_language }}
|
{{ form.preferred_language }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="id_them">{% trans "Theme:" %}</label>
|
||||||
|
<div class="select">
|
||||||
|
{{ form.theme }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -111,6 +117,12 @@
|
||||||
{% trans "Manually approve followers" %}
|
{% trans "Manually approve followers" %}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="checkbox label" for="id_hide_follows">
|
||||||
|
{{ form.hide_follows }}
|
||||||
|
{% trans "Hide followers and following on profile" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_default_post_privacy">
|
<label class="label" for="id_default_post_privacy">
|
||||||
{% trans "Default post privacy:" %}
|
{% trans "Default post privacy:" %}
|
||||||
|
|
|
@ -4,7 +4,19 @@
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
{% trans "Add instance" %}
|
{% trans "Add instance" %}
|
||||||
<a href="{% url 'settings-federation' %}" class="has-text-weight-normal help">{% trans "Back to instance list" %}</a>
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><a href="{% url 'settings-federation' %}">{% trans "Federated Instances" %}</a></li>
|
||||||
|
<li class="is-active">
|
||||||
|
<a href="#" aria-current="page">
|
||||||
|
{% trans "Add instance" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
|
@ -73,9 +85,13 @@
|
||||||
<label class="label" for="id_notes">
|
<label class="label" for="id_notes">
|
||||||
{% trans "Notes:" %}
|
{% trans "Notes:" %}
|
||||||
</label>
|
</label>
|
||||||
<textarea name="notes" cols="40" rows="5" class="textarea" id="id_notes">
|
<textarea
|
||||||
{{ form.notes.value|default:'' }}
|
name="notes"
|
||||||
</textarea>
|
cols="40"
|
||||||
|
rows="5"
|
||||||
|
class="textarea"
|
||||||
|
id="id_notes"
|
||||||
|
>{{ form.notes.value|default:'' }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="button is-primary">
|
<button type="submit" class="button is-primary">
|
||||||
|
|
|
@ -9,8 +9,26 @@
|
||||||
|
|
||||||
{% if server.status == "blocked" %}<span class="icon icon-x has-text-danger is-size-5" title="{% trans 'Blocked' %}"><span class="is-sr-only">{% trans "Blocked" %}</span></span>
|
{% if server.status == "blocked" %}<span class="icon icon-x has-text-danger is-size-5" title="{% trans 'Blocked' %}"><span class="is-sr-only">{% trans "Blocked" %}</span></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<a href="{% url 'settings-federation' %}" class="has-text-weight-normal help">{% trans "Back to list" %}</a>
|
{% block edit-button %}
|
||||||
|
<form name="reload" method="POST" action="{% url 'settings-federated-server-refresh' server.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button class="button" type="submit">{% trans "Refresh data" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><a href="{% url 'settings-federation' %}">{% trans "Federated Instances" %}</a></li>
|
||||||
|
<li class="is-active">
|
||||||
|
<a href="#" aria-current="page">
|
||||||
|
{{ server.server_name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
|
|
|
@ -4,7 +4,19 @@
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
{% trans "Import Blocklist" %}
|
{% trans "Import Blocklist" %}
|
||||||
<a href="{% url 'settings-federation' %}" class="has-text-weight-normal help">{% trans "Back to instance list" %}</a>
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><a href="{% url 'settings-federation' %}">{% trans "Federated Instances" %}</a></li>
|
||||||
|
<li class="is-active">
|
||||||
|
<a href="#" aria-current="page">
|
||||||
|
{% trans "Import Blocklist" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
|
|
|
@ -16,11 +16,11 @@
|
||||||
<ul>
|
<ul>
|
||||||
{% url 'settings-federation' status='federated' as url %}
|
{% url 'settings-federation' status='federated' as url %}
|
||||||
<li {% if request.path in url %}class="is-active" aria-current="page"{% endif %}>
|
<li {% if request.path in url %}class="is-active" aria-current="page"{% endif %}>
|
||||||
<a href="{{ url }}">{% trans "Federated" %}</a>
|
<a href="{{ url }}">{% trans "Federated" %} ({{ federated_count }})</a>
|
||||||
</li>
|
</li>
|
||||||
{% url 'settings-federation' status='blocked' as url %}
|
{% url 'settings-federation' status='blocked' as url %}
|
||||||
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||||
<a href="{{ url }}">{% trans "Blocked" %}</a>
|
<a href="{{ url }}">{% trans "Blocked" %} ({{ blocked_count }})</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,6 +5,12 @@
|
||||||
|
|
||||||
{% block header %}{% trans "Themes" %}{% endblock %}
|
{% block header %}{% trans "Themes" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<a class="subtitle help is-link" href="{% url 'settings-site' %}/#display">
|
||||||
|
{% trans "Set instance default theme" %}
|
||||||
|
</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
{% if success %}
|
{% if success %}
|
||||||
<div class="notification is-success is-light">
|
<div class="notification is-success is-light">
|
||||||
|
@ -117,8 +123,12 @@
|
||||||
<td>{{ theme.name }}</td>
|
<td>{{ theme.name }}</td>
|
||||||
<td><code>{{ theme.path }}</code></td>
|
<td><code>{{ theme.path }}</code></td>
|
||||||
<td>
|
<td>
|
||||||
<form>
|
<form method="POST" action="{% url 'settings-themes-delete' theme.id %}">
|
||||||
<button type="submit" class="button is-danger is-light">{% trans "Remove theme" %}</button>
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="button is-danger is-light is-small">
|
||||||
|
<span class="icon icon-x" aria-hideen="true"></span>
|
||||||
|
<span>{% trans "Remove theme" %}</span>
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -71,7 +71,9 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="book" value="{{ book.id }}">
|
<input type="hidden" name="book" value="{{ book.id }}">
|
||||||
<input type="hidden" name="shelf" value="{{ user_shelf.id }}">
|
<input type="hidden" name="shelf" value="{{ user_shelf.id }}">
|
||||||
<button class="button is-fullwidth is-small is-radiusless is-danger is-light" type="submit">{% trans "Remove from" %} {{ user_shelf.name }}</button>
|
<button class="button is-fullwidth is-small is-radiusless is-danger is-light" type="submit">
|
||||||
|
{% blocktrans with name=user_shelf|translate_shelf_name %}Remove from {{ name }}{% endblocktrans %}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
<input type="hidden" name="book" value="{{ active_shelf.book.id }}">
|
<input type="hidden" name="book" value="{{ active_shelf.book.id }}">
|
||||||
<input type="hidden" name="shelf" value="{{ active_shelf.shelf.id }}">
|
<input type="hidden" name="shelf" value="{{ active_shelf.shelf.id }}">
|
||||||
<button class="button is-fullwidth is-small{% if dropdown %} is-radiusless{% endif %} is-danger is-light" type="submit">
|
<button class="button is-fullwidth is-small{% if dropdown %} is-radiusless{% endif %} is-danger is-light" type="submit">
|
||||||
{% blocktrans with name=active_shelf.shelf.name %}Remove from {{ name }}{% endblocktrans %}
|
{% blocktrans with name=active_shelf.shelf|translate_shelf_name %}Remove from {{ name }}{% endblocktrans %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<span title="@{{ user|username }}" class="is-block pb-3">@{{ user|username|truncatechars:8 }}</span>
|
<span title="@{{ user|username }}" class="is-block pb-3">@{{ user|username|truncatechars:8 }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% include 'snippets/follow_button.html' with user=user minimal=True %}
|
{% include 'snippets/follow_button.html' with user=user minimal=True %}
|
||||||
{% if user.mutuals %}
|
{% if user.mutuals and not user.hide_follows %}
|
||||||
<p class="help">
|
<p class="help">
|
||||||
{% blocktrans trimmed with mutuals=user.mutuals|intcomma count counter=user.mutuals %}
|
{% blocktrans trimmed with mutuals=user.mutuals|intcomma count counter=user.mutuals %}
|
||||||
{{ mutuals }} follower you follow
|
{{ mutuals }} follower you follow
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% if shelf.identifier == 'all' %}
|
{% load shelf_tags %}
|
||||||
{% trans "All books" %}
|
|
||||||
{% elif shelf.identifier == 'to-read' %}
|
{{ shelf|translate_shelf_name }}
|
||||||
{% trans "To Read" %}
|
|
||||||
{% elif shelf.identifier == 'reading' %}
|
|
||||||
{% trans "Currently Reading" %}
|
|
||||||
{% elif shelf.identifier == 'read' %}
|
|
||||||
{% trans "Read" %}
|
|
||||||
{% else %}
|
|
||||||
{{ shelf.name }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
|
@ -28,16 +28,22 @@
|
||||||
|
|
||||||
{% elif request.user.is_authenticated %}
|
{% elif request.user.is_authenticated %}
|
||||||
|
|
||||||
{% mutuals_count user as mutuals %}
|
{% if user.hide_follows %}
|
||||||
<a href="{% url 'user-followers' user|username %}">
|
{% if request.user in user.following.all %}
|
||||||
{% if mutuals %}
|
{% trans "Follows you" %}
|
||||||
{% blocktrans with mutuals_display=mutuals|intcomma count counter=mutuals %}{{ mutuals_display }} follower you follow{% plural %}{{ mutuals_display }} followers you follow{% endblocktrans %}
|
|
||||||
{% elif request.user in user.following.all %}
|
|
||||||
{% trans "Follows you" %}
|
|
||||||
{% else %}
|
|
||||||
{% trans "No followers you follow" %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
{% else %}
|
||||||
|
{% mutuals_count user as mutuals %}
|
||||||
|
<a href="{% url 'user-followers' user|username %}">
|
||||||
|
{% if mutuals %}
|
||||||
|
{% blocktrans with mutuals_display=mutuals|intcomma count counter=mutuals %}{{ mutuals_display }} follower you follow{% plural %}{{ mutuals_display }} followers you follow{% endblocktrans %}
|
||||||
|
{% elif request.user in user.following.all %}
|
||||||
|
{% trans "Follows you" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "No followers you follow" %}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
""" Filters and tags related to shelving books """
|
""" Filters and tags related to shelving books """
|
||||||
from django import template
|
from django import template
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.utils import cache
|
from bookwyrm.utils import cache
|
||||||
|
@ -32,6 +33,24 @@ def get_next_shelf(current_shelf):
|
||||||
return "to-read"
|
return "to-read"
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="translate_shelf_name")
|
||||||
|
def get_translated_shelf_name(shelf):
|
||||||
|
"""produced translated shelf nidentifierame"""
|
||||||
|
if not shelf:
|
||||||
|
return ""
|
||||||
|
# support obj or dict
|
||||||
|
identifier = shelf["identifier"] if isinstance(shelf, dict) else shelf.identifier
|
||||||
|
if identifier == "all":
|
||||||
|
return _("All books")
|
||||||
|
if identifier == "to-read":
|
||||||
|
return _("To Read")
|
||||||
|
if identifier == "reading":
|
||||||
|
return _("Currently Reading")
|
||||||
|
if identifier == "read":
|
||||||
|
return _("Read")
|
||||||
|
return shelf["name"] if isinstance(shelf, dict) else shelf.name
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def active_shelf(context, book):
|
def active_shelf(context, book):
|
||||||
"""check what shelf a user has a book on, if any"""
|
"""check what shelf a user has a book on, if any"""
|
||||||
|
|
|
@ -87,6 +87,11 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
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(r"^settings/themes/?$", views.Themes.as_view(), name="settings-themes"),
|
||||||
|
re_path(
|
||||||
|
r"^settings/themes/(?P<theme_id>\d+)/delete/?$",
|
||||||
|
views.delete_theme,
|
||||||
|
name="settings-themes-delete",
|
||||||
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/announcements/?$",
|
r"^settings/announcements/?$",
|
||||||
views.Announcements.as_view(),
|
views.Announcements.as_view(),
|
||||||
|
@ -145,6 +150,11 @@ urlpatterns = [
|
||||||
views.unblock_server,
|
views.unblock_server,
|
||||||
name="settings-federated-server-unblock",
|
name="settings-federated-server-unblock",
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
r"^settings/federation/(?P<server>\d+)/refresh/?$",
|
||||||
|
views.refresh_server,
|
||||||
|
name="settings-federated-server-refresh",
|
||||||
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/federation/add/?$",
|
r"^settings/federation/add/?$",
|
||||||
views.AddFederatedServer.as_view(),
|
views.AddFederatedServer.as_view(),
|
||||||
|
|
|
@ -6,7 +6,7 @@ from .admin.automod import AutoMod, automod_delete, run_automod
|
||||||
from .admin.dashboard import Dashboard
|
from .admin.dashboard import Dashboard
|
||||||
from .admin.federation import Federation, FederatedServer
|
from .admin.federation import Federation, FederatedServer
|
||||||
from .admin.federation import AddFederatedServer, ImportServerBlocklist
|
from .admin.federation import AddFederatedServer, ImportServerBlocklist
|
||||||
from .admin.federation import block_server, unblock_server
|
from .admin.federation import block_server, unblock_server, refresh_server
|
||||||
from .admin.email_blocklist import EmailBlocklist
|
from .admin.email_blocklist import EmailBlocklist
|
||||||
from .admin.ip_blocklist import IPBlocklist
|
from .admin.ip_blocklist import IPBlocklist
|
||||||
from .admin.invite import ManageInvites, Invite, InviteRequest
|
from .admin.invite import ManageInvites, Invite, InviteRequest
|
||||||
|
@ -21,7 +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.themes import Themes, delete_theme
|
||||||
from .admin.user_admin import UserAdmin, UserAdminList
|
from .admin.user_admin import UserAdmin, UserAdminList
|
||||||
|
|
||||||
# user preferences
|
# user preferences
|
||||||
|
|
|
@ -11,6 +11,7 @@ from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
|
from bookwyrm.models.user import get_or_create_remote_server
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable= no-self-use
|
# pylint: disable= no-self-use
|
||||||
|
@ -37,6 +38,12 @@ class Federation(View):
|
||||||
page = paginated.get_page(request.GET.get("page"))
|
page = paginated.get_page(request.GET.get("page"))
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
"federated_count": models.FederatedServer.objects.filter(
|
||||||
|
status="federated"
|
||||||
|
).count(),
|
||||||
|
"blocked_count": models.FederatedServer.objects.filter(
|
||||||
|
status="blocked"
|
||||||
|
).count(),
|
||||||
"servers": page,
|
"servers": page,
|
||||||
"page_range": paginated.get_elided_page_range(
|
"page_range": paginated.get_elided_page_range(
|
||||||
page.number, on_each_side=2, on_ends=1
|
page.number, on_each_side=2, on_ends=1
|
||||||
|
@ -157,3 +164,14 @@ def unblock_server(request, server):
|
||||||
server = get_object_or_404(models.FederatedServer, id=server)
|
server = get_object_or_404(models.FederatedServer, id=server)
|
||||||
server.unblock()
|
server.unblock()
|
||||||
return redirect("settings-federated-server", server.id)
|
return redirect("settings-federated-server", server.id)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
@permission_required("bookwyrm.control_federation", raise_exception=True)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def refresh_server(request, server):
|
||||||
|
"""unblock a server"""
|
||||||
|
server = get_object_or_404(models.FederatedServer, id=server)
|
||||||
|
get_or_create_remote_server(server.server_name, refresh=True)
|
||||||
|
return redirect("settings-federated-server", server.id)
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
from django.contrib.staticfiles.utils import get_files
|
from django.contrib.staticfiles.utils import get_files
|
||||||
from django.contrib.staticfiles.storage import StaticFilesStorage
|
from django.contrib.staticfiles.storage import StaticFilesStorage
|
||||||
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
|
|
||||||
|
@ -46,3 +48,12 @@ def get_view_data():
|
||||||
"choices": [c for c in choices if c not in current and c[-5:] == ".scss"],
|
"choices": [c for c in choices if c not in current and c[-5:] == ".scss"],
|
||||||
"theme_form": forms.ThemeForm(),
|
"theme_form": forms.ThemeForm(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
@permission_required("bookwyrm.edit_instance_settings", raise_exception=True)
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def delete_theme(request, theme_id):
|
||||||
|
"""Remove a theme"""
|
||||||
|
get_object_or_404(models.Theme, id=theme_id).delete()
|
||||||
|
return redirect("settings-themes")
|
||||||
|
|
|
@ -83,6 +83,7 @@ class Book(View):
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
|
data["list_options"] = request.user.list_set.exclude(id__in=data["lists"])
|
||||||
data["file_link_form"] = forms.FileLinkForm()
|
data["file_link_form"] = forms.FileLinkForm()
|
||||||
readthroughs = models.ReadThrough.objects.filter(
|
readthroughs = models.ReadThrough.objects.filter(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
""" book list views"""
|
""" book list views"""
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import urlencode
|
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db import IntegrityError, transaction
|
from django.db import transaction
|
||||||
from django.db.models import Avg, DecimalField, Q, Max
|
from django.db.models import Avg, DecimalField, Q, Max
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.http import HttpResponseBadRequest, HttpResponse
|
from django.http import HttpResponseBadRequest, HttpResponse
|
||||||
|
@ -26,7 +25,7 @@ from bookwyrm.views.helpers import is_api_request
|
||||||
class List(View):
|
class List(View):
|
||||||
"""book list page"""
|
"""book list page"""
|
||||||
|
|
||||||
def get(self, request, list_id):
|
def get(self, request, list_id, add_failed=False, add_succeeded=False):
|
||||||
"""display a book list"""
|
"""display a book list"""
|
||||||
book_list = get_object_or_404(models.List, id=list_id)
|
book_list = get_object_or_404(models.List, id=list_id)
|
||||||
book_list.raise_visible_to_user(request.user)
|
book_list.raise_visible_to_user(request.user)
|
||||||
|
@ -37,33 +36,10 @@ class List(View):
|
||||||
query = request.GET.get("q")
|
query = request.GET.get("q")
|
||||||
suggestions = None
|
suggestions = None
|
||||||
|
|
||||||
# sort_by shall be "order" unless a valid alternative is given
|
items = book_list.listitem_set.filter(approved=True).prefetch_related(
|
||||||
sort_by = request.GET.get("sort_by", "order")
|
"user", "book", "book__authors"
|
||||||
if sort_by not in ("order", "title", "rating"):
|
)
|
||||||
sort_by = "order"
|
items = sort_list(request, items)
|
||||||
|
|
||||||
# direction shall be "ascending" unless a valid alternative is given
|
|
||||||
direction = request.GET.get("direction", "ascending")
|
|
||||||
if direction not in ("ascending", "descending"):
|
|
||||||
direction = "ascending"
|
|
||||||
|
|
||||||
directional_sort_by = {
|
|
||||||
"order": "order",
|
|
||||||
"title": "book__title",
|
|
||||||
"rating": "average_rating",
|
|
||||||
}[sort_by]
|
|
||||||
if direction == "descending":
|
|
||||||
directional_sort_by = "-" + directional_sort_by
|
|
||||||
|
|
||||||
items = book_list.listitem_set.prefetch_related("user", "book", "book__authors")
|
|
||||||
if sort_by == "rating":
|
|
||||||
items = items.annotate(
|
|
||||||
average_rating=Avg(
|
|
||||||
Coalesce("book__review__rating", 0.0),
|
|
||||||
output_field=DecimalField(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
items = items.filter(approved=True).order_by(directional_sort_by)
|
|
||||||
|
|
||||||
paginated = Paginator(items, PAGE_LENGTH)
|
paginated = Paginator(items, PAGE_LENGTH)
|
||||||
|
|
||||||
|
@ -106,10 +82,10 @@ class List(View):
|
||||||
"suggested_books": suggestions,
|
"suggested_books": suggestions,
|
||||||
"list_form": forms.ListForm(instance=book_list),
|
"list_form": forms.ListForm(instance=book_list),
|
||||||
"query": query or "",
|
"query": query or "",
|
||||||
"sort_form": forms.SortListForm(
|
"sort_form": forms.SortListForm(request.GET),
|
||||||
{"direction": direction, "sort_by": sort_by}
|
|
||||||
),
|
|
||||||
"embed_url": embed_url,
|
"embed_url": embed_url,
|
||||||
|
"add_failed": add_failed,
|
||||||
|
"add_succeeded": add_succeeded,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, "lists/list.html", data)
|
return TemplateResponse(request, "lists/list.html", data)
|
||||||
|
|
||||||
|
@ -131,6 +107,36 @@ class List(View):
|
||||||
return redirect(book_list.local_path)
|
return redirect(book_list.local_path)
|
||||||
|
|
||||||
|
|
||||||
|
def sort_list(request, items):
|
||||||
|
"""helper to handle the surprisngly involved sorting"""
|
||||||
|
# sort_by shall be "order" unless a valid alternative is given
|
||||||
|
sort_by = request.GET.get("sort_by", "order")
|
||||||
|
if sort_by not in ("order", "title", "rating"):
|
||||||
|
sort_by = "order"
|
||||||
|
|
||||||
|
# direction shall be "ascending" unless a valid alternative is given
|
||||||
|
direction = request.GET.get("direction", "ascending")
|
||||||
|
if direction not in ("ascending", "descending"):
|
||||||
|
direction = "ascending"
|
||||||
|
|
||||||
|
directional_sort_by = {
|
||||||
|
"order": "order",
|
||||||
|
"title": "book__title",
|
||||||
|
"rating": "average_rating",
|
||||||
|
}[sort_by]
|
||||||
|
if direction == "descending":
|
||||||
|
directional_sort_by = "-" + directional_sort_by
|
||||||
|
|
||||||
|
if sort_by == "rating":
|
||||||
|
items = items.annotate(
|
||||||
|
average_rating=Avg(
|
||||||
|
Coalesce("book__review__rating", 0.0),
|
||||||
|
output_field=DecimalField(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return items.order_by(directional_sort_by)
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
@login_required
|
@login_required
|
||||||
def save_list(request, list_id):
|
def save_list(request, list_id):
|
||||||
|
@ -179,8 +185,8 @@ def add_book(request):
|
||||||
|
|
||||||
form = forms.ListItemForm(request.POST)
|
form = forms.ListItemForm(request.POST)
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
# this shouldn't happen, there aren't validated fields
|
return List().get(request, book_list.id, add_failed=True)
|
||||||
raise Exception(form.errors)
|
|
||||||
item = form.save(commit=False)
|
item = form.save(commit=False)
|
||||||
|
|
||||||
if book_list.curation == "curated":
|
if book_list.curation == "curated":
|
||||||
|
@ -196,17 +202,9 @@ def add_book(request):
|
||||||
) or 0
|
) or 0
|
||||||
increment_order_in_reverse(book_list.id, order_max + 1)
|
increment_order_in_reverse(book_list.id, order_max + 1)
|
||||||
item.order = order_max + 1
|
item.order = order_max + 1
|
||||||
|
item.save()
|
||||||
|
|
||||||
try:
|
return List().get(request, book_list.id, add_succeeded=True)
|
||||||
item.save()
|
|
||||||
except IntegrityError:
|
|
||||||
# if the book is already on the list, don't flip out
|
|
||||||
pass
|
|
||||||
|
|
||||||
path = reverse("list", args=[book_list.id])
|
|
||||||
params = request.GET.copy()
|
|
||||||
params["updated"] = True
|
|
||||||
return redirect(f"{path}?{urlencode(params)}")
|
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
""" non-interactive pages """
|
""" non-interactive pages """
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models import Q, Count
|
from django.db.models import Q, Count
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
@ -105,6 +106,9 @@ class Followers(View):
|
||||||
if is_api_request(request):
|
if is_api_request(request):
|
||||||
return ActivitypubResponse(user.to_followers_activity(**request.GET))
|
return ActivitypubResponse(user.to_followers_activity(**request.GET))
|
||||||
|
|
||||||
|
if user.hide_follows:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
followers = annotate_if_follows(request.user, user.followers)
|
followers = annotate_if_follows(request.user, user.followers)
|
||||||
paginated = Paginator(followers.all(), PAGE_LENGTH)
|
paginated = Paginator(followers.all(), PAGE_LENGTH)
|
||||||
data = {
|
data = {
|
||||||
|
@ -125,6 +129,9 @@ class Following(View):
|
||||||
if is_api_request(request):
|
if is_api_request(request):
|
||||||
return ActivitypubResponse(user.to_following_activity(**request.GET))
|
return ActivitypubResponse(user.to_following_activity(**request.GET))
|
||||||
|
|
||||||
|
if user.hide_follows:
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
following = annotate_if_follows(request.user, user.following)
|
following = annotate_if_follows(request.user, user.following)
|
||||||
paginated = Paginator(following.all(), PAGE_LENGTH)
|
paginated = Paginator(following.all(), PAGE_LENGTH)
|
||||||
data = {
|
data = {
|
||||||
|
|
Loading…
Reference in a new issue