diff --git a/.env.dev.example b/.env.dev.example index 3cba06ab4..f42aaaaec 100644 --- a/.env.dev.example +++ b/.env.dev.example @@ -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 diff --git a/.env.prod.example b/.env.prod.example index d61c46af5..5115469ca 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 @@ -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 diff --git a/bookwyrm/context_processors.py b/bookwyrm/context_processors.py index f2661a193..1f0387fe7 100644 --- a/bookwyrm/context_processors.py +++ b/bookwyrm/context_processors.py @@ -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, } 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/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/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 50b526bd9..17fcfabe5 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", ["*"]) @@ -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")) diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css index 3db25d1fe..d10fb9b7e 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/templates/book/book.html b/bookwyrm/templates/book/book.html index 9a75cbe94..c5dab1097 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 %} @@ -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 @@