Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-08-02 07:32:22 -07:00
commit 9bf79bf9b9
36 changed files with 708 additions and 447 deletions

View file

@ -2,7 +2,8 @@
SECRET_KEY="7(2w1sedok=aznpq)ta1mc4i%4h=xx@hxwx*o57ctsuml0x%fr"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG=false
DEBUG=true
USE_HTTPS=false
DOMAIN=your.domain.here
#EMAIL=your@email.here
@ -42,6 +43,21 @@ EMAIL_HOST_PASSWORD=emailpassword123
EMAIL_USE_TLS=true
EMAIL_USE_SSL=false
# S3 configuration
USE_S3=false
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
# Commented are example values if you use a non-AWS, S3-compatible service
# AWS S3 should work with only AWS_STORAGE_BUCKET_NAME and AWS_S3_REGION_NAME
# non-AWS S3-compatible services will need AWS_STORAGE_BUCKET_NAME,
# along with both AWS_S3_CUSTOM_DOMAIN and AWS_S3_ENDPOINT_URL
# AWS_STORAGE_BUCKET_NAME= # "example-bucket-name"
# AWS_S3_CUSTOM_DOMAIN=None # "example-bucket-name.s3.fr-par.scw.cloud"
# AWS_S3_REGION_NAME=None # "fr-par"
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
# Preview image generation can be computing and storage intensive
# ENABLE_PREVIEW_IMAGES=True

View file

@ -3,6 +3,7 @@ SECRET_KEY="7(2w1sedok=aznpq)ta1mc4i%4h=xx@hxwx*o57ctsuml0x%fr"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG=false
USE_HTTPS=true
DOMAIN=your.domain.here
EMAIL=your@email.here
@ -42,6 +43,21 @@ EMAIL_HOST_PASSWORD=emailpassword123
EMAIL_USE_TLS=true
EMAIL_USE_SSL=false
# S3 configuration
USE_S3=false
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
# Commented are example values if you use a non-AWS, S3-compatible service
# AWS S3 should work with only AWS_STORAGE_BUCKET_NAME and AWS_S3_REGION_NAME
# non-AWS S3-compatible services will need AWS_STORAGE_BUCKET_NAME,
# along with both AWS_S3_CUSTOM_DOMAIN and AWS_S3_ENDPOINT_URL
# AWS_STORAGE_BUCKET_NAME= # "example-bucket-name"
# AWS_S3_CUSTOM_DOMAIN=None # "example-bucket-name.s3.fr-par.scw.cloud"
# AWS_S3_REGION_NAME=None # "fr-par"
# AWS_S3_ENDPOINT_URL=None # "https://s3.fr-par.scw.cloud"
# Preview image generation can be computing and storage intensive
# ENABLE_PREVIEW_IMAGES=True

View file

@ -11,10 +11,7 @@ def site_settings(request): # pylint: disable=unused-argument
return {
"site": models.SiteSettings.objects.get(),
"active_announcements": models.Announcement.active_announcements(),
"static_url": settings.STATIC_URL,
"media_url": settings.MEDIA_URL,
"static_path": settings.STATIC_PATH,
"media_path": settings.MEDIA_PATH,
"media_full_url": settings.MEDIA_FULL_URL,
"preview_images_enabled": settings.ENABLE_PREVIEW_IMAGES,
"request_protocol": request_protocol,
}

View file

@ -2,6 +2,8 @@
import csv
import logging
from django.utils import timezone
from bookwyrm import models
from bookwyrm.models import ImportJob, ImportItem
from bookwyrm.tasks import app
@ -100,7 +102,10 @@ def handle_imported_book(source, user, item, include_reviews, privacy):
# shelve the book if it hasn't been shelved already
if item.shelf and not existing_shelf:
desired_shelf = models.Shelf.objects.get(identifier=item.shelf, user=user)
models.ShelfBook.objects.create(book=item.book, shelf=desired_shelf, user=user)
shelved_date = item.date_added or timezone.now()
models.ShelfBook.objects.create(
book=item.book, shelf=desired_shelf, user=user, shelved_date=shelved_date
)
for read in item.reads:
# check for an existing readthrough with the same dates

View file

