mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-10-31 22:19:00 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
847c4b49b4
26 changed files with 521 additions and 118 deletions
|
@ -4,7 +4,7 @@ from django.db.models import signals, Q
|
|||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.redis_store import RedisStore, r
|
||||
from bookwyrm.settings import STREAMS
|
||||
from bookwyrm.tasks import app
|
||||
from bookwyrm.views.helpers import privacy_filter
|
||||
|
||||
|
||||
|
@ -56,7 +56,13 @@ class ActivityStream(RedisStore):
|
|||
return (
|
||||
models.Status.objects.select_subclasses()
|
||||
.filter(id__in=statuses)
|
||||
.select_related("user", "reply_parent")
|
||||
.select_related(
|
||||
"user",
|
||||
"reply_parent",
|
||||
"comment__book",
|
||||
"review__book",
|
||||
"quotation__book",
|
||||
)
|
||||
.prefetch_related("mention_books", "mention_users")
|
||||
.order_by("-published_date")
|
||||
)
|
||||
|
@ -235,15 +241,10 @@ class BooksStream(ActivityStream):
|
|||
|
||||
|
||||
# determine which streams are enabled in settings.py
|
||||
available_streams = [s["key"] for s in STREAMS]
|
||||
streams = {
|
||||
k: v
|
||||
for (k, v) in {
|
||||
"home": HomeStream(),
|
||||
"local": LocalStream(),
|
||||
"books": BooksStream(),
|
||||
}.items()
|
||||
if k in available_streams
|
||||
"home": HomeStream(),
|
||||
"local": LocalStream(),
|
||||
"books": BooksStream(),
|
||||
}
|
||||
|
||||
|
||||
|
@ -391,3 +392,11 @@ def remove_statuses_on_unshelve(sender, instance, *args, **kwargs):
|
|||
return
|
||||
|
||||
BooksStream().remove_book_statuses(instance.user, instance.book)
|
||||
|
||||
|
||||
@app.task
|
||||
def populate_stream_task(stream, user_id):
|
||||
"""background task for populating an empty activitystream"""
|
||||
user = models.User.objects.get(id=user_id)
|
||||
stream = streams[stream]
|
||||
stream.populate_streams(user)
|
||||
|
|
|
@ -3,22 +3,35 @@ from django.core.management.base import BaseCommand
|
|||
from bookwyrm import activitystreams, models
|
||||
|
||||
|
||||
def populate_streams():
|
||||
def populate_streams(stream=None):
|
||||
"""build all the streams for all the users"""
|
||||
streams = [stream] if stream else activitystreams.streams.keys()
|
||||
print("Populations streams", streams)
|
||||
users = models.User.objects.filter(
|
||||
local=True,
|
||||
is_active=True,
|
||||
)
|
||||
).order_by("-last_active_date")
|
||||
print("This may take a long time! Please be patient.")
|
||||
for user in users:
|
||||
for stream in activitystreams.streams.values():
|
||||
stream.populate_streams(user)
|
||||
for stream_key in streams:
|
||||
print(".", end="")
|
||||
activitystreams.populate_stream_task.delay(stream_key, user.id)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""start all over with user streams"""
|
||||
|
||||
help = "Populate streams for all users"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--stream",
|
||||
default=None,
|
||||
help="Specifies which time of stream to populate",
|
||||
)
|
||||
|
||||
# pylint: disable=no-self-use,unused-argument
|
||||
def handle(self, *args, **options):
|
||||
"""run feed builder"""
|
||||
populate_streams()
|
||||
stream = options.get("stream")
|
||||
populate_streams(stream=stream)
|
||||
|
|
|
@ -29,6 +29,11 @@ body {
|
|||
min-width: 75% !important;
|
||||
}
|
||||
|
||||
.clip-text {
|
||||
max-height: 35em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/** Utilities not covered by Bulma
|
||||
******************************************************************************/
|
||||
|
||||
|
|
|
@ -1,50 +1,89 @@
|
|||
{% extends 'discover/landing_layout.html' %}
|
||||
{% extends "layout.html" %}
|
||||
{% load i18n %}
|
||||
{% block panel %}
|
||||
|
||||
<div class="block is-hidden-tablet">
|
||||
<h2 class="title has-text-centered">{% trans "Recent Books" %}</h2>
|
||||
</div>
|
||||
{% block title %}{% trans "Discover" %}{% endblock %}
|
||||
|
||||
<section class="tile is-ancestor">
|
||||
<div class="tile is-vertical">
|
||||
<div class="tile is-parent">
|
||||
{% block content %}
|
||||
|
||||
<section class="block">
|
||||
<header class="block content has-text-centered">
|
||||
<h1 class="title">{% trans "Discover" %}</h1>
|
||||
<p class="subtitle">
|
||||
{% blocktrans trimmed with site_name=site.name %}
|
||||
See what's new in the local {{ site_name }} community
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="tile is-ancestor">
|
||||
<div class="tile is-6 is-parent">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/large-book.html' with book=books.0 %}
|
||||
{% include 'discover/large-book.html' with status=large_activities.0 %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-6 is-parent">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/large-book.html' with status=large_activities.1 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tile is-ancestor">
|
||||
<div class="tile is-vertical is-6">
|
||||
<div class="tile is-parent">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/small-book.html' with book=books.1 %}
|
||||
{% include 'discover/large-book.html' with status=large_activities.2 %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile">
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/small-book.html' with status=small_activities.0 %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/small-book.html' with status=small_activities.1 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-vertical is-6">
|
||||
<div class="tile">
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/small-book.html' with status=small_activities.2 %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/small-book.html' with status=small_activities.3 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-parent">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/small-book.html' with book=books.2 %}
|
||||
{% include 'discover/large-book.html' with status=large_activities.3 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-vertical">
|
||||
<div class="tile">
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/small-book.html' with book=books.3 %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/small-book.html' with book=books.4 %}
|
||||
</div>
|
||||
|
||||
<div class="tile is-ancestor">
|
||||
<div class="tile is-6 is-parent">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/large-book.html' with status=large_activities.4 %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-parent">
|
||||
<div class="tile is-6 is-parent">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'discover/large-book.html' with book=books.5 %}
|
||||
{% include 'discover/large-book.html' with status=large_activities.5 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="block">
|
||||
{% include 'snippets/pagination.html' with page=large_activities %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,38 +1,73 @@
|
|||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
{% load status_display %}
|
||||
|
||||
{% if book %}
|
||||
{% with book=book %}
|
||||
<div class="columns is-gapless">
|
||||
<div class="column is-5-tablet is-cover">
|
||||
<a
|
||||
class="align to-b to-l"
|
||||
href="{{ book.local_path }}"
|
||||
>{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto-tablet' %}</a>
|
||||
{% if status.book or status.mention_books.exists %}
|
||||
{% load_book status as book %}
|
||||
<div class="columns is-gapless">
|
||||
<div class="column is-6-tablet is-cover">
|
||||
<a
|
||||
class="align to-b to-l"
|
||||
href="{{ book.local_path }}"
|
||||
>{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto-tablet' %}</a>
|
||||
|
||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||
</div>
|
||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||
<h3 class="title is-6">
|
||||
<a href="{{ book.local_path }}">{{ book|book_title }}</a>
|
||||
</h3>
|
||||
|
||||
{% if book.authors %}
|
||||
<p class="subtitle is-6 mb-2">
|
||||
{% trans "by" %}
|
||||
{% include 'snippets/authors.html' with limit=3 %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="column mt-3-mobile ml-3-tablet">
|
||||
<h3 class="title is-5">
|
||||
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
||||
</h3>
|
||||
|
||||
{% if book.authors %}
|
||||
<p class="subtitle is-5">
|
||||
{% trans "by" %}
|
||||
{% include 'snippets/authors.html' %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if book|book_description %}
|
||||
<blockquote class="content">
|
||||
{{ book|book_description|to_markdown|safe|truncatewords_html:50 }}
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% include 'snippets/shelve_button/shelve_button.html' %}
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
<div class="column mt-3-mobile ml-3-tablet">
|
||||
<div class="media block mb-2">
|
||||
<figure class="media-left" aria-hidden="true">
|
||||
<a class="image is-48x48" href="{{ status.user.local_path }}">
|
||||
{% include 'snippets/avatar.html' with user=status.user ariaHide="true" medium="true" %}
|
||||
</a>
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<h3 class="title is-6">
|
||||
<a href="{{ status.user.local_path }}">
|
||||
<span>{{ status.user.display_name }}</span>
|
||||
</a>
|
||||
|
||||
{% if status.status_type == 'GeneratedNote' %}
|
||||
{{ status.content|safe }}
|
||||
{% elif status.status_type == 'Rating' %}
|
||||
{% trans "rated" %}
|
||||
{% elif status.status_type == 'Review' %}
|
||||
{% trans "reviewed" %}
|
||||
{% elif status.status_type == 'Comment' %}
|
||||
{% trans "commented on" %}
|
||||
{% elif status.status_type == 'Quotation' %}
|
||||
{% trans "quoted" %}
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
{% include 'snippets/follow_button.html' with user=status.user show_username=True minimal=True %}
|
||||
</div>
|
||||
|
||||
<div class="notification has-background-white p-2 mb-2 clip-text">
|
||||
{% include "snippets/status/content_status.html" with hide_book=True trim_length=70 hide_more=True %}
|
||||
</div>
|
||||
<a href="{{ status.remote_id }}">
|
||||
<span>{% trans "View status" %}</span>
|
||||
<span class="icon icon-arrow-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,24 +1,59 @@
|
|||
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
{% load i18n %}
|
||||
{% load status_display %}
|
||||
|
||||
{% if book %}
|
||||
{% with book=book %}
|
||||
<a href="{{ book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-h-l-tablet is-w-auto align to-b to-l' %}
|
||||
</a>
|
||||
{% if status.book or status.mention_books.exists %}
|
||||
{% load_book status as book %}
|
||||
<a href="{{ book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto align to-b to-l' %}
|
||||
</a>
|
||||
|
||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||
<div class="block mt-2">
|
||||
{% include 'snippets/shelve_button/shelve_button.html' %}
|
||||
</div>
|
||||
|
||||
<h3 class="title is-6">
|
||||
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
||||
</h3>
|
||||
<div class="media block mb-2">
|
||||
<figure class="media-left" aria-hidden="true">
|
||||
<a class="image is-48x48" href="{{ status.user.local_path }}">
|
||||
{% include 'snippets/avatar.html' with user=status.user ariaHide="true" medium="true" %}
|
||||
</a>
|
||||
</figure>
|
||||
|
||||
{% if book.authors %}
|
||||
<div class="media-content">
|
||||
<h3 class="title is-6">
|
||||
<a href="{{ status.user.local_path }}">
|
||||
<span>{{ status.user.display_name }}</span>
|
||||
</a>
|
||||
|
||||
{% if status.status_type == 'GeneratedNote' %}
|
||||
{{ status.content|safe }}
|
||||
{% elif status.status_type == 'Rating' %}
|
||||
{% trans "rated" %}
|
||||
{% elif status.status_type == 'Review' %}
|
||||
{% trans "reviewed" %}
|
||||
{% elif status.status_type == 'Comment' %}
|
||||
{% trans "commented on" %}
|
||||
{% elif status.status_type == 'Quotation' %}
|
||||
{% trans "quoted" %}
|
||||
{% endif %}
|
||||
|
||||
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
||||
</h3>
|
||||
{% if status.rating %}
|
||||
<p class="subtitle is-6">
|
||||
{% trans "by" %}
|
||||
{% include 'snippets/authors.html' %}
|
||||
{% include 'snippets/stars.html' with rating=status.rating %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<a href="{{ status.remote_id }}">
|
||||
<span>{% trans "View status" %}</span>
|
||||
<span class="icon icon-arrow-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="block">
|
||||
{% include 'snippets/follow_button.html' with user=status.user show_username=True minimal=True %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'discover/landing_layout.html' %}
|
||||
{% extends 'landing/landing_layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block panel %}
|
50
bookwyrm/templates/landing/landing.html
Normal file
50
bookwyrm/templates/landing/landing.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
{% extends 'landing/landing_layout.html' %}
|
||||
{% load i18n %}
|
||||
{% block panel %}
|
||||
|
||||
<div class="block is-hidden-tablet">
|
||||
<h2 class="title has-text-centered">{% trans "Recent Books" %}</h2>
|
||||
</div>
|
||||
|
||||
<section class="tile is-ancestor">
|
||||
<div class="tile is-vertical is-6">
|
||||
<div class="tile is-parent">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'landing/large-book.html' with book=books.0 %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'landing/small-book.html' with book=books.1 %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'landing/small-book.html' with book=books.2 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-vertical is-6">
|
||||
<div class="tile">
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'landing/small-book.html' with book=books.3 %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-parent is-6">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'landing/small-book.html' with book=books.4 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile is-parent">
|
||||
<div class="tile is-child box has-background-white-ter">
|
||||
{% include 'landing/large-book.html' with book=books.5 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
38
bookwyrm/templates/landing/large-book.html
Normal file
38
bookwyrm/templates/landing/large-book.html
Normal file
|
@ -0,0 +1,38 @@
|
|||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if book %}
|
||||
{% with book=book %}
|
||||
<div class="columns is-gapless">
|
||||
<div class="column is-7-tablet is-cover">
|
||||
<a
|
||||
class="align to-b to-l"
|
||||
href="{{ book.local_path }}"
|
||||
>{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto-tablet' %}</a>
|
||||
|
||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="column mt-3-mobile ml-3-tablet">
|
||||
<h3 class="title is-5">
|
||||
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
||||
</h3>
|
||||
|
||||
{% if book.authors %}
|
||||
<p class="subtitle is-5">
|
||||
{% trans "by" %}
|
||||
{% include 'snippets/authors.html' with limit=3 %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if book|book_description %}
|
||||
<blockquote class="content">
|
||||
{{ book|book_description|to_markdown|safe|truncatewords_html:50 }}
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endif %}
|
23
bookwyrm/templates/landing/small-book.html
Normal file
23
bookwyrm/templates/landing/small-book.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{% load bookwyrm_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if book %}
|
||||
{% with book=book %}
|
||||
<a href="{{ book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with cover_class='is-w-l-mobile is-w-auto align to-b to-l' %}
|
||||
</a>
|
||||
|
||||
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
|
||||
|
||||
<h3 class="title is-6">
|
||||
<a href="{{ book.local_path }}">{{ book.title }}</a>
|
||||
</h3>
|
||||
|
||||
{% if book.authors %}
|
||||
<p class="subtitle is-6">
|
||||
{% trans "by" %}
|
||||
{% include 'snippets/authors.html' with limit=3 %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
|
@ -67,8 +67,8 @@
|
|||
<a href="{% url 'lists' %}" class="navbar-item">
|
||||
{% trans "Lists" %}
|
||||
</a>
|
||||
<a href="{% url 'directory' %}" class="navbar-item">
|
||||
{% trans "Directory" %}
|
||||
<a href="{% url 'discover' %}" class="navbar-item">
|
||||
{% trans "Discover" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -89,6 +89,11 @@
|
|||
<span class="ml-2">{{ request.user.display_name }}</span>
|
||||
</a>
|
||||
<ul class="navbar-dropdown" id="navbar-dropdown">
|
||||
<li>
|
||||
<a href="{% url 'directory' %}" class="navbar-item">
|
||||
{% trans "Directory" %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'user-shelves' request.user.localname %}" class="navbar-item">
|
||||
{% trans 'Your Books' %}
|
||||
|
|
|
@ -9,15 +9,29 @@
|
|||
<form action="{% url 'follow' %}" method="POST" class="interaction follow-{{ user.id }} {% if request.user in user.followers.all or request.user in user.follower_requests.all %}is-hidden{%endif %}" data-id="follow-{{ user.id }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="user" value="{{ user.username }}">
|
||||
<button class="button is-small{% if not minimal %} is-link{% endif %}" type="submit">{% trans "Follow" %}</button>
|
||||
<button class="button is-small{% if not minimal %} is-link{% endif %}" type="submit">
|
||||
{% if show_username %}
|
||||
{% blocktrans with username=user.localname %}Follow @{{ username }}{% endblocktrans %}
|
||||
{% else %}
|
||||
{% trans "Follow" %}
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
<form action="{% url 'unfollow' %}" method="POST" class="interaction follow-{{ user.id }} {% if not request.user in user.followers.all and not request.user in user.follower_requests.all %}is-hidden{%endif %}" data-id="follow-{{ user.id }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="user" value="{{ user.username }}">
|
||||
{% if user.manually_approves_followers and request.user not in user.followers.all %}
|
||||
<button class="button is-small is-danger is-light" type="submit">{% trans "Undo follow request" %}</button>
|
||||
<button class="button is-small is-danger is-light" type="submit">
|
||||
{% trans "Undo follow request" %}
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="button is-small is-danger is-light" type="submit">{% trans "Unfollow" %}</button>
|
||||
<button class="button is-small is-danger is-light" type="submit">
|
||||
{% if show_username %}
|
||||
{% blocktrans with username=user.localname %}Unfollow @{{ username }}{% endblocktrans %}
|
||||
{% else %}
|
||||
{% trans "Unfollow" %}
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -3,20 +3,24 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% with 0|uuid as uuid %}
|
||||
{% firstof trim_length 150 as trim_length %}
|
||||
{% if full %}
|
||||
{% with full|to_markdown|safe as full %}
|
||||
{% with full|to_markdown|safe|truncatewords_html:150 as trimmed %}
|
||||
{% with full|to_markdown|safe|truncatewords_html:trim_length as trimmed %}
|
||||
{% if not no_trim and trimmed != full %}
|
||||
<div id="hide-full-{{ uuid }}">
|
||||
<div class="content" id="trimmed-{{ uuid }}">
|
||||
<div dir="auto">{{ trimmed }}</div>
|
||||
|
||||
<div>
|
||||
{% if not hide_more %}
|
||||
{% trans "Show more" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text controls_text="full" controls_uid=uuid class="is-small" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if not hide_more %}
|
||||
<div id="full-{{ uuid }}" class="is-hidden">
|
||||
<div class="content">
|
||||
<div
|
||||
|
@ -32,6 +36,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="content">
|
||||
<div
|
||||
|
|
|
@ -62,3 +62,9 @@ def get_published_date(date):
|
|||
if delta.days:
|
||||
return naturalday(date, "M j")
|
||||
return naturaltime(date)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def load_book(status):
|
||||
"""how many users that you follow, follow them"""
|
||||
return status.book if hasattr(status, "book") else status.mention_books.first()
|
||||
|
|
|
@ -42,8 +42,6 @@ class Activitystreams(TestCase):
|
|||
user=self.local_user, content="hi", book=self.book
|
||||
)
|
||||
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.populate_store"
|
||||
) as redis_mock:
|
||||
with patch("bookwyrm.activitystreams.populate_stream_task.delay") as redis_mock:
|
||||
populate_streams()
|
||||
self.assertEqual(redis_mock.call_count, 4) # 2 users x 2 streams
|
||||
self.assertEqual(redis_mock.call_count, 6) # 2 users x 3 streams
|
||||
|
|
77
bookwyrm/tests/views/test_discover.py
Normal file
77
bookwyrm/tests/views/test_discover.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
""" test for app action functionality """
|
||||
from unittest.mock import patch
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm import views
|
||||
|
||||
|
||||
class DiscoverViews(TestCase):
|
||||
"""pages you land on without really trying"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
"password",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
self.anonymous_user = AnonymousUser
|
||||
self.anonymous_user.is_authenticated = False
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_discover_page_empty(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Discover.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.get_activity_stream"
|
||||
) as mock:
|
||||
result = view(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
result.render()
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
def test_discover_page(self, *_):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Discover.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
book = models.Edition.objects.create(
|
||||
title="hi", parent_work=models.Work.objects.create(title="work")
|
||||
)
|
||||
|
||||
models.Comment.objects.create(
|
||||
book=book,
|
||||
user=self.local_user,
|
||||
content="hello",
|
||||
)
|
||||
models.Status.objects.create(user=self.local_user, content="beep")
|
||||
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.get_activity_stream"
|
||||
) as mock:
|
||||
mock.return_value = models.Status.objects.all()
|
||||
result = view(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
result.render()
|
||||
|
||||
def test_discover_page_logged_out(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Discover.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
result = view(request)
|
||||
self.assertEqual(result.status_code, 302)
|
|
@ -54,9 +54,9 @@ class LandingViews(TestCase):
|
|||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_discover(self):
|
||||
def test_landing(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.Discover.as_view()
|
||||
view = views.Landing.as_view()
|
||||
request = self.factory.get("")
|
||||
result = view(request)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
|
|
|
@ -162,7 +162,7 @@ urlpatterns = [
|
|||
# landing pages
|
||||
re_path(r"^about/?$", views.About.as_view(), name="about"),
|
||||
path("", views.Home.as_view(), name="landing"),
|
||||
re_path(r"^discover/?$", views.Discover.as_view()),
|
||||
re_path(r"^discover/?$", views.Discover.as_view(), name="discover"),
|
||||
re_path(r"^notifications/?$", views.Notifications.as_view(), name="notifications"),
|
||||
re_path(
|
||||
r"^notifications/(?P<notification_type>mentions)/?$",
|
||||
|
|
|
@ -7,6 +7,7 @@ from .block import Block, unblock
|
|||
from .books import Book, EditBook, ConfirmEditBook, Editions
|
||||
from .books import upload_cover, add_description, switch_edition, resolve_book
|
||||
from .directory import Directory
|
||||
from .discover import Discover
|
||||
from .edit_user import EditUser, DeleteUser
|
||||
from .federation import Federation, FederatedServer
|
||||
from .federation import AddFederatedServer, ImportServerBlocklist
|
||||
|
@ -22,7 +23,7 @@ from .interaction import Favorite, Unfavorite, Boost, Unboost
|
|||
from .invite import ManageInvites, Invite, InviteRequest
|
||||
from .invite import ManageInviteRequests, ignore_invite_request
|
||||
from .isbn import Isbn
|
||||
from .landing import About, Home, Discover
|
||||
from .landing import About, Home, Landing
|
||||
from .list import Lists, List, Curate, UserLists
|
||||
from .notifications import Notifications
|
||||
from .outbox import Outbox
|
||||
|
|
48
bookwyrm/views/discover.py
Normal file
48
bookwyrm/views/discover.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
""" What's up locally """
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Q
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import activitystreams
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class Discover(View):
|
||||
"""preview of recently reviewed books"""
|
||||
|
||||
def get(self, request):
|
||||
"""tiled book activity page"""
|
||||
activities = (
|
||||
activitystreams.streams["local"]
|
||||
.get_activity_stream(request.user)
|
||||
.filter(
|
||||
Q(comment__isnull=False)
|
||||
| Q(review__isnull=False)
|
||||
| Q(quotation__isnull=False)
|
||||
| Q(mention_books__isnull=False)
|
||||
)
|
||||
)
|
||||
|
||||
large_activities = Paginator(
|
||||
activities.filter(mention_books__isnull=True)
|
||||
.exclude(content=None, quotation__quote=None)
|
||||
.exclude(content=""),
|
||||
6,
|
||||
)
|
||||
small_activities = Paginator(
|
||||
activities.filter(
|
||||
Q(mention_books__isnull=False) | Q(content=None) | Q(content="")
|
||||
),
|
||||
4,
|
||||
)
|
||||
|
||||
page = request.GET.get("page")
|
||||
data = {
|
||||
"large_activities": large_activities.get_page(page),
|
||||
"small_activities": small_activities.get_page(page),
|
||||
}
|
||||
return TemplateResponse(request, "discover/discover.html", data)
|
|
@ -2,7 +2,7 @@
|
|||
import re
|
||||
from requests import HTTPError
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db.models import Max, Q
|
||||
from django.db.models import Q
|
||||
from django.http import Http404
|
||||
|
||||
from bookwyrm import activitypub, models
|
||||
|
@ -162,8 +162,9 @@ def is_blocked(viewer, user):
|
|||
return False
|
||||
|
||||
|
||||
def get_discover_books():
|
||||
"""list of books for the discover page"""
|
||||
def get_landing_books():
|
||||
"""list of books for the landing page"""
|
||||
|
||||
return list(
|
||||
set(
|
||||
models.Edition.objects.filter(
|
||||
|
@ -173,7 +174,7 @@ def get_discover_books():
|
|||
review__privacy__in=["public", "unlisted"],
|
||||
)
|
||||
.exclude(cover__exact="")
|
||||
.annotate(Max("review__published_date"))
|
||||
.order_by("-review__published_date__max")[:6]
|
||||
.distinct()
|
||||
.order_by("-review__published_date")[:6]
|
||||
)
|
||||
)
|
||||
|
|
|
@ -170,9 +170,9 @@ class InviteRequest(View):
|
|||
data = {
|
||||
"request_form": form,
|
||||
"request_received": received,
|
||||
"books": helpers.get_discover_books(),
|
||||
"books": helpers.get_landing_books(),
|
||||
}
|
||||
return TemplateResponse(request, "discover/discover.html", data)
|
||||
return TemplateResponse(request, "landing/landing.html", data)
|
||||
|
||||
|
||||
@require_POST
|
||||
|
|
|
@ -13,22 +13,22 @@ class About(View):
|
|||
|
||||
def get(self, request):
|
||||
"""more information about the instance"""
|
||||
return TemplateResponse(request, "discover/about.html")
|
||||
return TemplateResponse(request, "landing/about.html")
|
||||
|
||||
|
||||
class Home(View):
|
||||
"""discover page or home feed depending on auth"""
|
||||
"""landing page or home feed depending on auth"""
|
||||
|
||||
def get(self, request):
|
||||
"""this is the same as the feed on the home tab"""
|
||||
if request.user.is_authenticated:
|
||||
feed_view = Feed.as_view()
|
||||
return feed_view(request, "home")
|
||||
discover_view = Discover.as_view()
|
||||
return discover_view(request)
|
||||
landing_view = Landing.as_view()
|
||||
return landing_view(request)
|
||||
|
||||
|
||||
class Discover(View):
|
||||
class Landing(View):
|
||||
"""preview of recently reviewed books"""
|
||||
|
||||
def get(self, request):
|
||||
|
@ -36,6 +36,6 @@ class Discover(View):
|
|||
data = {
|
||||
"register_form": forms.RegisterForm(),
|
||||
"request_form": forms.InviteRequestForm(),
|
||||
"books": helpers.get_discover_books(),
|
||||
"books": helpers.get_landing_books(),
|
||||
}
|
||||
return TemplateResponse(request, "discover/discover.html", data)
|
||||
return TemplateResponse(request, "landing/landing.html", data)
|
||||
|
|
4
bw-dev
4
bw-dev
|
@ -89,7 +89,7 @@ case "$CMD" in
|
|||
docker-compose restart
|
||||
;;
|
||||
populate_streams)
|
||||
runweb python manage.py populate_streams
|
||||
runweb python manage.py populate_streams $@
|
||||
;;
|
||||
populate_suggestions)
|
||||
runweb python manage.py populate_suggestions
|
||||
|
@ -131,7 +131,7 @@ case "$CMD" in
|
|||
echo " collectstatic"
|
||||
echo " build"
|
||||
echo " clean"
|
||||
echo " populate_streams"
|
||||
echo " populate_streams [--stream=<stream name>]"
|
||||
echo " populate_suggestions"
|
||||
echo " generate_preview_images [--all]"
|
||||
echo " copy_media_to_s3"
|
||||
|
|
|
@ -20,6 +20,7 @@ app.config_from_object("django.conf:settings", namespace="CELERY")
|
|||
# Load task modules from all registered Django app configs.
|
||||
app.autodiscover_tasks()
|
||||
app.autodiscover_tasks(["bookwyrm"], related_name="activitypub.base_activity")
|
||||
app.autodiscover_tasks(["bookwyrm"], related_name="activitystreams")
|
||||
app.autodiscover_tasks(["bookwyrm"], related_name="broadcast")
|
||||
app.autodiscover_tasks(["bookwyrm"], related_name="connectors.abstract_connector")
|
||||
app.autodiscover_tasks(["bookwyrm"], related_name="emailing")
|
||||
|
|
Loading…
Reference in a new issue