Merge pull request #473 from mouse-reeve/discover-page

Discover page
This commit is contained in:
Mouse Reeve 2021-01-03 14:50:58 -08:00 committed by GitHub
commit b50d38d769
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 215 additions and 40 deletions

View file

@ -108,7 +108,7 @@ class Connector(AbstractConnector):
def get_cover_url(self, cover_blob): def get_cover_url(self, cover_blob):
''' ask openlibrary for the cover ''' ''' ask openlibrary for the cover '''
cover_id = cover_blob[0] cover_id = cover_blob[0]
image_name = '%s-M.jpg' % cover_id image_name = '%s-L.jpg' % cover_id
return '%s/b/id/%s' % (self.covers_url, image_name) return '%s/b/id/%s' % (self.covers_url, image_name)

View file

@ -67,6 +67,13 @@ input.toggle-control:checked ~ .modal.toggle-content {
width: max-content; width: max-content;
max-width: 250px; max-width: 250px;
} }
.cover-container.is-large {
height: max-content;
max-width: 500px;
}
.cover-container.is-large img {
max-height: 500px;
}
.cover-container.is-medium { .cover-container.is-medium {
height: 150px; height: 150px;
} }

View file

@ -0,0 +1,80 @@
{% extends 'layout.html' %}
{% block content %}
{% if not request.user.is_authenticated %}
<div class="block">
<h1 class="title has-text-centered">{{ site.name }}: Social Reading and Reviewing</h1>
</div>
<section class="tile is-ancestor">
<div class="tile is-7 is-parent">
<div class="tile is-child block">
{% include 'snippets/about.html' %}
</div>
</div>
<div class="tile is-5 is-parent">
<div class="tile is-child box has-background-primary-light">
{% if site.allow_registration %}
<h2 class="title">Join {{ site.name }}</h2>
<form name="register" method="post" action="/user-register">
{% include 'snippets/register_form.html' %}
</form>
{% else %}
<h2 class="title">This instance is closed</h2>
<p>Contact an administrator to get an invite</p>
{% endif %}
</div>
</div>
</section>
{% else %}
<div class="block">
<h1 class="title has-text-centered">Discover</h1>
</div>
{% endif %}
<div class="block is-hidden-tablet">
<h2 class="title has-text-centered">Recent Books</h2>
</div>
<section class="tile is-ancestor">
<div class="tile is-vertical">
<div class="tile is-parent">
<div class="tile is-child box has-background-white-ter">
{% include 'snippets/discover/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 'snippets/discover/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 'snippets/discover/small-book.html' with book=books.2 %}
</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 'snippets/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 'snippets/discover/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 'snippets/discover/large-book.html' with book=books.5 %}
</div>
</div>
</div>
</section>
{% endblock %}

View file

@ -18,7 +18,7 @@
</head> </head>
<body> <body>
<nav class="navbar" role="navigation" aria-label="main navigation"> <nav class="navbar container" role="navigation" aria-label="main navigation">
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" href="/"> <a class="navbar-item" href="/">
<img class="image logo" src="/static/images/logo-small.png" alt="Home page"> <img class="image logo" src="/static/images/logo-small.png" alt="Home page">
@ -63,7 +63,7 @@
<div class="navbar-end"> <div class="navbar-end">
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<div class="navbar-item has-dropdown is-hoverable"> <div class="navbar-item has-dropdown is-hoverable">
<div class="navbar-link" role="button" aria-expanded=false" onmouseover="toggleMenu(this)" tabindex="0" aria-haspopup="true" aria-controls="navbar-dropdown"><p> <div class="navbar-link" role="button" aria-expanded=false" onclick="toggleMenu(this)" tabindex="0" aria-haspopup="true" aria-controls="navbar-dropdown"><p>
{% include 'snippets/avatar.html' with user=request.user %} {% include 'snippets/avatar.html' with user=request.user %}
{% include 'snippets/username.html' with user=request.user %} {% include 'snippets/username.html' with user=request.user %}
</p></div> </p></div>
@ -117,48 +117,74 @@
</div> </div>
</a> </a>
</div> </div>
{% else %} {% elif request.path != '/login' and request.path != '/login/' %}
<div class="navbar-item"> <div class="navbar-item">
<div class="buttons"> <div class="columns">
<a href="/login" class="button is-primary"> <div class="column">
Join <form name="login" method="post" action="/user-login">
</a> {% csrf_token %}
<div class="field is-grouped">
<div class="control">
<label class="is-sr-only" for="id_username">Username:</label>
<input type="text" name="username" maxlength="150" class="input" required="" id="id_username" placeholder="username">
</div>
<div class="control">
<label class="is-sr-only" for="id_password">Username:</label>
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password" placeholder="password">
<p class="help"><a href="/password-reset">Forgot your password?</a></p>
</div>
<button class="button is-primary" type="submit">Log in</button>
</div>
</form>
</div>
<div class="column is-narrow">
<a href="/login" class="button is-link">
Join
</a>
</div>
</div> </div>
</div> </div>
{% else %}
<div class="navbar-item">
<a href="/login" class="button is-link">
Join
</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</nav> </nav>
<div class="section"> <div class="section container">
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</div> </div>
<div class="footer"> <footer class="footer">
<div class="columns"> <div class="container">
<div class="column"> <div class="columns">
<p> <div class="column">
<a href="/about">About this server</a> <p>
</p> <a href="/about">About this server</a>
{% if site.admin_email %} </p>
<p> {% if site.admin_email %}
<a href="mailto:{{ site.admin_email }}">Contact site admin</a> <p>
</p> <a href="mailto:{{ site.admin_email }}">Contact site admin</a>
</p>
{% endif %}
</div>
{% if site.support_link %}
<div class="column">
<span class="icon icon-heart"></span>
Support {{ site.name }} on <a href="{{ site.support_link }}" target="_blank">{{ site.support_title }}</a>
</div>
{% endif %} {% endif %}
</div> <div class="column">
{% if site.support_link %} BookWyrm is open source software. You can contribute or report issues on <a href="https://github.com/mouse-reeve/bookwyrm">GitHub</a>.
<div class="column"> </div>
<span class="icon icon-heart"></span>
Support {{ site.name }} on <a href="{{ site.support_link }}" target="_blank">{{ site.support_title }}</a>
</div>
{% endif %}
<div class="column">
BookWyrm is open source software. You can contribute or report issues on <a href="https://github.com/mouse-reeve/bookwyrm">GitHub</a>.
</div> </div>
</div> </div>
</div> </footer>
<script> <script>
var csrf_token = '{{ csrf_token }}'; var csrf_token = '{{ csrf_token }}';
@ -166,4 +192,3 @@
<script src="/static/js/shared.js"></script> <script src="/static/js/shared.js"></script>
</body> </body>
</html> </html>