@ -0,0 +1,34 @@
# Generated by Django 3.2.4 on 2021-07-03 08:25
from django.db import migrations, models
import django.utils.timezone
def copy_created_date(app_registry, schema_editor):
db_alias = schema_editor.connection.alias
ShelfBook = app_registry.get_model("bookwyrm", "ShelfBook")
ShelfBook.objects.all().update(shelved_date=models.F("created_date"))
def do_nothing(app_registry, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0077_auto_20210623_2155"),
]
operations = [
migrations.AlterModelOptions(
name="shelfbook",
options={"ordering": ("-shelved_date",)},
),
migrations.AddField(
model_name="shelfbook",
name="shelved_date",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.RunPython(copy_created_date, reverse_code=do_nothing),
]

View file

@ -1,6 +1,7 @@
""" puttin' books on shelves """
import re
from django.db import models
from django.utils import timezone
from bookwyrm import activitypub
from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
@ -69,6 +70,7 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel):
"Edition", on_delete=models.PROTECT, activitypub_field="book"
)
shelf = models.ForeignKey("Shelf", on_delete=models.PROTECT)
shelved_date = models.DateTimeField(default=timezone.now)
user = fields.ForeignKey(
"User", on_delete=models.PROTECT, activitypub_field="actor"
)
@ -86,4 +88,4 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel):
you can't put a book on shelf twice"""
unique_together = ("book", "shelf")
ordering = ("-created_date",)
ordering = ("-shelved_date", "-created_date", "-updated_date")

View file

@ -11,6 +11,7 @@ from PIL import Image, ImageDraw, ImageFont, ImageOps, ImageColor
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.core.files.storage import default_storage
from django.db.models import Avg
from bookwyrm import models, settings
@ -319,9 +320,9 @@ def save_and_cleanup(image, instance=None):
try:
try:
old_path = instance.preview_image.path
old_path = instance.preview_image.name
except ValueError:
old_path = ""
old_path = None
# Save
image.save(image_buffer, format="jpeg", quality=75)
@ -342,8 +343,8 @@ def save_and_cleanup(image, instance=None):
instance.save()
# Clean up old file after saving
if os.path.exists(old_path):
os.remove(old_path)
if old_path and default_storage.exists(old_path):
default_storage.delete(old_path)
finally:
image_buffer.close()

View file

@ -58,6 +58,7 @@ SECRET_KEY = env("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env.bool("DEBUG", True)
USE_HTTPS = env.bool("USE_HTTPS", False)
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", ["*"])
@ -74,6 +75,7 @@ INSTALLED_APPS = [
"django_rename_app",
"bookwyrm",
"celery",
"storages",
]
MIDDLEWARE = [
@ -179,19 +181,51 @@ USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
STATIC_URL = "/static/"
STATIC_PATH = "%s/%s" % (DOMAIN, env("STATIC_ROOT", "static"))
STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
MEDIA_URL = "/images/"
MEDIA_PATH = "%s/%s" % (DOMAIN, env("MEDIA_ROOT", "images"))
MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images"))
USER_AGENT = "%s (BookWyrm/%s; +https://%s/)" % (
requests.utils.default_user_agent(),
VERSION,
DOMAIN,
)
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
# Storage
PROTOCOL = "http"
if USE_HTTPS:
PROTOCOL = "https"
USE_S3 = env.bool("USE_S3", False)
if USE_S3:
# AWS settings
AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
AWS_S3_CUSTOM_DOMAIN = env("AWS_S3_CUSTOM_DOMAIN")
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME", "")
AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL")
AWS_DEFAULT_ACL = "public-read"
AWS_S3_OBJECT_PARAMETERS = {"CacheControl": "max-age=86400"}
# S3 Static settings
STATIC_LOCATION = "static"
STATIC_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, STATIC_LOCATION)
STATICFILES_STORAGE = "bookwyrm.storage_backends.StaticStorage"
# S3 Media settings
MEDIA_LOCATION = "images"
MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, MEDIA_LOCATION)
MEDIA_FULL_URL = MEDIA_URL
DEFAULT_FILE_STORAGE = "bookwyrm.storage_backends.ImagesStorage"
# I don't know if it's used, but the site crashes without it
STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images"))
else:
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, env("STATIC_ROOT", "static"))
MEDIA_URL = "/images/"
MEDIA_FULL_URL = "%s://%s%s" % (PROTOCOL, DOMAIN, MEDIA_URL)
MEDIA_ROOT = os.path.join(BASE_DIR, env("MEDIA_ROOT", "images"))

View file

@ -72,6 +72,14 @@ body {
flex-grow: 1;
}
.preserve-whitespace p {
white-space: pre-wrap !important;
}
.display-inline p {
display: inline !important;
}
/** Shelving
******************************************************************************/

View file

@ -0,0 +1,17 @@
"""Handles backends for storages"""
from storages.backends.s3boto3 import S3Boto3Storage
class StaticStorage(S3Boto3Storage): # pylint: disable=abstract-method
"""Storage class for Static contents"""
location = "static"
default_acl = "public-read"
class ImagesStorage(S3Boto3Storage): # pylint: disable=abstract-method
"""Storage class for Image files"""
location = "images"
default_acl = "public-read"
file_overwrite = False

View file

