diff --git a/.env.dev.example b/.env.dev.example index 427b1130a..d4476fd24 100644 --- a/.env.dev.example +++ b/.env.dev.example @@ -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=true +USE_HTTPS=false DOMAIN=your.domain.here #EMAIL=your@email.here @@ -45,6 +46,22 @@ EMAIL_USE_SSL=false # Thumbnails Generation ENABLE_THUMBNAIL_GENERATION=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 diff --git a/.env.prod.example b/.env.prod.example index 6d7d57ad2..99520916a 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -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 @@ -45,6 +46,22 @@ EMAIL_USE_SSL=false # Thumbnails Generation ENABLE_THUMBNAIL_GENERATION=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 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 56af524e4..0f663aa33 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: '' -labels: '' +labels: 'bug' assignees: '' --- @@ -23,6 +23,14 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. +**Instance** +On which BookWyrm instance did you encounter this problem. + +**Additional context** +Add any other context about the problem here. + +--- + **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] @@ -33,6 +41,3 @@ If applicable, add screenshots to help explain your problem. - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/workflows/django-tests.yml b/.github/workflows/django-tests.yml index b5b319f53..c11b7c408 100644 --- a/.github/workflows/django-tests.yml +++ b/.github/workflows/django-tests.yml @@ -20,7 +20,7 @@ jobs: services: postgres: - image: postgres:10 + image: postgres:12 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: hunter2 @@ -66,4 +66,4 @@ jobs: EMAIL_USE_TLS: true ENABLE_PREVIEW_IMAGES: true run: | - python manage.py test + pytest diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 6c032b830..fb102ea4b 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -2,11 +2,10 @@ from abc import ABC, abstractmethod from dataclasses import asdict, dataclass import logging -from urllib3.exceptions import RequestError from django.db import transaction import requests -from requests.exceptions import SSLError +from requests.exceptions import RequestException from bookwyrm import activitypub, models, settings from .connector_manager import load_more_data, ConnectorException @@ -237,7 +236,7 @@ def get_data(url, params=None, timeout=10): }, timeout=timeout, ) - except (RequestError, SSLError, ConnectionError) as err: + except RequestException as err: logger.exception(err) raise ConnectorException() @@ -262,7 +261,7 @@ def get_image(url, timeout=10): }, timeout=timeout, ) - except (RequestError, SSLError) as err: + except RequestException as err: logger.exception(err) return None if not resp.ok: diff --git a/bookwyrm/connectors/self_connector.py b/bookwyrm/connectors/self_connector.py index 930b7cb3d..8d5a7614e 100644 --- a/bookwyrm/connectors/self_connector.py +++ b/bookwyrm/connectors/self_connector.py @@ -2,7 +2,7 @@ from functools import reduce import operator -from django.contrib.postgres.search import SearchRank, SearchVector +from django.contrib.postgres.search import SearchRank, SearchQuery from django.db.models import OuterRef, Subquery, F, Q from bookwyrm import models @@ -13,7 +13,7 @@ class Connector(AbstractConnector): """instantiate a connector""" # pylint: disable=arguments-differ - def search(self, query, min_confidence=0.1, raw=False, filters=None): + def search(self, query, min_confidence=0, raw=False, filters=None): """search your local database""" filters = filters or [] if not query: @@ -141,16 +141,11 @@ def search_identifiers(query, *filters): def search_title_author(query, min_confidence, *filters): """searches for title and author""" - vector = ( - SearchVector("title", weight="A") - + SearchVector("subtitle", weight="B") - + SearchVector("authors__name", weight="C") - + SearchVector("series", weight="D") - ) - + query = SearchQuery(query, config="simple") | SearchQuery(query, config="english") results = ( - models.Edition.objects.annotate(rank=SearchRank(vector, query)) - .filter(*filters, rank__gt=min_confidence) + models.Edition.objects.filter(*filters, search_vector=query) + .annotate(rank=SearchRank(F("search_vector"), query)) + .filter(rank__gt=min_confidence) .order_by("-rank") ) diff --git a/bookwyrm/context_processors.py b/bookwyrm/context_processors.py index fe81c51ac..afda6b260 100644 --- a/bookwyrm/context_processors.py +++ b/bookwyrm/context_processors.py @@ -12,10 +12,7 @@ def site_settings(request): # pylint: disable=unused-argument "site": models.SiteSettings.objects.get(), "active_announcements": models.Announcement.active_announcements(), "enable_thumbnail_generation": settings.ENABLE_THUMBNAIL_GENERATION, - "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, } diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index cb55d229e..57a94e3cd 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -183,6 +183,7 @@ class EditionForm(CustomForm): "parent_work", "shelves", "connector", + "search_vector", ] @@ -194,6 +195,7 @@ class AuthorForm(CustomForm): "origin_id", "created_date", "updated_date", + "search_vector", ] diff --git a/bookwyrm/importers/importer.py b/bookwyrm/importers/importer.py index 203db0343..d5f1449ca 100644 --- a/bookwyrm/importers/importer.py +++ b/bookwyrm/importers/importer.py @@ -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 diff --git a/bookwyrm/migrations/0077_auto_20210623_2155.py b/bookwyrm/migrations/0077_auto_20210623_2155.py new file mode 100644 index 000000000..a73c43825 --- /dev/null +++ b/bookwyrm/migrations/0077_auto_20210623_2155.py @@ -0,0 +1,126 @@ +# Generated by Django 3.2.4 on 2021-06-23 21:55 + +import django.contrib.postgres.indexes +import django.contrib.postgres.search +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0076_preview_images"), + ] + + operations = [ + migrations.AddField( + model_name="author", + name="search_vector", + field=django.contrib.postgres.search.SearchVectorField(null=True), + ), + migrations.AddField( + model_name="book", + name="search_vector", + field=django.contrib.postgres.search.SearchVectorField(null=True), + ), + migrations.AddIndex( + model_name="author", + index=django.contrib.postgres.indexes.GinIndex( + fields=["search_vector"], name="bookwyrm_au_search__b050a8_gin" + ), + ), + migrations.AddIndex( + model_name="book", + index=django.contrib.postgres.indexes.GinIndex( + fields=["search_vector"], name="bookwyrm_bo_search__51beb3_gin" + ), + ), + migrations.RunSQL( + sql=""" + CREATE FUNCTION book_trigger() RETURNS trigger AS $$ + begin + new.search_vector := + coalesce( + NULLIF(setweight(to_tsvector('english', coalesce(new.title, '')), 'A'), ''), + setweight(to_tsvector('simple', coalesce(new.title, '')), 'A') + ) || + setweight(to_tsvector('english', coalesce(new.subtitle, '')), 'B') || + (SELECT setweight(to_tsvector('simple', coalesce(array_to_string(array_agg(bookwyrm_author.name), ' '), '')), 'C') + FROM bookwyrm_book + LEFT OUTER JOIN bookwyrm_book_authors + ON bookwyrm_book.id = bookwyrm_book_authors.book_id + LEFT OUTER JOIN bookwyrm_author + ON bookwyrm_book_authors.author_id = bookwyrm_author.id + WHERE bookwyrm_book.id = new.id + ) || + setweight(to_tsvector('english', coalesce(new.series, '')), 'D'); + return new; + end + $$ LANGUAGE plpgsql; + + CREATE TRIGGER search_vector_trigger + BEFORE INSERT OR UPDATE OF title, subtitle, series, search_vector + ON bookwyrm_book + FOR EACH ROW EXECUTE FUNCTION book_trigger(); + + UPDATE bookwyrm_book SET search_vector = NULL; + """, + reverse_sql=""" + DROP TRIGGER IF EXISTS search_vector_trigger + ON bookwyrm_book; + DROP FUNCTION IF EXISTS book_trigger; + """, + ), + # when an author is edited + migrations.RunSQL( + sql=""" + CREATE FUNCTION author_trigger() RETURNS trigger AS $$ + begin + WITH book AS ( + SELECT bookwyrm_book.id as row_id + FROM bookwyrm_author + LEFT OUTER JOIN bookwyrm_book_authors + ON bookwyrm_book_authors.id = new.id + LEFT OUTER JOIN bookwyrm_book + ON bookwyrm_book.id = bookwyrm_book_authors.book_id + ) + UPDATE bookwyrm_book SET search_vector = '' + FROM book + WHERE id = book.row_id; + return new; + end + $$ LANGUAGE plpgsql; + + CREATE TRIGGER author_search_vector_trigger + AFTER UPDATE OF name + ON bookwyrm_author + FOR EACH ROW EXECUTE FUNCTION author_trigger(); + """, + reverse_sql=""" + DROP TRIGGER IF EXISTS author_search_vector_trigger + ON bookwyrm_author; + DROP FUNCTION IF EXISTS author_trigger; + """, + ), + # when an author is added to or removed from a book + migrations.RunSQL( + sql=""" + CREATE FUNCTION book_authors_trigger() RETURNS trigger AS $$ + begin + UPDATE bookwyrm_book SET search_vector = '' + WHERE id = coalesce(new.book_id, old.book_id); + return new; + end + $$ LANGUAGE plpgsql; + + CREATE TRIGGER book_authors_search_vector_trigger + AFTER INSERT OR DELETE + ON bookwyrm_book_authors + FOR EACH ROW EXECUTE FUNCTION book_authors_trigger(); + """, + reverse_sql=""" + DROP TRIGGER IF EXISTS book_authors_search_vector_trigger + ON bookwyrm_book_authors; + DROP FUNCTION IF EXISTS book_authors_trigger; + """, + ), + ] diff --git a/bookwyrm/migrations/0078_add_shelved_date.py b/bookwyrm/migrations/0078_add_shelved_date.py new file mode 100644 index 000000000..b8a95ab17 --- /dev/null +++ b/bookwyrm/migrations/0078_add_shelved_date.py @@ -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), + ] diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 83b4c0abe..729d9cba0 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -30,6 +30,7 @@ logger = logging.getLogger(__name__) PropertyField = namedtuple("PropertyField", ("set_activity_from_field")) +# pylint: disable=invalid-name def set_activity_from_property_field(activity, obj, field): """assign a model property value to the activity json""" activity[field[1]] = getattr(obj, field[0]) @@ -318,7 +319,9 @@ class OrderedCollectionPageMixin(ObjectMixin): remote_id = remote_id or self.remote_id if page: - return to_ordered_collection_page(queryset, remote_id, **kwargs) + if isinstance(page, list) and len(page) > 0: + page = page[0] + return to_ordered_collection_page(queryset, remote_id, page=page, **kwargs) if collection_only or not hasattr(self, "activity_serializer"): serializer = activitypub.OrderedCollection diff --git a/bookwyrm/models/author.py b/bookwyrm/models/author.py index c4e26c5ab..6da80b176 100644 --- a/bookwyrm/models/author.py +++ b/bookwyrm/models/author.py @@ -1,4 +1,5 @@ """ database schema for info about authors """ +from django.contrib.postgres.indexes import GinIndex from django.db import models from bookwyrm import activitypub @@ -37,3 +38,8 @@ class Author(BookDataModel): return "https://%s/author/%s" % (DOMAIN, self.id) activity_serializer = activitypub.Author + + class Meta: + """sets up postgres GIN index field""" + + indexes = (GinIndex(fields=["search_vector"]),) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 189cbb897..8bed69249 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -1,6 +1,8 @@ """ database schema for books and shelves """ import re +from django.contrib.postgres.search import SearchVectorField +from django.contrib.postgres.indexes import GinIndex from django.db import models from django.dispatch import receiver from model_utils import FieldTracker @@ -40,6 +42,7 @@ class BookDataModel(ObjectMixin, BookWyrmModel): bnf_id = fields.CharField( # Bibliothèque nationale de France max_length=255, blank=True, null=True, deduplication_field=True ) + search_vector = SearchVectorField(null=True) last_edited_by = fields.ForeignKey( "User", @@ -182,6 +185,11 @@ class Book(BookDataModel): self.title, ) + class Meta: + """sets up postgres GIN index field""" + + indexes = (GinIndex(fields=["search_vector"]),) + class Work(OrderedCollectionPageMixin, Book): """a work (an abstract concept of a book that manifests in an edition)""" diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 4ec46504c..b59b61072 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -408,7 +408,8 @@ class ImageField(ActivitypubFieldMixin, models.ImageField): return None image_content = ContentFile(response.content) - image_name = str(uuid4()) + "." + imghdr.what(None, image_content.read()) + extension = imghdr.what(None, image_content.read()) or "" + image_name = "{:s}.{:s}".format(str(uuid4()), extension) return [image_name, image_content] def formfield(self, **kwargs): diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 4110ae8dc..c4e907d27 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -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") diff --git a/bookwyrm/preview_images.py b/bookwyrm/preview_images.py index 510625e39..29c4961c2 100644 --- a/bookwyrm/preview_images.py +++ b/bookwyrm/preview_images.py @@ -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() diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 5bdd41021..a811aedad 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -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", ["*"]) @@ -75,6 +76,7 @@ INSTALLED_APPS = [ "bookwyrm", "celery", "imagekit", + "storages", ] MIDDLEWARE = [ @@ -167,6 +169,7 @@ LANGUAGES = [ ("es", _("Spanish")), ("fr-fr", _("French")), ("zh-hans", _("Simplified Chinese")), + ("zh-hant", _("Traditional Chinese")), ] @@ -179,17 +182,6 @@ 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, @@ -199,3 +191,45 @@ USER_AGENT = "%s (BookWyrm/%s; +https://%s/)" % ( # Imagekit generated thumbnails ENABLE_THUMBNAIL_GENERATION = env.bool("ENABLE_THUMBNAIL_GENERATION", False) IMAGEKIT_CACHEFILE_DIR = "thumbnails" + +# 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")) diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index 5a4495c1e..a2434a6f1 100644 --- a/bookwyrm/static/css/bookwyrm.css +++ b/bookwyrm/static/css/bookwyrm.css @@ -72,6 +72,14 @@ body { flex-grow: 1; } +.preserve-whitespace p { + white-space: pre-wrap !important; +} + +.display-inline p { + display: inline !important; +} + /** Shelving ******************************************************************************/ diff --git a/bookwyrm/storage_backends.py b/bookwyrm/storage_backends.py new file mode 100644 index 000000000..e10dfb841 --- /dev/null +++ b/bookwyrm/storage_backends.py @@ -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 diff --git a/bookwyrm/tasks.py b/bookwyrm/tasks.py index 23765f09b..6d1992a77 100644 --- a/bookwyrm/tasks.py +++ b/bookwyrm/tasks.py @@ -7,6 +7,5 @@ from bookwyrm import settings # set the default Django settings module for the 'celery' program. os.environ.setdefault("DJANGO_SETTINGS_MODULE", "celerywyrm.settings") app = Celery( - "tasks", - broker=settings.CELERY_BROKER, + "tasks", broker=settings.CELERY_BROKER, backend=settings.CELERY_RESULT_BACKEND ) diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html index eb9e9cb87..cc34de82d 100644 --- a/bookwyrm/templates/book/book.html +++ b/bookwyrm/templates/book/book.html @@ -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 %} @@ -47,7 +52,7 @@ {% if user_authenticated and can_edit_book %}
- + {% trans "Edit Book" %} @@ -209,24 +214,24 @@ @@ -321,5 +326,5 @@ {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/bookwyrm/templates/directory/user_card.html b/bookwyrm/templates/directory/user_card.html index c52c1f7ae..b7941826b 100644 --- a/bookwyrm/templates/directory/user_card.html +++ b/bookwyrm/templates/directory/user_card.html @@ -18,7 +18,7 @@
-
+
{% if user.summary %} {{ user.summary|to_markdown|safe|truncatechars_html:40 }} {% else %} {% endif %} diff --git a/bookwyrm/templates/feed/direct_messages.html b/bookwyrm/templates/feed/direct_messages.html index 0b7dcd79c..30a50cd0a 100644 --- a/bookwyrm/templates/feed/direct_messages.html +++ b/bookwyrm/templates/feed/direct_messages.html @@ -1,4 +1,4 @@ -{% extends 'feed/feed_layout.html' %} +{% extends 'feed/layout.html' %} {% load i18n %} {% block panel %} diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 21e71ae18..44ee5c930 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -1,4 +1,4 @@ -{% extends 'feed/feed_layout.html' %} +{% extends 'feed/layout.html' %} {% load i18n %} {% block panel %} diff --git a/bookwyrm/templates/feed/feed_layout.html b/bookwyrm/templates/feed/feed_layout.html deleted file mode 100644 index 3c6b65a1b..000000000 --- a/bookwyrm/templates/feed/feed_layout.html +++ /dev/null @@ -1,107 +0,0 @@ -{% extends 'layout.html' %} -{% load i18n %} - -{% block title %}{% trans "Updates" %}{% endblock %} - -{% block content %} -
- {% if user.is_authenticated %} -
-

{% trans "Your books" %}

- {% if not suggested_books %} -

{% trans "There are no books here right now! Try searching for a book to get started" %}

- {% else %} - {% with active_book=request.GET.book %} -
-
-
    - {% for shelf in suggested_books %} - {% if shelf.books %} - {% with shelf_counter=forloop.counter %} -
  • -

    - {% 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 %} -

    -
    - -
    -
  • - {% endwith %} - {% endif %} - {% endfor %} -
-
- {% for shelf in suggested_books %} - {% with shelf_counter=forloop.counter %} - {% for book in shelf.books %} -
- -
-
-
-

{% include 'snippets/book_titleby.html' with book=book %}

- {% include 'snippets/shelve_button/shelve_button.html' with book=book %} -
-
-
- {% 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 %} -
-
-
- {% include 'snippets/create_status.html' with book=book %} -
-
- {% endfor %} - {% endwith %} - {% endfor %} -
- {% endwith %} - {% endif %} - - {% if goal %} -
-
-

{% blocktrans with yar=goal.year %}{{ year }} Reading Goal{% endblocktrans %}

- {% include 'snippets/goal_progress.html' with goal=goal %} -
-
- {% endif %} -
- {% endif %} - -
- {% block panel %}{% endblock %} - - {% if activities %} - {% include 'snippets/pagination.html' with page=activities path=path anchor="#feed" %} - {% endif %} -
-
-{% endblock %} - -{% block scripts %} - -{% endblock %} diff --git a/bookwyrm/templates/feed/layout.html b/bookwyrm/templates/feed/layout.html new file mode 100644 index 000000000..ad7d39f0c --- /dev/null +++ b/bookwyrm/templates/feed/layout.html @@ -0,0 +1,110 @@ +{% extends 'layout.html' %} +{% load i18n %} +{% load static %} + +{% block title %}{% trans "Updates" %}{% endblock %} + +{% block content %} +
+ {% if user.is_authenticated %} +
+
+

{% trans "Your books" %}

+ {% if not suggested_books %} +

{% trans "There are no books here right now! Try searching for a book to get started" %}

+ {% else %} + {% with active_book=request.GET.book %} +
+
+
    + {% for shelf in suggested_books %} + {% if shelf.books %} + {% with shelf_counter=forloop.counter %} +
  • +

    + {% 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 %} +

    +
    + +
    +
  • + {% endwith %} + {% endif %} + {% endfor %} +
+
+ {% for shelf in suggested_books %} + {% with shelf_counter=forloop.counter %} + {% for book in shelf.books %} +
+ +
+
+
+

{% include 'snippets/book_titleby.html' with book=book %}

+ {% include 'snippets/shelve_button/shelve_button.html' with book=book %} +
+
+
+ {% 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 %} +
+
+
+ {% include 'snippets/create_status.html' with book=book %} +
+
+ {% endfor %} + {% endwith %} + {% endfor %} +
+ {% endwith %} + {% endif %} +
+ + {% if goal %} +
+
+

{% blocktrans with yar=goal.year %}{{ year }} Reading Goal{% endblocktrans %}

+ {% include 'snippets/goal_progress.html' with goal=goal %} +
+
+ {% endif %} +
+ {% endif %} + +
+ {% block panel %}{% endblock %} + + {% if activities %} + {% include 'snippets/pagination.html' with page=activities path=path anchor="#feed" %} + {% endif %} +
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/bookwyrm/templates/feed/status.html b/bookwyrm/templates/feed/status.html index 9f90f355d..903ca7907 100644 --- a/bookwyrm/templates/feed/status.html +++ b/bookwyrm/templates/feed/status.html @@ -1,4 +1,4 @@ -{% extends 'feed/feed_layout.html' %} +{% extends 'feed/layout.html' %} {% load i18n %} {% block panel %} diff --git a/bookwyrm/templates/get_started/layout.html b/bookwyrm/templates/get_started/layout.html index 63c042895..08a2e7c54 100644 --- a/bookwyrm/templates/get_started/layout.html +++ b/bookwyrm/templates/get_started/layout.html @@ -1,5 +1,6 @@ {% extends 'layout.html' %} {% load i18n %} +{% load static %} {% block title %}{% trans "Welcome" %}{% endblock %} @@ -9,7 +10,7 @@