View file

@ -1,7 +1,12 @@
<h1 class="title">About {{ site.name }}</h1> <div class="columns">
<div class="block"> <div class="column is-narrow is-hidden-mobile">
<img src="/static/images/logo.png" alt="BookWyrm"> <figure class="block">
<img src="/static/images/logo.png" alt="BookWyrm">
</figure>
</div>
<div class="content">
<p class="block">
{{ site.instance_description | safe }}
</p>
</div>
</div> </div>
<p class="block">
{{ site.instance_description }}
</p>

View file

@ -0,0 +1,16 @@
{% load bookwyrm_tags %}
{% if book %}
<div class="columns">
<div class="column is-narrow">
{% include 'snippets/book_cover.html' with book=book size="large" %}
{% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %}
</div>
<div class="column">
<h3 class="title is-5"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
{% if book.authors %}
<p class="subtitle is-5">by {% include 'snippets/authors.html' with book=book %}</p>
{% endif %}
<blockquote class="content">{{ book|book_description|to_markdown|safe|truncatewords_html:50 }}</blockquote>
</div>
</div>
{% endif %}

View file

@ -0,0 +1,11 @@
{% load bookwyrm_tags %}
{% if book %}
{% include 'snippets/book_cover.html' with book=book %}
{% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %}
<h3 class="title is-6"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
{% if book.authors %}
<p class="subtitle is-6">by {% include 'snippets/authors.html' with book=book %}</p>
{% endif %}
{% endif %}

View file

@ -104,7 +104,7 @@ class Openlibrary(TestCase):
blob = ['image'] blob = ['image']
result = self.connector.get_cover_url(blob) result = self.connector.get_cover_url(blob)
self.assertEqual( self.assertEqual(
result, 'https://covers.openlibrary.org/b/id/image-M.jpg') result, 'https://covers.openlibrary.org/b/id/image-L.jpg')
def test_parse_search_result(self): def test_parse_search_result(self):
''' extract the results from the search json response ''' ''' extract the results from the search json response '''

View file

@ -52,6 +52,7 @@ urlpatterns = [
path('', views.home), path('', views.home),
re_path(r'^(?P<tab>home|local|federated)/?$', views.home_tab), re_path(r'^(?P<tab>home|local|federated)/?$', views.home_tab),
re_path(r'^discover/?$', views.discover_page),
re_path(r'^notifications/?$', views.notifications_page), re_path(r'^notifications/?$', views.notifications_page),
re_path(r'^direct-messages/?$', views.direct_messages_page), re_path(r'^direct-messages/?$', views.direct_messages_page),
re_path(r'^import/?$', views.import_page), re_path(r'^import/?$', views.import_page),

View file

@ -4,7 +4,7 @@ import re
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.postgres.search import TrigramSimilarity from django.contrib.postgres.search import TrigramSimilarity
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.db.models import Avg, Q from django.db.models import Avg, F, Q, Max
from django.db.models.functions import Greatest from django.db.models.functions import Greatest
from django.http import HttpResponseNotFound, JsonResponse from django.http import HttpResponseNotFound, JsonResponse
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
@ -64,11 +64,12 @@ def not_found_page(request, _):
request, 'notfound.html', {'title': 'Not found'}, status=404) request, 'notfound.html', {'title': 'Not found'}, status=404)
@login_required
@require_GET @require_GET
def home(request): def home(request):
''' this is the same as the feed on the home tab ''' ''' this is the same as the feed on the home tab '''
return home_tab(request, 'home') if request.user.is_authenticated:
return home_tab(request, 'home')
return discover_page(request)
@login_required @login_required
@ -131,6 +132,35 @@ def get_suggested_books(user, max_books=5):
return suggested_books return suggested_books
@require_GET
def discover_page(request):
''' tiled book activity page '''
books = models.Edition.objects.filter(
review__published_date__isnull=False,
review__user__local=True
).exclude(
cover__exact=''
).annotate(
Max('review__published_date')
).order_by('-review__published_date__max')[:6]
ratings = {}
for book in books:
reviews = models.Review.objects.filter(
book__in=book.parent_work.editions.all()
)
reviews = get_activity_feed(
request.user, 'federated', model=reviews)
ratings[book.id] = reviews.aggregate(Avg('rating'))['rating__avg']
data = {
'title': 'Discover',
'register_form': forms.RegisterForm(),
'books': list(set(books)),
'ratings': ratings
}
return TemplateResponse(request, 'discover.html', data)
@login_required @login_required
@require_GET @require_GET
def direct_messages_page(request, page=1): def direct_messages_page(request, page=1):