@ -1,5 +1,10 @@
{% extends 'layout.html' %}
{% load i18n %}{% load bookwyrm_tags %}{% load humanize %}{% load utilities %}{% load layout %}
{% load i18n %}
{% load bookwyrm_tags %}
{% load humanize %}
{% load utilities %}
{% load static %}
{% load layout %}
{% block title %}{{ book|book_title }}{% endblock %}
@ -321,5 +326,5 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/vendor/tabs.js"></script>
<script src="{% static "js/vendor/tabs.js" %}"></script>
{% endblock %}

View file

@ -18,7 +18,7 @@
</div>
</div>
<div>
<div class="display-inline">
{% if user.summary %}
{{ user.summary|to_markdown|safe|truncatechars_html:40 }}
{% else %}&nbsp;{% endif %}

View file

@ -1,4 +1,4 @@
{% extends 'feed/feed_layout.html' %}
{% extends 'feed/layout.html' %}
{% load i18n %}
{% block panel %}

View file

@ -1,4 +1,4 @@
{% extends 'feed/feed_layout.html' %}
{% extends 'feed/layout.html' %}
{% load i18n %}
{% block panel %}

View file

@ -1,107 +0,0 @@
{% extends 'layout.html' %}
{% load i18n %}
{% block title %}{% trans "Updates" %}{% endblock %}
{% block content %}
<div class="columns">
{% if user.is_authenticated %}
<div class="column is-one-third">
<h2 class="title is-5">{% trans "Your books" %}</h2>
{% if not suggested_books %}
<p>{% trans "There are no books here right now! Try searching for a book to get started" %}</p>
{% else %}
{% with active_book=request.GET.book %}
<div class="tab-group">
<div class="tabs is-small">
<ul role="tablist">
{% for shelf in suggested_books %}
{% if shelf.books %}
{% with shelf_counter=forloop.counter %}
<li>
<p>
{% if shelf.identifier == 'to-read' %}{% trans "To Read" %}
{% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %}
{% elif shelf.identifier == 'read' %}{% trans "Read" %}
{% else %}{{ shelf.name }}{% endif %}
</p>
<div class="tabs is-small is-toggle">
<ul>
{% for book in shelf.books %}
<li class="{% if active_book == book.id|stringformat:'d' %}is-active{% elif not active_book and shelf_counter == 1 and forloop.first %}is-active{% endif %}">
<a
href="{{ request.path }}?book={{ book.id }}"
id="tab-book-{{ book.id }}"
role="tab"
aria-label="{{ book.title }}"
aria-selected="{% if active_book == book.id|stringformat:'d' %}true{% elif shelf_counter == 1 and forloop.first %}true{% else %}false{% endif %}"
aria-controls="book-{{ book.id }}">
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m' %}
</a>
</li>
{% endfor %}
</ul>
</div>
</li>
{% endwith %}
{% endif %}
{% endfor %}
</ul>
</div>
{% for shelf in suggested_books %}
{% with shelf_counter=forloop.counter %}
{% for book in shelf.books %}
<div
class="suggested-tabs card"
role="tabpanel"
id="book-{{ book.id }}"
{% if active_book and active_book == book.id|stringformat:'d' %}{% elif not active_book and shelf_counter == 1 and forloop.first %}{% else %} hidden{% endif %}
aria-labelledby="tab-book-{{ book.id }}">
<div class="card-header">
<div class="card-header-title">
<div>
<p class="mb-2">{% include 'snippets/book_titleby.html' with book=book %}</p>
{% include 'snippets/shelve_button/shelve_button.html' with book=book %}
</div>
</div>
<div class="card-header-icon is-hidden-tablet">
{% trans "Close" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with label=button_text controls_text="book" controls_uid=book.id class="delete" nonbutton=True pressed=True %}
</div>
</div>
<div class="card-content">
{% include 'snippets/create_status.html' with book=book %}
</div>
</div>
{% endfor %}
{% endwith %}
{% endfor %}
</div>
{% endwith %}
{% endif %}
{% if goal %}
<section class="section">
<div class="block">
<h3 class="title is-4">{% blocktrans with yar=goal.year %}{{ year }} Reading Goal{% endblocktrans %}</h3>
{% include 'snippets/goal_progress.html' with goal=goal %}
</div>
</section>
{% endif %}
</div>
{% endif %}
<div class="column is-two-thirds" id="feed">
{% block panel %}{% endblock %}
{% if activities %}
{% include 'snippets/pagination.html' with page=activities path=path anchor="#feed" %}
{% endif %}
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/vendor/tabs.js"></script>
{% endblock %}

View file

