mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-19 16:11:05 +00:00
commit
b50d38d769
10 changed files with 215 additions and 40 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
80
bookwyrm/templates/discover.html
Normal file
80
bookwyrm/templates/discover.html
Normal 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 %}
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
16
bookwyrm/templates/snippets/discover/large-book.html
Normal file
16
bookwyrm/templates/snippets/discover/large-book.html
Normal 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 %}
|
11
bookwyrm/templates/snippets/discover/small-book.html
Normal file
11
bookwyrm/templates/snippets/discover/small-book.html
Normal 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 %}
|
|
@ -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 '''
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in a new issue