@ -0,0 +1,110 @@
{% extends 'layout.html' %}
{% load i18n %}
{% load static %}
{% block title %}{% trans "Updates" %}{% endblock %}
{% block content %}
<div class="columns">
{% if user.is_authenticated %}
<div class="column is-one-third">
<section class="block">
<h2 class="title is-4">{% trans "Your books" %}</h2>
{% if not suggested_books %}
<p>{% trans "There are no books here right now! Try searching for a book to get started" %}</p>
{% else %}
{% with active_book=request.GET.book %}
<div class="tab-group">
<div class="tabs is-small">
<ul role="tablist">
{% for shelf in suggested_books %}
{% if shelf.books %}
{% with shelf_counter=forloop.counter %}
<li>
<p>
{% if shelf.identifier == 'to-read' %}{% trans "To Read" %}
{% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %}
{% elif shelf.identifier == 'read' %}{% trans "Read" %}
{% else %}{{ shelf.name }}{% endif %}
</p>
<div class="tabs is-small is-toggle">
<ul>
{% for book in shelf.books %}
<li class="{% if active_book == book.id|stringformat:'d' %}is-active{% elif not active_book and shelf_counter == 1 and forloop.first %}is-active{% endif %}">
<a
href="{{ request.path }}?book={{ book.id }}"
id="tab-book-{{ book.id }}"
role="tab"
aria-label="{{ book.title }}"
aria-selected="{% if active_book == book.id|stringformat:'d' %}true{% elif shelf_counter == 1 and forloop.first %}true{% else %}false{% endif %}"
aria-controls="book-{{ book.id }}">
{% include 'snippets/book_cover.html' with book=book cover_class='is-h-m' %}
</a>
</li>
{% endfor %}
</ul>
</div>
</li>
{% endwith %}
{% endif %}
{% endfor %}
</ul>
</div>
{% for shelf in suggested_books %}
{% with shelf_counter=forloop.counter %}
{% for book in shelf.books %}
<div
class="suggested-tabs card"
role="tabpanel"
id="book-{{ book.id }}"
{% if active_book and active_book == book.id|stringformat:'d' %}{% elif not active_book and shelf_counter == 1 and forloop.first %}{% else %} hidden{% endif %}
aria-labelledby="tab-book-{{ book.id }}">
<div class="card-header">
<div class="card-header-title">
<div>
<p class="mb-2">{% include 'snippets/book_titleby.html' with book=book %}</p>
{% include 'snippets/shelve_button/shelve_button.html' with book=book %}
</div>
</div>
<div class="card-header-icon is-hidden-tablet">
{% trans "Close" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with label=button_text controls_text="book" controls_uid=book.id class="delete" nonbutton=True pressed=True %}
</div>
</div>
<div class="card-content">
{% include 'snippets/create_status.html' with book=book %}
</div>
</div>
{% endfor %}
{% endwith %}
{% endfor %}
</div>
{% endwith %}
{% endif %}
</section>
{% if goal %}
<section class="block">
<div class="block">
<h3 class="title is-4">{% blocktrans with yar=goal.year %}{{ year }} Reading Goal{% endblocktrans %}</h3>
{% include 'snippets/goal_progress.html' with goal=goal %}
</div>
</section>
{% endif %}
</div>
{% endif %}
<div class="column is-two-thirds" id="feed">
{% block panel %}{% endblock %}
{% if activities %}
{% include 'snippets/pagination.html' with page=activities path=path anchor="#feed" %}
{% endif %}
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{% static "js/vendor/tabs.js" %}"></script>
{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends 'feed/feed_layout.html' %}
{% extends 'feed/layout.html' %}
{% load i18n %}
{% block panel %}

View file

@ -1,5 +1,6 @@
{% extends 'layout.html' %}
{% load i18n %}
{% load static %}
{% block title %}{% trans "Welcome" %}{% endblock %}
@ -9,7 +10,7 @@
<div class="modal-background"></div>
<div class="modal-card is-fullwidth">
<header class="modal-card-head">
<img class="image logo mr-2" src="{% if site.logo_small %}/images/{{ site.logo_small }}{% else %}/static/images/logo-small.png{% endif %}" aria-hidden="true">
<img class="image logo mr-2" src="{% if site.logo_small %}{% get_media_prefix %}{{ site.logo_small }}{% else %}{% static "images/logo-small.png" %}{% endif %}" aria-hidden="true">
<h1 class="modal-card-title" id="get-started-header">
{% blocktrans %}Welcome to {{ site_name }}!{% endblocktrans %}
<span class="subtitle is-block">

View file

@ -1,6 +1,7 @@
{% extends 'layout.html' %}
{% load i18n %}
{% load humanize %}
{% load static %}
{% block title %}{% trans "Import Status" %}{% endblock %}
@ -156,5 +157,5 @@
{% endspaceless %}{% endblock %}
{% block scripts %}
<script src="/static/js/check_all.js"></script>
<script src="{% static "js/check_all.js" %}"></script>
{% endblock %}

View file

@ -1,14 +1,16 @@
{% load layout %}{% load i18n %}
{% load layout %}
{% load i18n %}
{% load static %}
<!DOCTYPE html>
<html lang="{% get_lang %}">
<head>
<title>{% block title %}BookWyrm{% endblock %} | {{ site.name }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/css/vendor/bulma.min.css">
<link rel="stylesheet" href="/static/css/vendor/icons.css">
<link rel="stylesheet" href="/static/css/bookwyrm.css">
<link rel="stylesheet" href="{% static "css/vendor/bulma.min.css" %}">
<link rel="stylesheet" href="{% static "css/vendor/icons.css" %}">
<link rel="stylesheet" href="{% static "css/bookwyrm.css" %}">
<link rel="shortcut icon" type="image/x-icon" href="{% if site.favicon %}{{ media_url }}{{ site.favicon }}{% else %}/static/images/favicon.ico{% endif %}">
<link rel="shortcut icon" type="image/x-icon" href="{% if site.favicon %}{% get_media_prefix %}{{ site.favicon }}{% else %}{% static "images/favicon.ico" %}{% endif %}">
{% if preview_images_enabled is True %}
<meta name="twitter:card" content="summary_large_image">
@ -30,7 +32,7 @@
<div class="container">
<div class="navbar-brand">
<a class="navbar-item" href="/">
<img class="image logo" src="{% if site.logo_small %}{{ media_url }}{{ site.logo_small }}{% else %}/static/images/logo-small.png{% endif %}" alt="Home page">
<img class="image logo" src="{% if site.logo_small %}{% get_media_prefix %}{{ site.logo_small }}{% else %}{% static "images/logo-small.png" %}{% endif %}" alt="Home page">
</a>
<form class="navbar-item column" action="/search/">
<div class="field has-addons">
@ -242,8 +244,8 @@
<script>
var csrf_token = '{{ csrf_token }}';
</script>
<script src="/static/js/bookwyrm.js"></script>
<script src="/static/js/localstorage.js"></script>
<script src="{% static "js/bookwyrm.js" %}"></script>
<script src="{% static "js/localstorage.js" %}"></script>
{% block scripts %}{% endblock %}
</body>
</html>

View file

@ -1,7 +1,9 @@
{% load static %}
<div class="columns">
<div class="column is-narrow is-hidden-mobile">
<figure class="block is-w-xl">
<img src="{% if site.logo %}/images/{{ site.logo }}{% else %}/static/images/logo.png{% endif %}" alt="BookWyrm logo">
<img src="{% if site.logo %}/images/{{ site.logo }}{% else %}{% static "images/logo.png" %}{% endif %}" alt="BookWyrm logo">
</figure>
</div>
<div class="content">

View file

@ -1,2 +1,4 @@
<img class="avatar image {% if large %}is-96x96{% elif medium %}is-48x48{% else %}is-32x32{% endif %}" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}" {% if ariaHide %}aria-hidden="true"{% endif %} alt="{{ user.alt_text }}">
{% load static %}
<img class="avatar image {% if large %}is-96x96{% elif medium %}is-48x48{% else %}is-32x32{% endif %}" src="{% if user.avatar %}{% get_media_prefix %}{{ user.avatar }}{% else %}{% static "images/default_avi.jpg" %}{% endif %}" {% if ariaHide %}aria-hidden="true"{% endif %} alt="{{ user.alt_text }}">

View file

@ -1,6 +1,7 @@
{% spaceless %}
{% load i18n %}
{% load static %}
<figure
class="
@ -20,14 +21,14 @@
class="book-cover"
{% if book.cover %}
src="{% if img_path is None %}/images/{% else %}{{ img_path }}{% endif %}{{ book.cover }}"
src="{% if img_path is None %}{% get_media_prefix %}{% else %}{{ img_path }}{% endif %}{{ book.cover }}"
itemprop="thumbnailUrl"
{% if book.alt_text %}
alt="{{ book.alt_text }}"
{% endif %}
{% else %}
src="/static/images/no_cover.jpg"
src="{% static "images/no_cover.jpg" %}"
alt="{% trans "No cover" %}"
{% endif %}
>

View file

@ -1,12 +1,14 @@
{% load static %}
{% if preview_images_enabled is True %}
{% if image %}
<meta name="twitter:image" content="{{ request.scheme }}://{{ media_path }}{{ image }}">
<meta name="og:image" content="{{ request.scheme }}://{{ media_path }}{{ image }}">
<meta name="twitter:image" content="{{ media_full_url }}{{ image }}">
<meta name="og:image" content="{{ media_full_url }}{{ image }}">
{% else %}
<meta name="twitter:image" content="{{ request.scheme }}://{{ media_path }}{{ site.preview_image }}">
<meta name="og:image" content="{{ request.scheme }}://{{ media_path }}{{ site.preview_image }}">
<meta name="twitter:image" content="{{ media_full_url }}{{ site.preview_image }}">
<meta name="og:image" content="{{ media_full_url }}{{ site.preview_image }}">
{% endif %}
{% else %}
<meta name="twitter:image" content="{{ request.scheme }}://{% if site.logo %}{{ media_path }}{{ site.logo }}{% else %}{{ static_path }}/images/logo.png{% endif %}">
<meta name="og:image" content="{{ request.scheme }}://{% if site.logo %}{{ media_path }}{{ site.logo }}{% else %}{{ static_path }}/images/logo.png{% endif %}">
<meta name="twitter:image" content="{% if site.logo %}{{ media_full_url }}{{ site.logo }}{% else %}{% static "images/logo.png" %}{% endif %}">
<meta name="og:image" content="{% if site.logo %}{{ media_full_url }}{{ site.logo }}{% else %}{% static "images/logo.png" %}{% endif %}">
{% endif %}

View file

@ -1,6 +1,7 @@
{% load bookwyrm_tags %}
{% load markdown %}
{% load i18n %}
{% load static %}
{% with status_type=status.status_type %}
<div
@ -111,12 +112,12 @@
<div class="column is-narrow">
<figure class="image is-128x128">
<a
href="/images/{{ attachment.image }}"
href="{% get_media_prefix %}{{ attachment.image }}"
target="_blank"
aria-label="{% trans 'Open image in new window' %}"
>
<img
src="/images/{{ attachment.image }}"
src="{% get_media_prefix %}{{ attachment.image }}"
{% if attachment.caption %}
alt="{{ attachment.caption }}"

View file

@ -2,6 +2,7 @@
{% load status_display %}
{% load i18n %}
{% load humanize %}
{% load static %}
<div class="media">
<figure class="media-left" aria-hidden="true">
@ -18,7 +19,7 @@
itemtype="https://schema.org/Person"
>
{% if status.user.avatar %}
<meta itemprop="image" content="/images/{{ status.user.avatar }}">
<meta itemprop="image" content="{% get_media_prefix %}{{ status.user.avatar }}">
{% endif %}
<a

View file

@ -28,8 +28,10 @@
</div>
{% if user.summary %}
<div class="column box has-background-white-bis content">
{% spaceless %}
<div class="column box has-background-white-bis content preserve-whitespace">
{{ user.summary|to_markdown|safe }}
{% endspaceless %}
</div>
{% endif %}
</div>

View file

@ -103,7 +103,7 @@
{% include 'snippets/authors.html' %}
</td>
<td data-title="{% trans "Shelved" %}">
{{ book.created_date|naturalday }}
{{ book.shelved_date|naturalday }}
</td>
{% latest_read_through book user as read_through %}
<td data-title="{% trans "Started" %}">

View file

@ -3,6 +3,8 @@ from collections import namedtuple
import csv
import pathlib
from unittest.mock import patch
import datetime
import pytz
from django.test import TestCase
import responses
@ -13,6 +15,10 @@ from bookwyrm.importers.importer import import_data, handle_imported_book
from bookwyrm.settings import DOMAIN
def make_date(*args):
return datetime.datetime(*args, tzinfo=pytz.UTC)
class GoodreadsImport(TestCase):
"""importing from goodreads csv"""
@ -130,22 +136,25 @@ class GoodreadsImport(TestCase):
shelf.refresh_from_db()
self.assertEqual(shelf.books.first(), self.book)
self.assertEqual(
shelf.shelfbook_set.first().shelved_date, make_date(2020, 10, 21)
)
readthrough = models.ReadThrough.objects.get(user=self.user)
self.assertEqual(readthrough.book, self.book)
# I can't remember how to create dates and I don't want to look it up.
self.assertEqual(readthrough.start_date.year, 2020)
self.assertEqual(readthrough.start_date.month, 10)
self.assertEqual(readthrough.start_date.day, 21)
self.assertEqual(readthrough.finish_date.year, 2020)
self.assertEqual(readthrough.finish_date.month, 10)
self.assertEqual(readthrough.finish_date.day, 25)
self.assertEqual(readthrough.start_date, make_date(2020, 10, 21))
self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25))
def test_handle_imported_book_already_shelved(self):
"""goodreads import added a book, this adds related connections"""
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
shelf = self.user.shelf_set.filter(identifier="to-read").first()
models.ShelfBook.objects.create(shelf=shelf, user=self.user, book=self.book)
models.ShelfBook.objects.create(
shelf=shelf,
user=self.user,
book=self.book,
shelved_date=make_date(2020, 2, 2),
)
import_job = models.ImportJob.objects.create(user=self.user)
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
@ -164,15 +173,15 @@ class GoodreadsImport(TestCase):
shelf.refresh_from_db()
self.assertEqual(shelf.books.first(), self.book)
self.assertEqual(
shelf.shelfbook_set.first().shelved_date, make_date(2020, 2, 2)
)
self.assertIsNone(self.user.shelf_set.get(identifier="read").books.first())
readthrough = models.ReadThrough.objects.get(user=self.user)
self.assertEqual(readthrough.book, self.book)
self.assertEqual(readthrough.start_date.year, 2020)
self.assertEqual(readthrough.start_date.month, 10)
self.assertEqual(readthrough.start_date.day, 21)
self.assertEqual(readthrough.finish_date.year, 2020)
self.assertEqual(readthrough.finish_date.month, 10)
self.assertEqual(readthrough.finish_date.day, 25)
self.assertEqual(readthrough.start_date, make_date(2020, 10, 21))
self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25))
def test_handle_import_twice(self):
"""re-importing books"""
@ -198,16 +207,14 @@ class GoodreadsImport(TestCase):
shelf.refresh_from_db()
self.assertEqual(shelf.books.first(), self.book)
self.assertEqual(
shelf.shelfbook_set.first().shelved_date, make_date(2020, 10, 21)
)
readthrough = models.ReadThrough.objects.get(user=self.user)
self.assertEqual(readthrough.book, self.book)
# I can't remember how to create dates and I don't want to look it up.
self.assertEqual(readthrough.start_date.year, 2020)
self.assertEqual(readthrough.start_date.month, 10)
self.assertEqual(readthrough.start_date.day, 21)
self.assertEqual(readthrough.finish_date.year, 2020)
self.assertEqual(readthrough.finish_date.month, 10)
self.assertEqual(readthrough.finish_date.day, 25)
self.assertEqual(readthrough.start_date, make_date(2020, 10, 21))
self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25))
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_imported_book_review(self, _):
@ -229,9 +236,7 @@ class GoodreadsImport(TestCase):
review = models.Review.objects.get(book=self.book, user=self.user)
self.assertEqual(review.content, "mixed feelings")
self.assertEqual(review.rating, 2)
self.assertEqual(review.published_date.year, 2019)
self.assertEqual(review.published_date.month, 7)
self.assertEqual(review.published_date.day, 8)
self.assertEqual(review.published_date, make_date(2019, 7, 8))
self.assertEqual(review.privacy, "unlisted")
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
@ -256,9 +261,7 @@ class GoodreadsImport(TestCase):
review = models.ReviewRating.objects.get(book=self.book, user=self.user)
self.assertIsInstance(review, models.ReviewRating)
self.assertEqual(review.rating, 2)
self.assertEqual(review.published_date.year, 2019)
self.assertEqual(review.published_date.month, 7)
self.assertEqual(review.published_date.day, 8)
self.assertEqual(review.published_date, make_date(2019, 7, 8))
self.assertEqual(review.privacy, "unlisted")
def test_handle_imported_book_reviews_disabled(self):

View file

@ -2,6 +2,8 @@
import csv
import pathlib
from unittest.mock import patch
import datetime
import pytz
from django.test import TestCase
import responses
@ -12,6 +14,10 @@ from bookwyrm.importers.importer import import_data, handle_imported_book
from bookwyrm.settings import DOMAIN
def make_date(*args):
return datetime.datetime(*args, tzinfo=pytz.UTC)
class LibrarythingImport(TestCase):
"""importing from librarything tsv"""
@ -125,13 +131,8 @@ class LibrarythingImport(TestCase):
readthrough = models.ReadThrough.objects.get(user=self.user)
self.assertEqual(readthrough.book, self.book)
# I can't remember how to create dates and I don't want to look it up.
self.assertEqual(readthrough.start_date.year, 2007)
self.assertEqual(readthrough.start_date.month, 4)
self.assertEqual(readthrough.start_date.day, 16)
self.assertEqual(readthrough.finish_date.year, 2007)
self.assertEqual(readthrough.finish_date.month, 5)
self.assertEqual(readthrough.finish_date.day, 8)
self.assertEqual(readthrough.start_date, make_date(2007, 4, 16))
self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8))
def test_handle_imported_book_already_shelved(self):
"""librarything import added a book, this adds related connections"""
@ -160,14 +161,11 @@ class LibrarythingImport(TestCase):
shelf.refresh_from_db()
self.assertEqual(shelf.books.first(), self.book)
self.assertIsNone(self.user.shelf_set.get(identifier="read").books.first())
readthrough = models.ReadThrough.objects.get(user=self.user)
self.assertEqual(readthrough.book, self.book)
self.assertEqual(readthrough.start_date.year, 2007)
self.assertEqual(readthrough.start_date.month, 4)
self.assertEqual(readthrough.start_date.day, 16)
self.assertEqual(readthrough.finish_date.year, 2007)
self.assertEqual(readthrough.finish_date.month, 5)
self.assertEqual(readthrough.finish_date.day, 8)
self.assertEqual(readthrough.start_date, make_date(2007, 4, 16))
self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8))
def test_handle_import_twice(self):
"""re-importing books"""
@ -198,13 +196,8 @@ class LibrarythingImport(TestCase):
readthrough = models.ReadThrough.objects.get(user=self.user)
self.assertEqual(readthrough.book, self.book)
# I can't remember how to create dates and I don't want to look it up.
self.assertEqual(readthrough.start_date.year, 2007)
self.assertEqual(readthrough.start_date.month, 4)
self.assertEqual(readthrough.start_date.day, 16)
self.assertEqual(readthrough.finish_date.year, 2007)
self.assertEqual(readthrough.finish_date.month, 5)
self.assertEqual(readthrough.finish_date.day, 8)
self.assertEqual(readthrough.start_date, make_date(2007, 4, 16))
self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8))
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
def test_handle_imported_book_review(self, _):
@ -226,9 +219,7 @@ class LibrarythingImport(TestCase):
review = models.Review.objects.get(book=self.book, user=self.user)
self.assertEqual(review.content, "chef d'oeuvre")
self.assertEqual(review.rating, 5)
self.assertEqual(review.published_date.year, 2007)
self.assertEqual(review.published_date.month, 5)
self.assertEqual(review.published_date.day, 8)
self.assertEqual(review.published_date, make_date(2007, 5, 8))
self.assertEqual(review.privacy, "unlisted")
def test_handle_imported_book_reviews_disabled(self):

View file

@ -2,7 +2,7 @@
from collections import namedtuple
from django.db import IntegrityError
from django.db.models import OuterRef, Subquery
from django.db.models import OuterRef, Subquery, F
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.http import HttpResponseBadRequest, HttpResponseNotFound
@ -69,7 +69,8 @@ class Shelf(View):
reviews = privacy_filter(request.user, reviews)
books = books.annotate(
rating=Subquery(reviews.values("rating")[:1])
rating=Subquery(reviews.values("rating")[:1]),
shelved_date=F("shelfbook__shelved_date"),
).prefetch_related("authors")
paginated = Paginator(

44
bw-dev
View file

@ -31,6 +31,17 @@ function initdb {
execweb python manage.py initdb
}
function awscommand {
# expose env vars
export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
export AWS_DEFAULT_REGION=${AWS_S3_REGION_NAME}
# first arg is mountpoint, second is the whole aws command
docker run --rm -it -v $1\
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_DEFAULT_REGION\
amazon/aws-cli $2
}
CMD=$1
if [ -n "$CMD" ]; then
shift
@ -83,6 +94,19 @@ case "$CMD" in
generate_preview_images)
runweb python manage.py generate_preview_images $@
;;
copy_media_to_s3)
awscommand "bookwyrm_media_volume:/images"\
"s3 cp /images s3://${AWS_STORAGE_BUCKET_NAME}/images\
--endpoint-url ${AWS_S3_ENDPOINT_URL}\
--recursive --acl public-read"
;;
set_cors_to_s3)
awscommand "$(pwd):/bw"\
"s3api put-bucket-cors\
--bucket ${AWS_STORAGE_BUCKET_NAME}\
--endpoint-url ${AWS_S3_ENDPOINT_URL}\
--cors-configuration file:///bw/$@"
;;
runweb)
runweb "$@"
;;
@ -91,6 +115,24 @@ case "$CMD" in
;;
*)
set +x # No need to echo echo
echo "Unrecognised command. Try: build, up, initdb, migrate, bash, shell, dbshell, restart_celery, update, populate_streams, generate_preview_images"
echo "Unrecognised command. Try:"
echo " up [container]"
echo " run"
echo " initdb"
echo " makemigrations [migration]"
echo " migrate [migration]"
echo " bash"
echo " shell"
echo " dbshell"
echo " restart_celery"
echo " collectstatic"
echo " build"
echo " clean"
echo " populate_streams"
echo " generate_preview_images [--all]"
echo " copy_media_to_s3"
echo " set_cors_to_s3 [cors file]"
echo " runweb [command]"
echo " rundb [command]"
;;
esac

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -15,3 +15,5 @@ requests==2.22.0
responses==0.10.14
django-rename-app==0.1.2
pytz>=2021.1
boto3==1.17.88
django-storages==1.11.1