mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-10-31 22:19:00 +00:00
Merge branch 'main' into production
This commit is contained in:
commit
8534e49f96
138 changed files with 3161 additions and 2224 deletions
|
@ -41,3 +41,15 @@ EMAIL_HOST_USER=mail@your.domain.here
|
|||
EMAIL_HOST_PASSWORD=emailpassword123
|
||||
EMAIL_USE_TLS=true
|
||||
EMAIL_USE_SSL=false
|
||||
|
||||
# Preview image generation can be computing and storage intensive
|
||||
# ENABLE_PREVIEW_IMAGES=True
|
||||
|
||||
# Specify RGB tuple or RGB hex strings,
|
||||
# or use_dominant_color_light / use_dominant_color_dark
|
||||
PREVIEW_BG_COLOR=use_dominant_color_light
|
||||
# Change to #FFF if you use use_dominant_color_dark
|
||||
PREVIEW_TEXT_COLOR="#363636"
|
||||
PREVIEW_IMG_WIDTH=1200
|
||||
PREVIEW_IMG_HEIGHT=630
|
||||
PREVIEW_DEFAULT_COVER_COLOR="#002549"
|
||||
|
|
|
@ -41,3 +41,15 @@ EMAIL_HOST_USER=mail@your.domain.here
|
|||
EMAIL_HOST_PASSWORD=emailpassword123
|
||||
EMAIL_USE_TLS=true
|
||||
EMAIL_USE_SSL=false
|
||||
|
||||
# Preview image generation can be computing and storage intensive
|
||||
# ENABLE_PREVIEW_IMAGES=True
|
||||
|
||||
# Specify RGB tuple or RGB hex strings,
|
||||
# or use_dominant_color_light / use_dominant_color_dark
|
||||
PREVIEW_BG_COLOR=use_dominant_color_light
|
||||
# Change to #FFF if you use use_dominant_color_dark
|
||||
PREVIEW_TEXT_COLOR="#363636"
|
||||
PREVIEW_IMG_WIDTH=1200
|
||||
PREVIEW_IMG_HEIGHT=630
|
||||
PREVIEW_DEFAULT_COVER_COLOR="#002549"
|
||||
|
|
2
.github/workflows/black.yml
vendored
2
.github/workflows/black.yml
vendored
|
@ -1,4 +1,4 @@
|
|||
name: Lint Python
|
||||
name: Python Formatting (run ./bw-dev black to fix)
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
|
|
5
.github/workflows/django-tests.yml
vendored
5
.github/workflows/django-tests.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
|||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:10
|
||||
image: postgres:12
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: hunter2
|
||||
|
@ -64,5 +64,6 @@ jobs:
|
|||
EMAIL_HOST_USER: ""
|
||||
EMAIL_HOST_PASSWORD: ""
|
||||
EMAIL_USE_TLS: true
|
||||
ENABLE_PREVIEW_IMAGES: true
|
||||
run: |
|
||||
python manage.py test
|
||||
pytest
|
||||
|
|
24
.github/workflows/pylint.yml
vendored
Normal file
24
.github/workflows/pylint.yml
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
name: Pylint
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install pylint
|
||||
- name: Analysing the code with pylint
|
||||
run: |
|
||||
pylint bookwyrm/ --ignore=migrations,tests --disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -27,3 +27,6 @@
|
|||
|
||||
#nginx
|
||||
nginx/default.conf
|
||||
|
||||
#macOS
|
||||
**/.DS_Store
|
||||
|
|
|
@ -37,6 +37,7 @@ class Mention(Link):
|
|||
|
||||
|
||||
@dataclass
|
||||
# pylint: disable=invalid-name
|
||||
class Signature:
|
||||
"""public key block"""
|
||||
|
||||
|
@ -56,11 +57,11 @@ def naive_parse(activity_objects, activity_json, serializer=None):
|
|||
activity_type = activity_json.get("type")
|
||||
try:
|
||||
serializer = activity_objects[activity_type]
|
||||
except KeyError as e:
|
||||
except KeyError as err:
|
||||
# we know this exists and that we can't handle it
|
||||
if activity_type in ["Question"]:
|
||||
return None
|
||||
raise ActivitySerializerError(e)
|
||||
raise ActivitySerializerError(err)
|
||||
|
||||
return serializer(activity_objects=activity_objects, **activity_json)
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ from .base_activity import ActivityObject
|
|||
from .image import Document
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class BookData(ActivityObject):
|
||||
"""shared fields for all book data and authors"""
|
||||
|
@ -18,6 +19,7 @@ class BookData(ActivityObject):
|
|||
lastEditedBy: str = None
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Book(BookData):
|
||||
"""serializes an edition or work, abstract"""
|
||||
|
@ -40,6 +42,7 @@ class Book(BookData):
|
|||
type: str = "Book"
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Edition(Book):
|
||||
"""Edition instance of a book object"""
|
||||
|
@ -57,6 +60,7 @@ class Edition(Book):
|
|||
type: str = "Edition"
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Work(Book):
|
||||
"""work instance of a book object"""
|
||||
|
@ -66,6 +70,7 @@ class Work(Book):
|
|||
type: str = "Work"
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Author(BookData):
|
||||
"""author of a book"""
|
||||
|
|
|
@ -19,6 +19,7 @@ class Tombstone(ActivityObject):
|
|||
return model.find_existing_by_remote_id(self.id)
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Note(ActivityObject):
|
||||
"""Note activity"""
|
||||
|
@ -52,6 +53,7 @@ class GeneratedNote(Note):
|
|||
type: str = "GeneratedNote"
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Comment(Note):
|
||||
"""like a note but with a book"""
|
||||
|
|
|
@ -5,6 +5,7 @@ from typing import List
|
|||
from .base_activity import ActivityObject
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class OrderedCollection(ActivityObject):
|
||||
"""structure of an ordered collection activity"""
|
||||
|
@ -17,6 +18,7 @@ class OrderedCollection(ActivityObject):
|
|||
type: str = "OrderedCollection"
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class OrderedCollectionPrivate(OrderedCollection):
|
||||
"""an ordered collection with privacy settings"""
|
||||
|
@ -41,6 +43,7 @@ class BookList(OrderedCollectionPrivate):
|
|||
type: str = "BookList"
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class OrderedCollectionPage(ActivityObject):
|
||||
"""structure of an ordered collection activity"""
|
||||
|
|
|
@ -6,6 +6,7 @@ from .base_activity import ActivityObject
|
|||
from .image import Image
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class PublicKey(ActivityObject):
|
||||
"""public key block"""
|
||||
|
@ -15,6 +16,7 @@ class PublicKey(ActivityObject):
|
|||
type: str = "PublicKey"
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Person(ActivityObject):
|
||||
"""actor activitypub json"""
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
""" ActivityPub-specific json response wrapper """
|
||||
from django.http import JsonResponse
|
||||
|
||||
from .base_activity import ActivityEncoder
|
||||
|
|
|
@ -22,6 +22,7 @@ class Verb(ActivityObject):
|
|||
self.object.to_model()
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Create(Verb):
|
||||
"""Create activity"""
|
||||
|
@ -32,6 +33,7 @@ class Create(Verb):
|
|||
type: str = "Create"
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Delete(Verb):
|
||||
"""Create activity"""
|
||||
|
@ -57,6 +59,7 @@ class Delete(Verb):
|
|||
# if we can't find it, we don't need to delete it because we don't have it
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Update(Verb):
|
||||
"""Update activity"""
|
||||
|
@ -192,6 +195,7 @@ class Like(Verb):
|
|||
self.to_model()
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@dataclass(init=False)
|
||||
class Announce(Verb):
|
||||
"""boosting a status"""
|
||||
|
|
|
@ -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
|
||||
|
@ -127,8 +126,8 @@ class AbstractConnector(AbstractMinimalConnector):
|
|||
edition_data = data
|
||||
try:
|
||||
work_data = self.get_work_from_edition_data(data)
|
||||
except (KeyError, ConnectorException) as e:
|
||||
logger.exception(e)
|
||||
except (KeyError, ConnectorException) as err:
|
||||
logger.exception(err)
|
||||
work_data = data
|
||||
|
||||
if not work_data or not edition_data:
|
||||
|
@ -237,16 +236,16 @@ def get_data(url, params=None, timeout=10):
|
|||
},
|
||||
timeout=timeout,
|
||||
)
|
||||
except (RequestError, SSLError, ConnectionError) as e:
|
||||
logger.exception(e)
|
||||
except RequestException as err:
|
||||
logger.exception(err)
|
||||
raise ConnectorException()
|
||||
|
||||
if not resp.ok:
|
||||
raise ConnectorException()
|
||||
try:
|
||||
data = resp.json()
|
||||
except ValueError as e:
|
||||
logger.exception(e)
|
||||
except ValueError as err:
|
||||
logger.exception(err)
|
||||
raise ConnectorException()
|
||||
|
||||
return data
|
||||
|
@ -262,8 +261,8 @@ def get_image(url, timeout=10):
|
|||
},
|
||||
timeout=timeout,
|
||||
)
|
||||
except (RequestError, SSLError) as e:
|
||||
logger.exception(e)
|
||||
except RequestException as err:
|
||||
logger.exception(err)
|
||||
return None
|
||||
if not resp.ok:
|
||||
return None
|
||||
|
|
|
@ -38,17 +38,17 @@ def search(query, min_confidence=0.1, return_first=False):
|
|||
# Search on ISBN
|
||||
try:
|
||||
result_set = connector.isbn_search(isbn)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
logger.exception(e)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
logger.exception(err)
|
||||
# if this fails, we can still try regular search
|
||||
|
||||
# if no isbn search results, we fallback to generic search
|
||||
if not result_set:
|
||||
try:
|
||||
result_set = connector.search(query, min_confidence=min_confidence)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
# we don't want *any* error to crash the whole search page
|
||||
logger.exception(e)
|
||||
logger.exception(err)
|
||||
continue
|
||||
|
||||
if return_first and result_set:
|
||||
|
|
|
@ -74,7 +74,7 @@ class Connector(AbstractConnector):
|
|||
**{k: data.get(k) for k in ["uri", "image", "labels", "sitelinks"]},
|
||||
}
|
||||
|
||||
def search(self, query, min_confidence=None):
|
||||
def search(self, query, min_confidence=None): # pylint: disable=arguments-differ
|
||||
"""overrides default search function with confidence ranking"""
|
||||
results = super().search(query)
|
||||
if min_confidence:
|
||||
|
|
|
@ -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:
|
||||
|
@ -114,6 +114,7 @@ class Connector(AbstractConnector):
|
|||
|
||||
def search_identifiers(query, *filters):
|
||||
"""tries remote_id, isbn; defined as dedupe fields on the model"""
|
||||
# pylint: disable=W0212
|
||||
or_filters = [
|
||||
{f.name: query}
|
||||
for f in models.Edition._meta.get_fields()
|
||||
|
@ -140,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")
|
||||
)
|
||||
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
""" customize the info available in context for rendering templates """
|
||||
from bookwyrm import models
|
||||
from bookwyrm import models, settings
|
||||
|
||||
|
||||
def site_settings(request): # pylint: disable=unused-argument
|
||||
"""include the custom info about the site"""
|
||||
request_protocol = "https://"
|
||||
if not request.is_secure():
|
||||
request_protocol = "http://"
|
||||
|
||||
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,
|
||||
"preview_images_enabled": settings.ENABLE_PREVIEW_IMAGES,
|
||||
"request_protocol": request_protocol,
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ class CustomForm(ModelForm):
|
|||
css_classes["number"] = "input"
|
||||
css_classes["checkbox"] = "checkbox"
|
||||
css_classes["textarea"] = "textarea"
|
||||
# pylint: disable=super-with-arguments
|
||||
super(CustomForm, self).__init__(*args, **kwargs)
|
||||
for visible in self.visible_fields():
|
||||
if hasattr(visible.field.widget, "input_type"):
|
||||
|
@ -181,9 +182,8 @@ class EditionForm(CustomForm):
|
|||
"authors",
|
||||
"parent_work",
|
||||
"shelves",
|
||||
"subjects", # TODO
|
||||
"subject_places", # TODO
|
||||
"connector",
|
||||
"search_vector",
|
||||
]
|
||||
|
||||
|
||||
|
@ -195,6 +195,7 @@ class AuthorForm(CustomForm):
|
|||
"origin_id",
|
||||
"created_date",
|
||||
"updated_date",
|
||||
"search_vector",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -67,8 +67,8 @@ def import_data(source, job_id):
|
|||
for item in job.items.all():
|
||||
try:
|
||||
item.resolve()
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
logger.exception(e)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
logger.exception(err)
|
||||
item.fail_reason = "Error loading book"
|
||||
item.save()
|
||||
continue
|
||||
|
|
65
bookwyrm/management/commands/generate_preview_images.py
Normal file
65
bookwyrm/management/commands/generate_preview_images.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
""" Generate preview images """
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from bookwyrm import models, preview_images
|
||||
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
class Command(BaseCommand):
|
||||
"""Creates previews for existing objects"""
|
||||
|
||||
help = "Generate preview images"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--all",
|
||||
"-a",
|
||||
action="store_true",
|
||||
help="Generates images for ALL types: site, users and books. Can use a lot of computing power.",
|
||||
)
|
||||
|
||||
# pylint: disable=no-self-use,unused-argument
|
||||
def handle(self, *args, **options):
|
||||
"""generate preview images"""
|
||||
self.stdout.write(
|
||||
" | Hello! I will be generating preview images for your instance."
|
||||
)
|
||||
if options["all"]:
|
||||
self.stdout.write(
|
||||
"🧑🎨 ⎨ This might take quite long if your instance has a lot of books and users."
|
||||
)
|
||||
self.stdout.write(" | ✧ Thank you for your patience ✧")
|
||||
else:
|
||||
self.stdout.write("🧑🎨 ⎨ I will only generate the instance preview image.")
|
||||
self.stdout.write(" | ✧ Be right back! ✧")
|
||||
|
||||
# Site
|
||||
self.stdout.write(" → Site preview image: ", ending="")
|
||||
preview_images.generate_site_preview_image_task.delay()
|
||||
self.stdout.write(" OK 🖼")
|
||||
|
||||
if options["all"]:
|
||||
# Users
|
||||
users = models.User.objects.filter(
|
||||
local=True,
|
||||
is_active=True,
|
||||
)
|
||||
self.stdout.write(
|
||||
" → User preview images ({}): ".format(len(users)), ending=""
|
||||
)
|
||||
for user in users:
|
||||
preview_images.generate_user_preview_image_task.delay(user.id)
|
||||
self.stdout.write(".", ending="")
|
||||
self.stdout.write(" OK 🖼")
|
||||
|
||||
# Books
|
||||
books = models.Book.objects.select_subclasses().filter()
|
||||
self.stdout.write(
|
||||
" → Book preview images ({}): ".format(len(books)), ending=""
|
||||
)
|
||||
for book in books:
|
||||
preview_images.generate_edition_preview_image_task.delay(book.id)
|
||||
self.stdout.write(".", ending="")
|
||||
self.stdout.write(" OK 🖼")
|
||||
|
||||
self.stdout.write("🧑🎨 ⎨ I’m all done! ✧ Enjoy ✧")
|
32
bookwyrm/migrations/0076_preview_images.py
Normal file
32
bookwyrm/migrations/0076_preview_images.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Generated by Django 3.2 on 2021-05-26 12:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0075_announcement"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="book",
|
||||
name="preview_image",
|
||||
field=models.ImageField(
|
||||
blank=True, null=True, upload_to="previews/covers/"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="sitesettings",
|
||||
name="preview_image",
|
||||
field=models.ImageField(blank=True, null=True, upload_to="previews/logos/"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="preview_image",
|
||||
field=models.ImageField(
|
||||
blank=True, null=True, upload_to="previews/avatars/"
|
||||
),
|
||||
),
|
||||
]
|
126
bookwyrm/migrations/0077_auto_20210623_2155.py
Normal file
126
bookwyrm/migrations/0077_auto_20210623_2155.py
Normal file
|
@ -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;
|
||||
""",
|
||||
),
|
||||
]
|
|
@ -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"]),)
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
""" 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
|
||||
from model_utils.managers import InheritanceManager
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.settings import DOMAIN, DEFAULT_LANGUAGE
|
||||
from bookwyrm.preview_images import generate_edition_preview_image_task
|
||||
from bookwyrm.settings import DOMAIN, DEFAULT_LANGUAGE, ENABLE_PREVIEW_IMAGES
|
||||
|
||||
from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin
|
||||
from .base_model import BookWyrmModel
|
||||
|
@ -31,6 +36,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",
|
||||
|
@ -82,10 +88,14 @@ class Book(BookDataModel):
|
|||
cover = fields.ImageField(
|
||||
upload_to="covers/", blank=True, null=True, alt_field="alt_text"
|
||||
)
|
||||
preview_image = models.ImageField(
|
||||
upload_to="previews/covers/", blank=True, null=True
|
||||
)
|
||||
first_published_date = fields.DateTimeField(blank=True, null=True)
|
||||
published_date = fields.DateTimeField(blank=True, null=True)
|
||||
|
||||
objects = InheritanceManager()
|
||||
field_tracker = FieldTracker(fields=["authors", "title", "subtitle", "cover"])
|
||||
|
||||
@property
|
||||
def author_text(self):
|
||||
|
@ -135,6 +145,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)"""
|
||||
|
@ -293,3 +308,17 @@ def isbn_13_to_10(isbn_13):
|
|||
if checkdigit == 10:
|
||||
checkdigit = "X"
|
||||
return converted + str(checkdigit)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@receiver(models.signals.post_save, sender=Edition)
|
||||
def preview_image(instance, *args, **kwargs):
|
||||
"""create preview image on book create"""
|
||||
if not ENABLE_PREVIEW_IMAGES:
|
||||
return
|
||||
changed_fields = {}
|
||||
if instance.field_tracker:
|
||||
changed_fields = instance.field_tracker.changed()
|
||||
|
||||
if len(changed_fields) > 0:
|
||||
generate_edition_preview_image_task.delay(instance.id)
|
||||
|
|
|
@ -202,6 +202,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
|
|||
*args, max_length=255, choices=PrivacyLevels.choices, default="public"
|
||||
)
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def set_field_from_activity(self, instance, data):
|
||||
to = data.to
|
||||
cc = data.cc
|
||||
|
@ -220,6 +221,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
|
|||
if hasattr(instance, "mention_users"):
|
||||
mentions = [u.remote_id for u in instance.mention_users.all()]
|
||||
# this is a link to the followers list
|
||||
# pylint: disable=protected-access
|
||||
followers = instance.user.__class__._meta.get_field(
|
||||
"followers"
|
||||
).field_to_activity(instance.user.followers)
|
||||
|
@ -406,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):
|
||||
|
|
|
@ -93,7 +93,8 @@ class ListItem(CollectionItemMixin, BookWyrmModel):
|
|||
)
|
||||
|
||||
class Meta:
|
||||
# A book may only be placed into a list once, and each order in the list may be used only
|
||||
# once
|
||||
"""A book may only be placed into a list once,
|
||||
and each order in the list may be used only once"""
|
||||
|
||||
unique_together = (("book", "book_list"), ("order", "book_list"))
|
||||
ordering = ("-created_date",)
|
||||
|
|
|
@ -99,7 +99,7 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
|
|||
status = "follow_request"
|
||||
activity_serializer = activitypub.Follow
|
||||
|
||||
def save(self, *args, broadcast=True, **kwargs):
|
||||
def save(self, *args, broadcast=True, **kwargs): # pylint: disable=arguments-differ
|
||||
"""make sure the follow or block relationship doesn't already exist"""
|
||||
# if there's a request for a follow that already exists, accept it
|
||||
# without changing the local database state
|
||||
|
|
|
@ -4,9 +4,12 @@ import datetime
|
|||
|
||||
from Crypto import Random
|
||||
from django.db import models, IntegrityError
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
from model_utils import FieldTracker
|
||||
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from bookwyrm.preview_images import generate_site_preview_image_task
|
||||
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES
|
||||
from .base_model import BookWyrmModel
|
||||
from .user import User
|
||||
|
||||
|
@ -35,6 +38,9 @@ class SiteSettings(models.Model):
|
|||
logo = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||
logo_small = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||
favicon = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||
preview_image = models.ImageField(
|
||||
upload_to="previews/logos/", null=True, blank=True
|
||||
)
|
||||
|
||||
# footer
|
||||
support_link = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
@ -42,6 +48,8 @@ class SiteSettings(models.Model):
|
|||
admin_email = models.EmailField(max_length=255, null=True, blank=True)
|
||||
footer_item = models.TextField(null=True, blank=True)
|
||||
|
||||
field_tracker = FieldTracker(fields=["name", "instance_tagline", "logo"])
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
"""gets the site settings db entry or defaults"""
|
||||
|
@ -119,3 +127,15 @@ class PasswordReset(models.Model):
|
|||
def link(self):
|
||||
"""formats the invite link"""
|
||||
return "https://{}/password-reset/{}".format(DOMAIN, self.code)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@receiver(models.signals.post_save, sender=SiteSettings)
|
||||
def preview_image(instance, *args, **kwargs):
|
||||
"""Update image preview for the default site image"""
|
||||
if not ENABLE_PREVIEW_IMAGES:
|
||||
return
|
||||
changed_fields = instance.field_tracker.changed()
|
||||
|
||||
if len(changed_fields) > 0:
|
||||
generate_site_preview_image_task.delay()
|
||||
|
|
|
@ -5,11 +5,15 @@ import re
|
|||
from django.apps import apps
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.template.loader import get_template
|
||||
from django.utils import timezone
|
||||
from model_utils import FieldTracker
|
||||
from model_utils.managers import InheritanceManager
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.preview_images import generate_edition_preview_image_task
|
||||
from bookwyrm.settings import ENABLE_PREVIEW_IMAGES
|
||||
from .activitypub_mixin import ActivitypubMixin, ActivityMixin
|
||||
from .activitypub_mixin import OrderedCollectionPageMixin
|
||||
from .base_model import BookWyrmModel
|
||||
|
@ -304,6 +308,8 @@ class Review(Status):
|
|||
max_digits=3,
|
||||
)
|
||||
|
||||
field_tracker = FieldTracker(fields=["rating"])
|
||||
|
||||
@property
|
||||
def pure_name(self):
|
||||
"""clarify review names for mastodon serialization"""
|
||||
|
@ -398,3 +404,17 @@ class Boost(ActivityMixin, Status):
|
|||
# This constraint can't work as it would cross tables.
|
||||
# class Meta:
|
||||
# unique_together = ('user', 'boosted_status')
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@receiver(models.signals.post_save)
|
||||
def preview_image(instance, sender, *args, **kwargs):
|
||||
"""Updates book previews if the rating has changed"""
|
||||
if not ENABLE_PREVIEW_IMAGES or sender not in (Review, ReviewRating):
|
||||
return
|
||||
|
||||
changed_fields = instance.field_tracker.changed()
|
||||
|
||||
if len(changed_fields) > 0:
|
||||
edition = instance.book
|
||||
generate_edition_preview_image_task.delay(edition.id)
|
||||
|
|
|
@ -6,15 +6,18 @@ from django.apps import apps
|
|||
from django.contrib.auth.models import AbstractUser, Group
|
||||
from django.contrib.postgres.fields import CICharField
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.dispatch import receiver
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from model_utils import FieldTracker
|
||||
import pytz
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.connectors import get_data, ConnectorException
|
||||
from bookwyrm.models.shelf import Shelf
|
||||
from bookwyrm.models.status import Status, Review
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from bookwyrm.preview_images import generate_user_preview_image_task
|
||||
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES
|
||||
from bookwyrm.signatures import create_key_pair
|
||||
from bookwyrm.tasks import app
|
||||
from bookwyrm.utils import regex
|
||||
|
@ -70,6 +73,9 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
activitypub_field="icon",
|
||||
alt_field="alt_text",
|
||||
)
|
||||
preview_image = models.ImageField(
|
||||
upload_to="previews/avatars/", blank=True, null=True
|
||||
)
|
||||
followers = fields.ManyToManyField(
|
||||
"self",
|
||||
link_only=True,
|
||||
|
@ -117,6 +123,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
|
||||
name_field = "username"
|
||||
property_fields = [("following_link", "following")]
|
||||
field_tracker = FieldTracker(fields=["name", "avatar"])
|
||||
|
||||
@property
|
||||
def following_link(self):
|
||||
|
@ -232,7 +239,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
def save(self, *args, **kwargs):
|
||||
"""populate fields for new local users"""
|
||||
created = not bool(self.id)
|
||||
if not self.local and not re.match(regex.full_username, self.username):
|
||||
if not self.local and not re.match(regex.FULL_USERNAME, self.username):
|
||||
# generate a username that uses the domain (webfinger format)
|
||||
actor_parts = urlparse(self.remote_id)
|
||||
self.username = "%s@%s" % (self.username, actor_parts.netloc)
|
||||
|
@ -356,7 +363,7 @@ class AnnualGoal(BookWyrmModel):
|
|||
|
||||
def get_remote_id(self):
|
||||
"""put the year in the path"""
|
||||
return "%s/goal/%d" % (self.user.remote_id, self.year)
|
||||
return "{:s}/goal/{:d}".format(self.user.remote_id, self.year)
|
||||
|
||||
@property
|
||||
def books(self):
|
||||
|
@ -443,3 +450,15 @@ def get_remote_reviews(outbox):
|
|||
if not activity["type"] == "Review":
|
||||
continue
|
||||
activitypub.Review(**activity).to_model()
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@receiver(models.signals.post_save, sender=User)
|
||||
def preview_image(instance, *args, **kwargs):
|
||||
"""create preview images when user is updated"""
|
||||
if not ENABLE_PREVIEW_IMAGES:
|
||||
return
|
||||
changed_fields = instance.field_tracker.changed()
|
||||
|
||||
if len(changed_fields) > 0:
|
||||
generate_user_preview_image_task.delay(instance.id)
|
||||
|
|
424
bookwyrm/preview_images.py
Normal file
424
bookwyrm/preview_images.py
Normal file
|
@ -0,0 +1,424 @@
|
|||
""" Generate social media preview images for twitter/mastodon/etc """
|
||||
import math
|
||||
import os
|
||||
import textwrap
|
||||
from io import BytesIO
|
||||
from uuid import uuid4
|
||||
|
||||
import colorsys
|
||||
from colorthief import ColorThief
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageOps, ImageColor
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||
from django.db.models import Avg
|
||||
|
||||
from bookwyrm import models, settings
|
||||
from bookwyrm.tasks import app
|
||||
|
||||
|
||||
IMG_WIDTH = settings.PREVIEW_IMG_WIDTH
|
||||
IMG_HEIGHT = settings.PREVIEW_IMG_HEIGHT
|
||||
BG_COLOR = settings.PREVIEW_BG_COLOR
|
||||
TEXT_COLOR = settings.PREVIEW_TEXT_COLOR
|
||||
DEFAULT_COVER_COLOR = settings.PREVIEW_DEFAULT_COVER_COLOR
|
||||
TRANSPARENT_COLOR = (0, 0, 0, 0)
|
||||
|
||||
margin = math.floor(IMG_HEIGHT / 10)
|
||||
gutter = math.floor(margin / 2)
|
||||
inner_img_height = math.floor(IMG_HEIGHT * 0.8)
|
||||
inner_img_width = math.floor(inner_img_height * 0.7)
|
||||
font_dir = os.path.join(settings.STATIC_ROOT, "fonts/public_sans")
|
||||
|
||||
|
||||
def get_font(font_name, size=28):
|
||||
"""Loads custom font"""
|
||||
if font_name == "light":
|
||||
font_path = os.path.join(font_dir, "PublicSans-Light.ttf")
|
||||
if font_name == "regular":
|
||||
font_path = os.path.join(font_dir, "PublicSans-Regular.ttf")
|
||||
elif font_name == "bold":
|
||||
font_path = os.path.join(font_dir, "PublicSans-Bold.ttf")
|
||||
|
||||
try:
|
||||
font = ImageFont.truetype(font_path, size)
|
||||
except OSError:
|
||||
font = ImageFont.load_default()
|
||||
|
||||
return font
|
||||
|
||||
|
||||
def generate_texts_layer(texts, content_width):
|
||||
"""Adds text for images"""
|
||||
font_text_zero = get_font("bold", size=20)
|
||||
font_text_one = get_font("bold", size=48)
|
||||
font_text_two = get_font("bold", size=40)
|
||||
font_text_three = get_font("regular", size=40)
|
||||
|
||||
text_layer = Image.new("RGBA", (content_width, IMG_HEIGHT), color=TRANSPARENT_COLOR)
|
||||
text_layer_draw = ImageDraw.Draw(text_layer)
|
||||
|
||||
text_y = 0
|
||||
|
||||
if "text_zero" in texts and texts["text_zero"]:
|
||||
# Text one (Book title)
|
||||
text_zero = textwrap.fill(texts["text_zero"], width=72)
|
||||
text_layer_draw.multiline_text(
|
||||
(0, text_y), text_zero, font=font_text_zero, fill=TEXT_COLOR
|
||||
)
|
||||
|
||||
try:
|
||||
text_y = text_y + font_text_zero.getsize_multiline(text_zero)[1] + 16
|
||||
except (AttributeError, IndexError):
|
||||
text_y = text_y + 26
|
||||
|
||||
if "text_one" in texts and texts["text_one"]:
|
||||
# Text one (Book title)
|
||||
text_one = textwrap.fill(texts["text_one"], width=28)
|
||||
text_layer_draw.multiline_text(
|
||||
(0, text_y), text_one, font=font_text_one, fill=TEXT_COLOR
|
||||
)
|
||||
|
||||
try:
|
||||
text_y = text_y + font_text_one.getsize_multiline(text_one)[1] + 16
|
||||
except (AttributeError, IndexError):
|
||||
text_y = text_y + 26
|
||||
|
||||
if "text_two" in texts and texts["text_two"]:
|
||||
# Text one (Book subtitle)
|
||||
text_two = textwrap.fill(texts["text_two"], width=36)
|
||||
text_layer_draw.multiline_text(
|
||||
(0, text_y), text_two, font=font_text_two, fill=TEXT_COLOR
|
||||
)
|
||||
|
||||
try:
|
||||
text_y = text_y + font_text_one.getsize_multiline(text_two)[1] + 16
|
||||
except (AttributeError, IndexError):
|
||||
text_y = text_y + 26
|
||||
|
||||
if "text_three" in texts and texts["text_three"]:
|
||||
# Text three (Book authors)
|
||||
text_three = textwrap.fill(texts["text_three"], width=36)
|
||||
text_layer_draw.multiline_text(
|
||||
(0, text_y), text_three, font=font_text_three, fill=TEXT_COLOR
|
||||
)
|
||||
|
||||
text_layer_box = text_layer.getbbox()
|
||||
return text_layer.crop(text_layer_box)
|
||||
|
||||
|
||||
def generate_instance_layer(content_width):
|
||||
"""Places components for instance preview"""
|
||||
font_instance = get_font("light", size=28)
|
||||
|
||||
site = models.SiteSettings.objects.get()
|
||||
|
||||
if site.logo_small:
|
||||
logo_img = Image.open(site.logo_small)
|
||||
else:
|
||||
try:
|
||||
static_path = os.path.join(settings.STATIC_ROOT, "images/logo-small.png")
|
||||
logo_img = Image.open(static_path)
|
||||
except FileNotFoundError:
|
||||
logo_img = None
|
||||
|
||||
instance_layer = Image.new("RGBA", (content_width, 62), color=TRANSPARENT_COLOR)
|
||||
|
||||
instance_text_x = 0
|
||||
|
||||
if logo_img:
|
||||
logo_img.thumbnail((50, 50), Image.ANTIALIAS)
|
||||
|
||||
instance_layer.paste(logo_img, (0, 0))
|
||||
|
||||
instance_text_x = instance_text_x + 60
|
||||
|
||||
instance_layer_draw = ImageDraw.Draw(instance_layer)
|
||||
instance_layer_draw.text(
|
||||
(instance_text_x, 10), site.name, font=font_instance, fill=TEXT_COLOR
|
||||
)
|
||||
|
||||
line_width = 50 + 10 + font_instance.getsize(site.name)[0]
|
||||
|
||||
line_layer = Image.new(
|
||||
"RGBA", (line_width, 2), color=(*(ImageColor.getrgb(TEXT_COLOR)), 50)
|
||||
)
|
||||
instance_layer.alpha_composite(line_layer, (0, 60))
|
||||
|
||||
return instance_layer
|
||||
|
||||
|
||||
def generate_rating_layer(rating, content_width):
|
||||
"""Places components for rating preview"""
|
||||
try:
|
||||
icon_star_full = Image.open(
|
||||
os.path.join(settings.STATIC_ROOT, "images/icons/star-full.png")
|
||||
)
|
||||
icon_star_empty = Image.open(
|
||||
os.path.join(settings.STATIC_ROOT, "images/icons/star-empty.png")
|
||||
)
|
||||
icon_star_half = Image.open(
|
||||
os.path.join(settings.STATIC_ROOT, "images/icons/star-half.png")
|
||||
)
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
icon_size = 64
|
||||
icon_margin = 10
|
||||
|
||||
rating_layer_base = Image.new(
|
||||
"RGBA", (content_width, icon_size), color=TRANSPARENT_COLOR
|
||||
)
|
||||
rating_layer_color = Image.new("RGBA", (content_width, icon_size), color=TEXT_COLOR)
|
||||
rating_layer_mask = Image.new(
|
||||
"RGBA", (content_width, icon_size), color=TRANSPARENT_COLOR
|
||||
)
|
||||
|
||||
position_x = 0
|
||||
|
||||
for _ in range(math.floor(rating)):
|
||||
rating_layer_mask.alpha_composite(icon_star_full, (position_x, 0))
|
||||
position_x = position_x + icon_size + icon_margin
|
||||
|
||||
if math.floor(rating) != math.ceil(rating):
|
||||
rating_layer_mask.alpha_composite(icon_star_half, (position_x, 0))
|
||||
position_x = position_x + icon_size + icon_margin
|
||||
|
||||
for _ in range(5 - math.ceil(rating)):
|
||||
rating_layer_mask.alpha_composite(icon_star_empty, (position_x, 0))
|
||||
position_x = position_x + icon_size + icon_margin
|
||||
|
||||
rating_layer_mask = rating_layer_mask.getchannel("A")
|
||||
rating_layer_mask = ImageOps.invert(rating_layer_mask)
|
||||
|
||||
rating_layer_composite = Image.composite(
|
||||
rating_layer_base, rating_layer_color, rating_layer_mask
|
||||
)
|
||||
|
||||
return rating_layer_composite
|
||||
|
||||
|
||||
def generate_default_inner_img():
|
||||
"""Adds cover image"""
|
||||
font_cover = get_font("light", size=28)
|
||||
|
||||
default_cover = Image.new(
|
||||
"RGB", (inner_img_width, inner_img_height), color=DEFAULT_COVER_COLOR
|
||||
)
|
||||
default_cover_draw = ImageDraw.Draw(default_cover)
|
||||
|
||||
text = "no image :("
|
||||
text_dimensions = font_cover.getsize(text)
|
||||
text_coords = (
|
||||
math.floor((inner_img_width - text_dimensions[0]) / 2),
|
||||
math.floor((inner_img_height - text_dimensions[1]) / 2),
|
||||
)
|
||||
default_cover_draw.text(text_coords, text, font=font_cover, fill="white")
|
||||
|
||||
return default_cover
|
||||
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
def generate_preview_image(
|
||||
texts=None, picture=None, rating=None, show_instance_layer=True
|
||||
):
|
||||
"""Puts everything together"""
|
||||
texts = texts or {}
|
||||
# Cover
|
||||
try:
|
||||
inner_img_layer = Image.open(picture)
|
||||
inner_img_layer.thumbnail((inner_img_width, inner_img_height), Image.ANTIALIAS)
|
||||
color_thief = ColorThief(picture)
|
||||
dominant_color = color_thief.get_color(quality=1)
|
||||
except: # pylint: disable=bare-except
|
||||
inner_img_layer = generate_default_inner_img()
|
||||
dominant_color = ImageColor.getrgb(DEFAULT_COVER_COLOR)
|
||||
|
||||
# Color
|
||||
if BG_COLOR in ["use_dominant_color_light", "use_dominant_color_dark"]:
|
||||
image_bg_color = "rgb(%s, %s, %s)" % dominant_color
|
||||
|
||||
# Adjust color
|
||||
image_bg_color_rgb = [x / 255.0 for x in ImageColor.getrgb(image_bg_color)]
|
||||
image_bg_color_hls = colorsys.rgb_to_hls(*image_bg_color_rgb)
|
||||
|
||||
if BG_COLOR == "use_dominant_color_light":
|
||||
lightness = max(0.9, image_bg_color_hls[1])
|
||||
else:
|
||||
lightness = min(0.15, image_bg_color_hls[1])
|
||||
|
||||
image_bg_color_hls = (
|
||||
image_bg_color_hls[0],
|
||||
lightness,
|
||||
image_bg_color_hls[2],
|
||||
)
|
||||
image_bg_color = tuple(
|
||||
math.ceil(x * 255) for x in colorsys.hls_to_rgb(*image_bg_color_hls)
|
||||
)
|
||||
else:
|
||||
image_bg_color = BG_COLOR
|
||||
|
||||
# Background (using the color)
|
||||
img = Image.new("RGBA", (IMG_WIDTH, IMG_HEIGHT), color=image_bg_color)
|
||||
|
||||
# Contents
|
||||
inner_img_x = margin + inner_img_width - inner_img_layer.width
|
||||
inner_img_y = math.floor((IMG_HEIGHT - inner_img_layer.height) / 2)
|
||||
content_x = margin + inner_img_width + gutter
|
||||
content_width = IMG_WIDTH - content_x - margin
|
||||
|
||||
contents_layer = Image.new(
|
||||
"RGBA", (content_width, IMG_HEIGHT), color=TRANSPARENT_COLOR
|
||||
)
|
||||
contents_composite_y = 0
|
||||
|
||||
if show_instance_layer:
|
||||
instance_layer = generate_instance_layer(content_width)
|
||||
contents_layer.alpha_composite(instance_layer, (0, contents_composite_y))
|
||||
contents_composite_y = contents_composite_y + instance_layer.height + gutter
|
||||
|
||||
texts_layer = generate_texts_layer(texts, content_width)
|
||||
contents_layer.alpha_composite(texts_layer, (0, contents_composite_y))
|
||||
contents_composite_y = contents_composite_y + texts_layer.height + gutter
|
||||
|
||||
if rating:
|
||||
# Add some more margin
|
||||
contents_composite_y = contents_composite_y + gutter
|
||||
rating_layer = generate_rating_layer(rating, content_width)
|
||||
|
||||
if rating_layer:
|
||||
contents_layer.alpha_composite(rating_layer, (0, contents_composite_y))
|
||||
contents_composite_y = contents_composite_y + rating_layer.height + gutter
|
||||
|
||||
contents_layer_box = contents_layer.getbbox()
|
||||
contents_layer_height = contents_layer_box[3] - contents_layer_box[1]
|
||||
|
||||
contents_y = math.floor((IMG_HEIGHT - contents_layer_height) / 2)
|
||||
|
||||
if show_instance_layer:
|
||||
# Remove Instance Layer from centering calculations
|
||||
contents_y = contents_y - math.floor((instance_layer.height + gutter) / 2)
|
||||
|
||||
contents_y = max(contents_y, margin)
|
||||
|
||||
# Composite layers
|
||||
img.paste(
|
||||
inner_img_layer, (inner_img_x, inner_img_y), inner_img_layer.convert("RGBA")
|
||||
)
|
||||
img.alpha_composite(contents_layer, (content_x, contents_y))
|
||||
|
||||
return img.convert("RGB")
|
||||
|
||||
|
||||
def save_and_cleanup(image, instance=None):
|
||||
"""Save and close the file"""
|
||||
if not isinstance(instance, (models.Book, models.User, models.SiteSettings)):
|
||||
return False
|
||||
file_name = "%s-%s.jpg" % (str(instance.id), str(uuid4()))
|
||||
image_buffer = BytesIO()
|
||||
|
||||
try:
|
||||
try:
|
||||
old_path = instance.preview_image.path
|
||||
except ValueError:
|
||||
old_path = ""
|
||||
|
||||
# Save
|
||||
image.save(image_buffer, format="jpeg", quality=75)
|
||||
|
||||
instance.preview_image = InMemoryUploadedFile(
|
||||
ContentFile(image_buffer.getvalue()),
|
||||
"preview_image",
|
||||
file_name,
|
||||
"image/jpg",
|
||||
image_buffer.tell(),
|
||||
None,
|
||||
)
|
||||
|
||||
save_without_broadcast = isinstance(instance, (models.Book, models.User))
|
||||
if save_without_broadcast:
|
||||
instance.save(broadcast=False)
|
||||
else:
|
||||
instance.save()
|
||||
|
||||
# Clean up old file after saving
|
||||
if os.path.exists(old_path):
|
||||
os.remove(old_path)
|
||||
|
||||
finally:
|
||||
image_buffer.close()
|
||||
return True
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@app.task
|
||||
def generate_site_preview_image_task():
|
||||
"""generate preview_image for the website"""
|
||||
if not settings.ENABLE_PREVIEW_IMAGES:
|
||||
return
|
||||
|
||||
site = models.SiteSettings.objects.get()
|
||||
|
||||
if site.logo:
|
||||
logo = site.logo
|
||||
else:
|
||||
logo = os.path.join(settings.STATIC_ROOT, "images/logo.png")
|
||||
|
||||
texts = {
|
||||
"text_zero": settings.DOMAIN,
|
||||
"text_one": site.name,
|
||||
"text_three": site.instance_tagline,
|
||||
}
|
||||
|
||||
image = generate_preview_image(texts=texts, picture=logo, show_instance_layer=False)
|
||||
|
||||
save_and_cleanup(image, instance=site)
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@app.task
|
||||
def generate_edition_preview_image_task(book_id):
|
||||
"""generate preview_image for a book"""
|
||||
if not settings.ENABLE_PREVIEW_IMAGES:
|
||||
return
|
||||
|
||||
book = models.Book.objects.select_subclasses().get(id=book_id)
|
||||
|
||||
rating = models.Review.objects.filter(
|
||||
privacy="public",
|
||||
deleted=False,
|
||||
book__in=[book_id],
|
||||
).aggregate(Avg("rating"))["rating__avg"]
|
||||
|
||||
texts = {
|
||||
"text_one": book.title,
|
||||
"text_two": book.subtitle,
|
||||
"text_three": book.author_text,
|
||||
}
|
||||
|
||||
image = generate_preview_image(texts=texts, picture=book.cover, rating=rating)
|
||||
|
||||
save_and_cleanup(image, instance=book)
|
||||
|
||||
|
||||
@app.task
|
||||
def generate_user_preview_image_task(user_id):
|
||||
"""generate preview_image for a book"""
|
||||
if not settings.ENABLE_PREVIEW_IMAGES:
|
||||
return
|
||||
|
||||
user = models.User.objects.get(id=user_id)
|
||||
|
||||
texts = {
|
||||
"text_one": user.display_name,
|
||||
"text_three": "@{}@{}".format(user.localname, settings.DOMAIN),
|
||||
}
|
||||
|
||||
if user.avatar:
|
||||
avatar = user.avatar
|
||||
else:
|
||||
avatar = os.path.join(settings.STATIC_ROOT, "images/default_avi.jpg")
|
||||
|
||||
image = generate_preview_image(texts=texts, picture=avatar)
|
||||
|
||||
save_and_cleanup(image, instance=user)
|
|
@ -42,6 +42,14 @@ LOCALE_PATHS = [
|
|||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||
|
||||
# Preview image
|
||||
ENABLE_PREVIEW_IMAGES = env.bool("ENABLE_PREVIEW_IMAGES", False)
|
||||
PREVIEW_BG_COLOR = env.str("PREVIEW_BG_COLOR", "use_dominant_color_light")
|
||||
PREVIEW_TEXT_COLOR = env.str("PREVIEW_TEXT_COLOR", "#363636")
|
||||
PREVIEW_IMG_WIDTH = env.int("PREVIEW_IMG_WIDTH", 1200)
|
||||
PREVIEW_IMG_HEIGHT = env.int("PREVIEW_IMG_HEIGHT", 630)
|
||||
PREVIEW_DEFAULT_COVER_COLOR = env.str("PREVIEW_DEFAULT_COVER_COLOR", "#002549")
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||
|
||||
|
@ -175,8 +183,10 @@ USE_TZ = True
|
|||
|
||||
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/)" % (
|
||||
|
|
|
@ -73,6 +73,7 @@ class Signature:
|
|||
self.headers = headers
|
||||
self.signature = signature
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
@classmethod
|
||||
def parse(cls, request):
|
||||
"""extract and parse a signature from an http request"""
|
||||
|
|
94
bookwyrm/static/fonts/public_sans/OFL.txt
Normal file
94
bookwyrm/static/fonts/public_sans/OFL.txt
Normal file
|
@ -0,0 +1,94 @@
|
|||
Copyright (c) 2015, Pablo Impallari, Rodrigo Fuenzalida
|
||||
(Modified by Dan O. Williams and USWDS) (https://github.com/uswds/public-sans)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
bookwyrm/static/fonts/public_sans/PublicSans-Bold.ttf
Normal file
BIN
bookwyrm/static/fonts/public_sans/PublicSans-Bold.ttf
Normal file
Binary file not shown.
BIN
bookwyrm/static/fonts/public_sans/PublicSans-Light.ttf
Normal file
BIN
bookwyrm/static/fonts/public_sans/PublicSans-Light.ttf
Normal file
Binary file not shown.
BIN
bookwyrm/static/fonts/public_sans/PublicSans-Regular.ttf
Normal file
BIN
bookwyrm/static/fonts/public_sans/PublicSans-Regular.ttf
Normal file
Binary file not shown.
BIN
bookwyrm/static/images/icons/star-empty.png
Executable file
BIN
bookwyrm/static/images/icons/star-empty.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
bookwyrm/static/images/icons/star-full.png
Executable file
BIN
bookwyrm/static/images/icons/star-full.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 923 B |
BIN
bookwyrm/static/images/icons/star-half.png
Executable file
BIN
bookwyrm/static/images/icons/star-half.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -1,8 +1,12 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% load i18n %}{% load bookwyrm_tags %}{% load humanize %}{% load utilities %}
|
||||
{% load i18n %}{% load bookwyrm_tags %}{% load humanize %}{% load utilities %}{% load layout %}
|
||||
|
||||
{% block title %}{{ book|book_title }}{% endblock %}
|
||||
|
||||
{% block opengraph_images %}
|
||||
{% include 'snippets/opengraph_images.html' with image=book.preview_image %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% with user_authenticated=request.user.is_authenticated can_edit_book=perms.bookwyrm.edit_book %}
|
||||
<div class="block" itemscope itemtype="https://schema.org/Book">
|
||||
|
|
|
@ -8,16 +8,21 @@
|
|||
<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 %}/images/{{ site.favicon }}{% else %}/static/images/favicon.ico{% endif %}">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{% if site.favicon %}{{ media_url }}{{ site.favicon }}{% else %}/static/images/favicon.ico{% endif %}">
|
||||
|
||||
{% if preview_images_enabled is True %}
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
{% else %}
|
||||
<meta name="twitter:card" content="summary">
|
||||
{% endif %}
|
||||
<meta name="twitter:title" content="{% if title %}{{ title }} | {% endif %}{{ site.name }}">
|
||||
<meta name="og:title" content="{% if title %}{{ title }} | {% endif %}{{ site.name }}">
|
||||
<meta name="twitter:description" content="{{ site.instance_tagline }}">
|
||||
<meta name="og:description" content="{{ site.instance_tagline }}">
|
||||
|
||||
<meta name="twitter:image" content="{% if site.logo %}/images/{{ site.logo }}{% else %}/static/images/logo.png{% endif %}">
|
||||
<meta name="og:image" content="{% if site.logo %}/images/{{ site.logo }}{% else %}/static/images/logo.png{% endif %}">
|
||||
{% block opengraph_images %}
|
||||
{% include 'snippets/opengraph_images.html' %}
|
||||
{% endblock %}
|
||||
<meta name="twitter:image:alt" content="BookWyrm Logo">
|
||||
</head>
|
||||
<body>
|
||||
|
@ -25,7 +30,7 @@
|
|||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<img class="image logo" src="{% if site.logo_small %}/images/{{ site.logo_small }}{% else %}/static/images/logo-small.png{% endif %}" alt="Home page">
|
||||
<img class="image logo" src="{% if site.logo_small %}{{ media_url }}{{ 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">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="columns">
|
||||
<div class="column is-narrow is-hidden-mobile">
|
||||
<figure class="block">
|
||||
<figure class="block is-w-xl">
|
||||
<img src="{% if site.logo %}/images/{{ site.logo }}{% else %}/static/images/logo.png{% endif %}" alt="BookWyrm logo">
|
||||
</figure>
|
||||
</div>
|
||||
|
|
12
bookwyrm/templates/snippets/opengraph_images.html
Normal file
12
bookwyrm/templates/snippets/opengraph_images.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% 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 }}">
|
||||
{% 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 }}">
|
||||
{% 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 %}">
|
||||
{% endif %}
|
|
@ -3,9 +3,14 @@
|
|||
{% load humanize %}
|
||||
{% load utilities %}
|
||||
{% load markdown %}
|
||||
{% load layout %}
|
||||
|
||||
{% block title %}{{ user.display_name }}{% endblock %}
|
||||
|
||||
{% block opengraph_images %}
|
||||
{% include 'snippets/opengraph_images.html' with image=user.preview_image %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header class="block">
|
||||
{% block header %}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
""" template filters used for creating the layout"""
|
||||
from django import template, utils
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import datetime
|
||||
|
||||
from unittest.mock import patch
|
||||
from django.test import TestCase
|
||||
from bookwyrm import models
|
||||
|
||||
|
||||
class Author(TestCase):
|
||||
def setUp(self):
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
|
|
|
@ -25,6 +25,7 @@ class BaseActivity(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""we're probably going to re-use this so why copy/paste"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
|
@ -97,6 +98,7 @@ class BaseActivity(TestCase):
|
|||
status=200,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
result = resolve_remote_id(
|
||||
"https://example.com/user/mouse", model=models.User
|
||||
|
@ -139,6 +141,7 @@ class BaseActivity(TestCase):
|
|||
self.user.avatar.file # pylint: disable=pointless-statement
|
||||
|
||||
# this would trigger a broadcast because it's a local user
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
activity.to_model(model=models.User, instance=self.user)
|
||||
self.assertIsNotNone(self.user.avatar.file)
|
||||
|
@ -152,6 +155,7 @@ class BaseActivity(TestCase):
|
|||
content="test status",
|
||||
user=self.user,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
book = models.Edition.objects.create(
|
||||
title="Test Edition", remote_id="http://book.com/book"
|
||||
)
|
||||
|
|
|
@ -20,6 +20,7 @@ class Person(TestCase):
|
|||
|
||||
def test_user_to_model(self):
|
||||
activity = activitypub.Person(**self.user_data)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
user = activity.to_model(model=models.User)
|
||||
self.assertEqual(user.username, "mouse@example.com")
|
||||
|
|
|
@ -12,6 +12,7 @@ class Quotation(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""model objects we'll need"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
self.user = models.User.objects.create_user(
|
||||
"mouse",
|
||||
|
@ -22,6 +23,7 @@ class Quotation(TestCase):
|
|||
outbox="https://example.com/user/mouse/outbox",
|
||||
remote_id="https://example.com/user/mouse",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
|
|
|
@ -74,6 +74,7 @@ class AbstractConnector(TestCase):
|
|||
Mapping("openlibraryKey"),
|
||||
]
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Test Book",
|
||||
remote_id="https://example.com/book/1234",
|
||||
|
@ -110,8 +111,11 @@ class AbstractConnector(TestCase):
|
|||
responses.add(
|
||||
responses.GET, "https://example.com/book/abcd", json=self.edition_data
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.connectors.abstract_connector.load_more_data.delay"):
|
||||
result = self.connector.get_or_create_book("https://example.com/book/abcd")
|
||||
result = self.connector.get_or_create_book(
|
||||
"https://example.com/book/abcd"
|
||||
)
|
||||
self.assertEqual(result, self.book)
|
||||
self.assertEqual(models.Edition.objects.count(), 1)
|
||||
self.assertEqual(models.Edition.objects.count(), 1)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" testing book data connectors """
|
||||
from unittest.mock import patch
|
||||
import json
|
||||
import pathlib
|
||||
from django.test import TestCase
|
||||
|
@ -25,6 +26,7 @@ class BookWyrmConnector(TestCase):
|
|||
|
||||
def test_get_or_create_book_existing(self):
|
||||
"""load book activity"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
work = models.Work.objects.create(title="Test Work")
|
||||
book = models.Edition.objects.create(title="Test Edition", parent_work=work)
|
||||
result = self.connector.get_or_create_book(book.remote_id)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" interface between the app and various connectors """
|
||||
from unittest.mock import patch
|
||||
from django.test import TestCase
|
||||
import responses
|
||||
|
||||
|
@ -13,6 +14,7 @@ class ConnectorManager(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""we'll need some books and a connector info entry"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.work = models.Work.objects.create(title="Example Work")
|
||||
|
||||
self.edition = models.Edition.objects.create(
|
||||
|
|
|
@ -178,14 +178,20 @@ class Openlibrary(TestCase):
|
|||
@responses.activate
|
||||
def test_expand_book_data(self):
|
||||
"""given a book, get more editions"""
|
||||
work = models.Work.objects.create(title="Test Work", openlibrary_key="OL1234W")
|
||||
edition = models.Edition.objects.create(title="Test Edition", parent_work=work)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
work = models.Work.objects.create(
|
||||
title="Test Work", openlibrary_key="OL1234W"
|
||||
)
|
||||
edition = models.Edition.objects.create(
|
||||
title="Test Edition", parent_work=work
|
||||
)
|
||||
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"https://openlibrary.org/works/OL1234W/editions",
|
||||
json={"entries": []},
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.connectors.abstract_connector.AbstractConnector."
|
||||
"create_edition_from_data"
|
||||
|
@ -224,11 +230,14 @@ class Openlibrary(TestCase):
|
|||
json={"hi": "there"},
|
||||
status=200,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.connectors.openlibrary.Connector." "get_authors_from_data"
|
||||
) as mock:
|
||||
mock.return_value = []
|
||||
result = self.connector.create_edition_from_data(work, self.edition_data)
|
||||
result = self.connector.create_edition_from_data(
|
||||
work, self.edition_data
|
||||
)
|
||||
self.assertEqual(result.parent_work, work)
|
||||
self.assertEqual(result.title, "Sabriel")
|
||||
self.assertEqual(result.isbn_10, "0060273224")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" testing book data connectors """
|
||||
from unittest.mock import patch
|
||||
import datetime
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
@ -29,6 +30,7 @@ class SelfConnector(TestCase):
|
|||
def test_format_search_result(self):
|
||||
"""create a SearchResult"""
|
||||
author = models.Author.objects.create(name="Anonymous")
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
edition = models.Edition.objects.create(
|
||||
title="Edition of Example Work",
|
||||
published_date=datetime.datetime(1980, 5, 10, tzinfo=timezone.utc),
|
||||
|
@ -41,7 +43,8 @@ class SelfConnector(TestCase):
|
|||
self.assertEqual(result.year, 1980)
|
||||
self.assertEqual(result.connector, self.connector)
|
||||
|
||||
def test_search_rank(self):
|
||||
@patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay")
|
||||
def test_search_rank(self, _):
|
||||
"""prioritize certain results"""
|
||||
author = models.Author.objects.create(name="Anonymous")
|
||||
edition = models.Edition.objects.create(
|
||||
|
@ -49,7 +52,7 @@ class SelfConnector(TestCase):
|
|||
published_date=datetime.datetime(1980, 5, 10, tzinfo=timezone.utc),
|
||||
parent_work=models.Work.objects.create(title=""),
|
||||
)
|
||||
# author text is rank C
|
||||
# author text is rank B
|
||||
edition.authors.add(author)
|
||||
|
||||
# series is rank D
|
||||
|
@ -67,17 +70,19 @@ class SelfConnector(TestCase):
|
|||
# title is rank A
|
||||
models.Edition.objects.create(title="Anonymous")
|
||||
# doesn't rank in this search
|
||||
edition = models.Edition.objects.create(
|
||||
models.Edition.objects.create(
|
||||
title="An Edition", parent_work=models.Work.objects.create(title="")
|
||||
)
|
||||
|
||||
results = self.connector.search("Anonymous")
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertEqual(len(results), 4)
|
||||
self.assertEqual(results[0].title, "Anonymous")
|
||||
self.assertEqual(results[1].title, "More Editions")
|
||||
self.assertEqual(results[2].title, "Edition of Example Work")
|
||||
self.assertEqual(results[3].title, "Another Edition")
|
||||
|
||||
def test_search_multiple_editions(self):
|
||||
@patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay")
|
||||
def test_search_multiple_editions(self, _):
|
||||
"""it should get rid of duplicate editions for the same work"""
|
||||
work = models.Work.objects.create(title="Work Title")
|
||||
edition_1 = models.Edition.objects.create(
|
||||
|
@ -86,7 +91,7 @@ class SelfConnector(TestCase):
|
|||
edition_2 = models.Edition.objects.create(
|
||||
title="Edition 2 Title",
|
||||
parent_work=work,
|
||||
edition_rank=20, # that's default babey
|
||||
isbn_13="123456789", # this is now the defualt edition
|
||||
)
|
||||
edition_3 = models.Edition.objects.create(title="Fish", parent_work=work)
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ class GoodreadsImport(TestCase):
|
|||
self.importer = GoodreadsImporter()
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv")
|
||||
self.csv = open(datafile, "r", encoding=self.importer.encoding)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "password", local=True
|
||||
)
|
||||
|
@ -36,6 +37,7 @@ class GoodreadsImport(TestCase):
|
|||
search_url="https://%s/search?q=" % DOMAIN,
|
||||
priority=1,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
work = models.Work.objects.create(title="Test Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
|
@ -92,6 +94,7 @@ class GoodreadsImport(TestCase):
|
|||
def test_import_data(self):
|
||||
"""resolve entry"""
|
||||
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
book = models.Edition.objects.create(title="Test Book")
|
||||
|
||||
with patch(
|
||||
|
@ -119,6 +122,7 @@ class GoodreadsImport(TestCase):
|
|||
)
|
||||
break
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "public"
|
||||
|
@ -183,6 +187,7 @@ class GoodreadsImport(TestCase):
|
|||
)
|
||||
break
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "public"
|
||||
|
@ -216,6 +221,7 @@ class GoodreadsImport(TestCase):
|
|||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, True, "unlisted"
|
||||
|
@ -242,6 +248,7 @@ class GoodreadsImport(TestCase):
|
|||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, True, "unlisted"
|
||||
|
@ -265,6 +272,7 @@ class GoodreadsImport(TestCase):
|
|||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "unlisted"
|
||||
|
|
|
@ -22,6 +22,7 @@ class LibrarythingImport(TestCase):
|
|||
|
||||
# Librarything generates latin encoded exports...
|
||||
self.csv = open(datafile, "r", encoding=self.importer.encoding)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.user = models.User.objects.create_user(
|
||||
"mmai", "mmai@mmai.mmai", "password", local=True
|
||||
)
|
||||
|
@ -37,6 +38,7 @@ class LibrarythingImport(TestCase):
|
|||
search_url="https://%s/search?q=" % DOMAIN,
|
||||
priority=1,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
work = models.Work.objects.create(title="Test Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
|
@ -82,6 +84,7 @@ class LibrarythingImport(TestCase):
|
|||
def test_import_data(self):
|
||||
"""resolve entry"""
|
||||
import_job = self.importer.create_job(self.user, self.csv, False, "unlisted")
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
book = models.Edition.objects.create(title="Test Book")
|
||||
|
||||
with patch(
|
||||
|
@ -111,6 +114,7 @@ class LibrarythingImport(TestCase):
|
|||
)
|
||||
break
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "public"
|
||||
|
@ -147,6 +151,7 @@ class LibrarythingImport(TestCase):
|
|||
)
|
||||
break
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "public"
|
||||
|
@ -179,6 +184,7 @@ class LibrarythingImport(TestCase):
|
|||
)
|
||||
break
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "public"
|
||||
|
@ -212,6 +218,7 @@ class LibrarythingImport(TestCase):
|
|||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, True, "unlisted"
|
||||
|
@ -235,6 +242,7 @@ class LibrarythingImport(TestCase):
|
|||
job_id=import_job.id, index=0, data=entry, book=self.book
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
handle_imported_book(
|
||||
self.importer.service, self.user, import_item, False, "unlisted"
|
||||
|
|
|
@ -12,11 +12,16 @@ class Activitystreams(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""we need some stuff"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse"
|
||||
)
|
||||
self.another_user = models.User.objects.create_user(
|
||||
"nutria", "nutria@nutria.nutria", "password", local=True, localname="nutria"
|
||||
"nutria",
|
||||
"nutria@nutria.nutria",
|
||||
"password",
|
||||
local=True,
|
||||
localname="nutria",
|
||||
)
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
self.remote_user = models.User.objects.create_user(
|
||||
|
@ -28,6 +33,7 @@ class Activitystreams(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(title="test book")
|
||||
|
||||
def test_populate_streams(self, _):
|
||||
|
|
|
@ -19,6 +19,7 @@ class ActivitypubMixins(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""shared data"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
|
@ -70,6 +71,7 @@ class ActivitypubMixins(TestCase):
|
|||
"""attempt to match a remote id to an object in the db"""
|
||||
# uses a different remote id scheme
|
||||
# this isn't really part of this test directly but it's helpful to state
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
book = models.Edition.objects.create(
|
||||
title="Test Edition", remote_id="http://book.com/book"
|
||||
)
|
||||
|
@ -101,6 +103,7 @@ class ActivitypubMixins(TestCase):
|
|||
|
||||
def test_find_existing(self, _):
|
||||
"""match a blob of data to a model"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
book = models.Edition.objects.create(
|
||||
title="Test edition",
|
||||
openlibrary_key="OL1234",
|
||||
|
@ -140,6 +143,7 @@ class ActivitypubMixins(TestCase):
|
|||
MockSelf = namedtuple("Self", ("privacy", "user"))
|
||||
mock_self = MockSelf("public", self.local_user)
|
||||
self.local_user.followers.add(self.remote_user)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
another_remote_user = models.User.objects.create_user(
|
||||
"nutria",
|
||||
|
@ -163,6 +167,7 @@ class ActivitypubMixins(TestCase):
|
|||
MockSelf = namedtuple("Self", ("privacy", "user"))
|
||||
mock_self = MockSelf("public", self.local_user)
|
||||
self.local_user.followers.add(self.remote_user)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
another_remote_user = models.User.objects.create_user(
|
||||
"nutria",
|
||||
|
@ -184,6 +189,7 @@ class ActivitypubMixins(TestCase):
|
|||
"""should combine users with the same shared_inbox"""
|
||||
self.remote_user.shared_inbox = "http://example.com/inbox"
|
||||
self.remote_user.save(broadcast=False)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
another_remote_user = models.User.objects.create_user(
|
||||
"nutria",
|
||||
|
@ -206,6 +212,7 @@ class ActivitypubMixins(TestCase):
|
|||
|
||||
def test_get_recipients_software(self, _):
|
||||
"""should differentiate between bookwyrm and other remote users"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
another_remote_user = models.User.objects.create_user(
|
||||
"nutria",
|
||||
|
|
|
@ -12,6 +12,7 @@ class BaseModel(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""shared data"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
""" testing models """
|
||||
from unittest.mock import patch
|
||||
|
||||
from dateutil.parser import parse
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
@ -12,6 +14,7 @@ class Book(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""we'll need some books"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.work = models.Work.objects.create(
|
||||
title="Example Work", remote_id="https://example.com/book/1"
|
||||
)
|
||||
|
@ -56,6 +59,7 @@ class Book(TestCase):
|
|||
|
||||
def test_get_edition_info(self):
|
||||
"""text slug about an edition"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
book = models.Edition.objects.create(title="Test Edition")
|
||||
self.assertEqual(book.edition_info, "")
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ class FederatedServer(TestCase):
|
|||
def setUp(self):
|
||||
"""we'll need a user"""
|
||||
self.server = models.FederatedServer.objects.create(server_name="test.server")
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
self.remote_user = models.User.objects.create_user(
|
||||
"rat",
|
||||
|
|
|
@ -188,6 +188,7 @@ class ActivitypubFields(TestCase):
|
|||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
def test_privacy_field_set_activity_from_field(self, *_):
|
||||
"""translate between to/cc fields and privacy"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
user = User.objects.create_user(
|
||||
"rat", "rat@rat.rat", "ratword", local=True, localname="rat"
|
||||
)
|
||||
|
@ -248,13 +249,17 @@ class ActivitypubFields(TestCase):
|
|||
del userdata["icon"]
|
||||
|
||||
# it shouldn't match with this unrelated user:
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
unrelated_user = User.objects.create_user(
|
||||
"rat", "rat@rat.rat", "ratword", local=True, localname="rat"
|
||||
)
|
||||
|
||||
# test receiving an unknown remote id and loading data
|
||||
responses.add(
|
||||
responses.GET, "https://example.com/user/mouse", json=userdata, status=200
|
||||
responses.GET,
|
||||
"https://example.com/user/mouse",
|
||||
json=userdata,
|
||||
status=200,
|
||||
)
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
value = instance.field_from_activity("https://example.com/user/mouse")
|
||||
|
@ -272,6 +277,7 @@ class ActivitypubFields(TestCase):
|
|||
del userdata["icon"]
|
||||
|
||||
# it shouldn't match with this unrelated user:
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
unrelated_user = User.objects.create_user(
|
||||
"rat", "rat@rat.rat", "ratword", local=True, localname="rat"
|
||||
)
|
||||
|
@ -288,11 +294,13 @@ class ActivitypubFields(TestCase):
|
|||
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
|
||||
userdata = json.loads(datafile.read_bytes())
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
user = User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
user.remote_id = "https://example.com/user/mouse"
|
||||
user.save(broadcast=False)
|
||||
|
||||
User.objects.create_user(
|
||||
"rat", "rat@rat.rat", "ratword", local=True, localname="rat"
|
||||
)
|
||||
|
@ -304,6 +312,7 @@ class ActivitypubFields(TestCase):
|
|||
def test_foreign_key_from_activity_str_existing(self):
|
||||
"""test receiving a remote id of an existing object in the db"""
|
||||
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
user = User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
|
@ -351,6 +360,7 @@ class ActivitypubFields(TestCase):
|
|||
responses.add(
|
||||
responses.GET, "https://example.com/user/mouse", json=userdata, status=200
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
value = instance.field_from_activity(
|
||||
["https://example.com/user/mouse", "bleh"]
|
||||
|
@ -386,6 +396,7 @@ class ActivitypubFields(TestCase):
|
|||
@patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast")
|
||||
def test_image_field(self, _):
|
||||
"""storing images"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
user = User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
|
|
|
@ -59,6 +59,7 @@ class ImportJob(TestCase):
|
|||
unknown_read_data["Exclusive Shelf"] = "read"
|
||||
unknown_read_data["Date Read"] = ""
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
|
@ -173,6 +174,9 @@ class ImportJob(TestCase):
|
|||
search.return_value = result
|
||||
with patch(
|
||||
"bookwyrm.connectors.openlibrary.Connector." "get_authors_from_data"
|
||||
):
|
||||
with patch(
|
||||
"bookwyrm.preview_images.generate_edition_preview_image_task.delay"
|
||||
):
|
||||
book = self.item_1.get_book_from_isbn()
|
||||
|
||||
|
|
|
@ -11,9 +11,11 @@ class List(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""look, a list"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
work = models.Work.objects.create(title="hello")
|
||||
self.book = models.Edition.objects.create(title="hi", parent_work=work)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
""" testing models """
|
||||
from django.test import TestCase
|
||||
from django.core.exceptions import ValidationError
|
||||
from unittest.mock import patch
|
||||
|
||||
from bookwyrm import models
|
||||
|
||||
|
@ -10,12 +11,13 @@ class ReadThrough(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""look, a shelf"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.work = models.Work.objects.create(title="Example Work")
|
||||
|
||||
self.edition = models.Edition.objects.create(
|
||||
title="Example Edition", parent_work=self.work
|
||||
)
|
||||
|
|
|
@ -10,6 +10,7 @@ class Relationship(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""we need some users for this"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
self.remote_user = models.User.objects.create_user(
|
||||
"rat",
|
||||
|
|
|
@ -12,11 +12,15 @@ class Shelf(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""look, a shelf"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
work = models.Work.objects.create(title="Test Work")
|
||||
self.book = models.Edition.objects.create(title="test book", parent_work=work)
|
||||
self.book = models.Edition.objects.create(
|
||||
title="test book", parent_work=work
|
||||
)
|
||||
|
||||
def test_remote_id(self):
|
||||
"""shelves use custom remote ids"""
|
||||
|
|
|
@ -22,6 +22,7 @@ class Status(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""useful things for creating a status"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse"
|
||||
)
|
||||
|
@ -35,6 +36,7 @@ class Status(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(title="Test Edition")
|
||||
|
||||
image_file = pathlib.Path(__file__).parent.joinpath(
|
||||
|
@ -59,6 +61,7 @@ class Status(TestCase):
|
|||
child = models.Status.objects.create(
|
||||
content="hello", reply_parent=parent, user=self.local_user
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
models.Review.objects.create(
|
||||
content="hey", reply_parent=parent, user=self.local_user, book=self.book
|
||||
)
|
||||
|
@ -93,6 +96,7 @@ class Status(TestCase):
|
|||
child = models.Status.objects.create(
|
||||
content="hello", reply_parent=parent, user=self.local_user
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
models.Review.objects.create(
|
||||
content="hey", reply_parent=parent, user=self.local_user, book=self.book
|
||||
)
|
||||
|
@ -252,6 +256,7 @@ class Status(TestCase):
|
|||
|
||||
def test_review_to_activity(self, *_):
|
||||
"""subclass of the base model version with a "pure" serializer"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
status = models.Review.objects.create(
|
||||
name="Review name",
|
||||
content="test content",
|
||||
|
@ -269,6 +274,7 @@ class Status(TestCase):
|
|||
|
||||
def test_review_to_pure_activity(self, *_):
|
||||
"""subclass of the base model version with a "pure" serializer"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
status = models.Review.objects.create(
|
||||
name="Review's name",
|
||||
content="test content",
|
||||
|
@ -293,6 +299,7 @@ class Status(TestCase):
|
|||
|
||||
def test_review_to_pure_activity_no_rating(self, *_):
|
||||
"""subclass of the base model version with a "pure" serializer"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
status = models.Review.objects.create(
|
||||
name="Review name",
|
||||
content="test content",
|
||||
|
@ -315,6 +322,7 @@ class Status(TestCase):
|
|||
|
||||
def test_reviewrating_to_pure_activity(self, *_):
|
||||
"""subclass of the base model version with a "pure" serializer"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
status = models.ReviewRating.objects.create(
|
||||
rating=3.0,
|
||||
user=self.local_user,
|
||||
|
@ -349,6 +357,7 @@ class Status(TestCase):
|
|||
status = models.Status.objects.create(
|
||||
content="test content", user=self.local_user
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
fav = models.Favorite.objects.create(status=status, user=self.local_user)
|
||||
|
||||
# can't fav a status twice
|
||||
|
|
|
@ -11,6 +11,7 @@ from bookwyrm.settings import DOMAIN
|
|||
# pylint: disable=missing-function-docstring
|
||||
class User(TestCase):
|
||||
def setUp(self):
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.user = models.User.objects.create_user(
|
||||
"mouse@%s" % DOMAIN,
|
||||
"mouse@mouse.mouse",
|
||||
|
@ -34,6 +35,7 @@ class User(TestCase):
|
|||
self.assertIsNotNone(self.user.key_pair.public_key)
|
||||
|
||||
def test_remote_user(self):
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
user = models.User.objects.create_user(
|
||||
"rat",
|
||||
|
|
|
@ -11,11 +11,16 @@ class Activitystreams(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""use a test csv"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse"
|
||||
)
|
||||
self.another_user = models.User.objects.create_user(
|
||||
"nutria", "nutria@nutria.nutria", "password", local=True, localname="nutria"
|
||||
"nutria",
|
||||
"nutria@nutria.nutria",
|
||||
"password",
|
||||
local=True,
|
||||
localname="nutria",
|
||||
)
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
self.remote_user = models.User.objects.create_user(
|
||||
|
@ -27,6 +32,7 @@ class Activitystreams(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(title="test book")
|
||||
|
||||
class TestStream(activitystreams.ActivityStream):
|
||||
|
|
|
@ -15,6 +15,7 @@ class Emailing(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
|
@ -22,6 +23,7 @@ class Emailing(TestCase):
|
|||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_invite_email(self, email_mock):
|
||||
|
|
77
bookwyrm/tests/test_postgres.py
Normal file
77
bookwyrm/tests/test_postgres.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
""" django configuration of postgres """
|
||||
from unittest.mock import patch
|
||||
from django.test import TestCase
|
||||
|
||||
from bookwyrm import models
|
||||
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay")
|
||||
class PostgresTriggers(TestCase):
|
||||
"""special migrations, fancy stuff ya know"""
|
||||
|
||||
def test_search_vector_on_create(self, *_):
|
||||
"""make sure that search_vector is being set correctly on create"""
|
||||
book = models.Edition.objects.create(title="The Long Goodbye")
|
||||
book.refresh_from_db()
|
||||
self.assertEqual(book.search_vector, "'goodby':3A 'long':2A")
|
||||
|
||||
def test_search_vector_on_update(self, *_):
|
||||
"""make sure that search_vector is being set correctly on edit"""
|
||||
book = models.Edition.objects.create(title="The Long Goodbye")
|
||||
book.title = "The Even Longer Goodbye"
|
||||
book.save(broadcast=False)
|
||||
book.refresh_from_db()
|
||||
self.assertEqual(book.search_vector, "'even':2A 'goodby':4A 'longer':3A")
|
||||
|
||||
def test_search_vector_fields(self, *_):
|
||||
"""use multiple fields to create search vector"""
|
||||
author = models.Author.objects.create(name="The Rays")
|
||||
book = models.Edition.objects.create(
|
||||
title="The Long Goodbye",
|
||||
subtitle="wow cool",
|
||||
series="series name",
|
||||
languages=["irrelevent"],
|
||||
)
|
||||
book.authors.add(author)
|
||||
book.refresh_from_db()
|
||||
self.assertEqual(
|
||||
book.search_vector,
|
||||
"'cool':5B 'goodby':3A 'long':2A 'name':9 'rays':7C 'seri':8 'the':6C 'wow':4B",
|
||||
)
|
||||
|
||||
def test_seach_vector_on_author_update(self, *_):
|
||||
"""update search when an author name changes"""
|
||||
author = models.Author.objects.create(name="The Rays")
|
||||
book = models.Edition.objects.create(
|
||||
title="The Long Goodbye",
|
||||
)
|
||||
book.authors.add(author)
|
||||
author.name = "Jeremy"
|
||||
author.save(broadcast=False)
|
||||
book.refresh_from_db()
|
||||
|
||||
self.assertEqual(book.search_vector, "'goodby':3A 'jeremy':4C 'long':2A")
|
||||
|
||||
def test_seach_vector_on_author_delete(self, *_):
|
||||
"""update search when an author name changes"""
|
||||
author = models.Author.objects.create(name="Jeremy")
|
||||
book = models.Edition.objects.create(
|
||||
title="The Long Goodbye",
|
||||
)
|
||||
|
||||
book.authors.add(author)
|
||||
book.refresh_from_db()
|
||||
self.assertEqual(book.search_vector, "'goodby':3A 'jeremy':4C 'long':2A")
|
||||
|
||||
book.authors.remove(author)
|
||||
book.refresh_from_db()
|
||||
self.assertEqual(book.search_vector, "'goodby':3A 'long':2A")
|
||||
|
||||
def test_search_vector_stop_word_fallback(self, *_):
|
||||
"""use a fallback when removing stop words leads to an empty vector"""
|
||||
book = models.Edition.objects.create(
|
||||
title="there there",
|
||||
)
|
||||
book.refresh_from_db()
|
||||
self.assertEqual(book.search_vector, "'there':1A,2A")
|
119
bookwyrm/tests/test_preview_images.py
Normal file
119
bookwyrm/tests/test_preview_images.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
""" test generating preview images """
|
||||
import pathlib
|
||||
from unittest.mock import patch
|
||||
from PIL import Image
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.db.models.fields.files import ImageFieldFile
|
||||
|
||||
from bookwyrm import models, settings
|
||||
|
||||
from bookwyrm.preview_images import (
|
||||
generate_site_preview_image_task,
|
||||
generate_edition_preview_image_task,
|
||||
generate_user_preview_image_task,
|
||||
generate_preview_image,
|
||||
save_and_cleanup,
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=missing-function-docstring
|
||||
class PreviewImages(TestCase):
|
||||
"""every response to a get request, html or json"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
avatar_file = pathlib.Path(__file__).parent.joinpath(
|
||||
"../static/images/no_cover.jpg"
|
||||
)
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"possum@local.com",
|
||||
"possum@possum.possum",
|
||||
"password",
|
||||
local=True,
|
||||
localname="possum",
|
||||
avatar=SimpleUploadedFile(
|
||||
avatar_file,
|
||||
open(avatar_file, "rb").read(),
|
||||
content_type="image/jpeg",
|
||||
),
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.work = models.Work.objects.create(title="Test Work")
|
||||
self.edition = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=self.work,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
self.site = models.SiteSettings.objects.create()
|
||||
|
||||
def test_generate_preview_image(self, *args, **kwargs):
|
||||
image_file = pathlib.Path(__file__).parent.joinpath(
|
||||
"../static/images/no_cover.jpg"
|
||||
)
|
||||
|
||||
texts = {
|
||||
"text_one": "Awesome Possum",
|
||||
"text_three": "@possum@local.com",
|
||||
}
|
||||
|
||||
result = generate_preview_image(texts=texts, picture=image_file, rating=5)
|
||||
self.assertIsInstance(result, Image.Image)
|
||||
self.assertEqual(
|
||||
result.size, (settings.PREVIEW_IMG_WIDTH, settings.PREVIEW_IMG_HEIGHT)
|
||||
)
|
||||
|
||||
def test_store_preview_image(self, *args, **kwargs):
|
||||
image = Image.new("RGB", (200, 200), color="#F00")
|
||||
|
||||
result = save_and_cleanup(image, instance=self.local_user)
|
||||
self.assertTrue(result)
|
||||
|
||||
self.local_user.refresh_from_db()
|
||||
self.assertIsInstance(self.local_user.preview_image, ImageFieldFile)
|
||||
self.assertIsNotNone(self.local_user.preview_image)
|
||||
self.assertEqual(self.local_user.preview_image.width, 200)
|
||||
self.assertEqual(self.local_user.preview_image.height, 200)
|
||||
|
||||
def test_site_preview(self, *args, **kwargs):
|
||||
"""generate site preview"""
|
||||
generate_site_preview_image_task()
|
||||
|
||||
self.site.refresh_from_db()
|
||||
|
||||
self.assertIsInstance(self.site.preview_image, ImageFieldFile)
|
||||
self.assertIsNotNone(self.site.preview_image)
|
||||
self.assertEqual(self.site.preview_image.width, settings.PREVIEW_IMG_WIDTH)
|
||||
self.assertEqual(self.site.preview_image.height, settings.PREVIEW_IMG_HEIGHT)
|
||||
|
||||
def test_edition_preview(self, *args, **kwargs):
|
||||
"""generate edition preview"""
|
||||
generate_edition_preview_image_task(self.edition.id)
|
||||
|
||||
self.edition.refresh_from_db()
|
||||
|
||||
self.assertIsInstance(self.edition.preview_image, ImageFieldFile)
|
||||
self.assertIsNotNone(self.edition.preview_image)
|
||||
self.assertEqual(self.edition.preview_image.width, settings.PREVIEW_IMG_WIDTH)
|
||||
self.assertEqual(self.edition.preview_image.height, settings.PREVIEW_IMG_HEIGHT)
|
||||
|
||||
def test_user_preview(self, *args, **kwargs):
|
||||
"""generate user preview"""
|
||||
generate_user_preview_image_task(self.local_user.id)
|
||||
|
||||
self.local_user.refresh_from_db()
|
||||
|
||||
self.assertIsInstance(self.local_user.preview_image, ImageFieldFile)
|
||||
self.assertIsNotNone(self.local_user.preview_image)
|
||||
self.assertEqual(
|
||||
self.local_user.preview_image.width, settings.PREVIEW_IMG_WIDTH
|
||||
)
|
||||
self.assertEqual(
|
||||
self.local_user.preview_image.height, settings.PREVIEW_IMG_HEIGHT
|
||||
)
|
|
@ -37,8 +37,13 @@ class Signature(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""create users and test data"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.mouse = models.User.objects.create_user(
|
||||
"mouse@%s" % DOMAIN, "mouse@example.com", "", local=True, localname="mouse"
|
||||
"mouse@%s" % DOMAIN,
|
||||
"mouse@example.com",
|
||||
"",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
self.rat = models.User.objects.create_user(
|
||||
"rat@%s" % DOMAIN, "rat@example.com", "", local=True, localname="rat"
|
||||
|
@ -53,6 +58,7 @@ class Signature(TestCase):
|
|||
"http://localhost/user/remote", KeyPair(private_key, public_key)
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def send(self, signature, now, data, digest):
|
||||
|
@ -113,6 +119,7 @@ class Signature(TestCase):
|
|||
status=200,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.get_remote_reviews.delay"):
|
||||
response = self.send_test_request(sender=self.fake_remote)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -136,6 +143,7 @@ class Signature(TestCase):
|
|||
data["publicKey"]["publicKeyPem"] = key_pair.public_key
|
||||
responses.add(responses.GET, self.fake_remote.remote_id, json=data, status=200)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.get_remote_reviews.delay"):
|
||||
# Key correct:
|
||||
response = self.send_test_request(sender=self.fake_remote)
|
||||
|
|
|
@ -22,6 +22,7 @@ class TemplateTags(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""create some filler objects"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.mouse",
|
||||
|
@ -37,10 +38,12 @@ class TemplateTags(TestCase):
|
|||
remote_id="http://example.com/rat",
|
||||
local=False,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(title="Test Book")
|
||||
|
||||
def test_get_user_rating(self, *_):
|
||||
"""get a user's most recent rating of a book"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.Review.objects.create(user=self.user, book=self.book, rating=3)
|
||||
self.assertEqual(bookwyrm_tags.get_user_rating(self.book, self.user), 3)
|
||||
|
@ -63,6 +66,9 @@ class TemplateTags(TestCase):
|
|||
def test_get_replies(self, *_):
|
||||
"""direct replies to a status"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.preview_images.generate_edition_preview_image_task.delay"
|
||||
):
|
||||
parent = models.Review.objects.create(
|
||||
user=self.user, book=self.book, content="hi"
|
||||
)
|
||||
|
@ -91,6 +97,9 @@ class TemplateTags(TestCase):
|
|||
def test_get_parent(self, *_):
|
||||
"""get the reply parent of a status"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.preview_images.generate_edition_preview_image_task.delay"
|
||||
):
|
||||
parent = models.Review.objects.create(
|
||||
user=self.user, book=self.book, content="hi"
|
||||
)
|
||||
|
@ -104,6 +113,7 @@ class TemplateTags(TestCase):
|
|||
|
||||
def test_get_user_liked(self, *_):
|
||||
"""did a user like a status"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||
|
||||
self.assertFalse(interaction.get_user_liked(self.user, status))
|
||||
|
@ -113,6 +123,7 @@ class TemplateTags(TestCase):
|
|||
|
||||
def test_get_user_boosted(self, *_):
|
||||
"""did a user boost a status"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||
|
||||
self.assertFalse(interaction.get_user_boosted(self.user, status))
|
||||
|
@ -122,15 +133,21 @@ class TemplateTags(TestCase):
|
|||
|
||||
def test_get_boosted(self, *_):
|
||||
"""load a boosted status"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||
boost = models.Boost.objects.create(user=self.user, boosted_status=status)
|
||||
status = models.Review.objects.create(
|
||||
user=self.remote_user, book=self.book
|
||||
)
|
||||
boost = models.Boost.objects.create(
|
||||
user=self.user, boosted_status=status
|
||||
)
|
||||
boosted = status_display.get_boosted(boost)
|
||||
self.assertIsInstance(boosted, models.Review)
|
||||
self.assertEqual(boosted, status)
|
||||
|
||||
def test_get_book_description(self, *_):
|
||||
"""grab it from the edition or the parent"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
work = models.Work.objects.create(title="Test Work")
|
||||
self.book.parent_work = work
|
||||
self.book.save()
|
||||
|
|
|
@ -18,6 +18,8 @@ class Inbox(TestCase):
|
|||
"""basic user and book data"""
|
||||
self.client = Client()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -45,6 +47,7 @@ class Inbox(TestCase):
|
|||
"cc": ["https://example.com/user/mouse/followers"],
|
||||
"object": {},
|
||||
}
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_inbox_invalid_get(self):
|
||||
|
|
|
@ -13,6 +13,7 @@ class InboxAdd(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""basic user and book data"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -32,13 +33,14 @@ class InboxAdd(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
work = models.Work.objects.create(title="work title")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Test",
|
||||
remote_id="https://example.com/book/37292",
|
||||
parent_work=work,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
@responses.activate
|
||||
|
|
|
@ -13,6 +13,7 @@ class InboxActivities(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""basic user and book data"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -32,6 +33,7 @@ class InboxActivities(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
self.status = models.Status.objects.create(
|
||||
|
@ -48,6 +50,7 @@ class InboxActivities(TestCase):
|
|||
"cc": ["https://example.com/user/mouse/followers"],
|
||||
"object": {},
|
||||
}
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
|
@ -85,6 +88,7 @@ class InboxActivities(TestCase):
|
|||
@patch("bookwyrm.activitystreams.ActivityStream.remove_object_from_related_stores")
|
||||
def test_boost_remote_status(self, redis_mock, _):
|
||||
"""boost a status from a remote server"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
work = models.Work.objects.create(title="work title")
|
||||
book = models.Edition.objects.create(
|
||||
title="Test",
|
||||
|
|
|
@ -12,6 +12,7 @@ class InboxBlock(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""basic user and book data"""
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -31,7 +32,7 @@ class InboxBlock(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_handle_blocks(self):
|
||||
|
|
|
@ -15,6 +15,7 @@ class InboxCreate(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""basic user and book data"""
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -24,13 +25,6 @@ class InboxCreate(TestCase):
|
|||
)
|
||||
self.local_user.remote_id = "https://example.com/user/mouse"
|
||||
self.local_user.save(broadcast=False)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
self.status = models.Status.objects.create(
|
||||
user=self.local_user,
|
||||
content="Test status",
|
||||
remote_id="https://example.com/status/1",
|
||||
)
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
self.remote_user = models.User.objects.create_user(
|
||||
"rat",
|
||||
|
@ -41,6 +35,13 @@ class InboxCreate(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
self.status = models.Status.objects.create(
|
||||
user=self.local_user,
|
||||
content="Test status",
|
||||
remote_id="https://example.com/status/1",
|
||||
)
|
||||
|
||||
self.create_json = {
|
||||
"id": "hi",
|
||||
|
@ -50,6 +51,7 @@ class InboxCreate(TestCase):
|
|||
"cc": ["https://example.com/user/mouse/followers"],
|
||||
"object": {},
|
||||
}
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_create_status(self):
|
||||
|
@ -60,6 +62,8 @@ class InboxCreate(TestCase):
|
|||
"../../data/ap_quotation.json"
|
||||
)
|
||||
status_data = json.loads(datafile.read_bytes())
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
models.Edition.objects.create(
|
||||
title="Test Book", remote_id="https://example.com/book/1"
|
||||
)
|
||||
|
@ -129,6 +133,7 @@ class InboxCreate(TestCase):
|
|||
|
||||
def test_create_rating(self):
|
||||
"""a remote rating activity"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
book = models.Edition.objects.create(
|
||||
title="Test Book", remote_id="https://example.com/book/1"
|
||||
)
|
||||
|
@ -157,7 +162,10 @@ class InboxCreate(TestCase):
|
|||
"rating": 3,
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
}
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status") as redis_mock:
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.activitystreams.ActivityStream.add_status"
|
||||
) as redis_mock:
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertTrue(redis_mock.called)
|
||||
rating = models.ReviewRating.objects.first()
|
||||
|
|
|
@ -13,6 +13,7 @@ class InboxActivities(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""basic user and book data"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -47,6 +48,7 @@ class InboxActivities(TestCase):
|
|||
"cc": ["https://example.com/user/mouse/followers"],
|
||||
"object": {},
|
||||
}
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_delete_status(self):
|
||||
|
@ -117,6 +119,7 @@ class InboxActivities(TestCase):
|
|||
"object": self.remote_user.remote_id,
|
||||
}
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
views.inbox.activity_task(activity)
|
||||
self.assertFalse(models.User.objects.get(username="rat@example.com").is_active)
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ class InboxRelationships(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""basic user and book data"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -33,6 +34,7 @@ class InboxRelationships(TestCase):
|
|||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_follow(self):
|
||||
|
|
|
@ -12,6 +12,7 @@ class InboxActivities(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""basic user and book data"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -31,6 +32,7 @@ class InboxActivities(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
|
||||
self.status = models.Status.objects.create(
|
||||
|
@ -47,6 +49,8 @@ class InboxActivities(TestCase):
|
|||
"cc": ["https://example.com/user/mouse/followers"],
|
||||
"object": {},
|
||||
}
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_handle_favorite(self):
|
||||
|
|
|
@ -12,6 +12,7 @@ class InboxRemove(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""basic user and book data"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -31,6 +32,8 @@ class InboxRemove(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.work = models.Work.objects.create(title="work title")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Test",
|
||||
|
@ -38,6 +41,7 @@ class InboxRemove(TestCase):
|
|||
parent_work=self.work,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_handle_unshelve_book(self):
|
||||
|
|
|
@ -14,6 +14,7 @@ class InboxUpdate(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
"""basic user and book data"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@example.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -42,6 +43,7 @@ class InboxUpdate(TestCase):
|
|||
"cc": ["https://example.com/user/mouse/followers"],
|
||||
"object": {},
|
||||
}
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_update_list(self):
|
||||
|
@ -96,6 +98,8 @@ class InboxUpdate(TestCase):
|
|||
del userdata["icon"]
|
||||
self.assertIsNone(self.remote_user.name)
|
||||
self.assertFalse(self.remote_user.discoverable)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
views.inbox.activity_task(
|
||||
{
|
||||
"type": "Update",
|
||||
|
@ -120,6 +124,7 @@ class InboxUpdate(TestCase):
|
|||
datafile = pathlib.Path(__file__).parent.joinpath("../../data/bw_edition.json")
|
||||
bookdata = json.loads(datafile.read_bytes())
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
models.Work.objects.create(
|
||||
title="Test Work", remote_id="https://bookwyrm.social/book/5988"
|
||||
)
|
||||
|
@ -150,6 +155,7 @@ class InboxUpdate(TestCase):
|
|||
datafile = pathlib.Path(__file__).parent.joinpath("../../data/bw_work.json")
|
||||
bookdata = json.loads(datafile.read_bytes())
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
book = models.Work.objects.create(
|
||||
title="Test Book", remote_id="https://bookwyrm.social/book/5988"
|
||||
)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from unittest.mock import patch
|
||||
|
||||
from bookwyrm import forms, models, views
|
||||
|
||||
|
@ -12,6 +13,7 @@ class AnnouncementViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
|
@ -19,6 +21,7 @@ class AnnouncementViews(TestCase):
|
|||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_announcements_page(self):
|
||||
|
|
|
@ -19,6 +19,7 @@ class AuthenticationViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -28,6 +29,7 @@ class AuthenticationViews(TestCase):
|
|||
)
|
||||
self.anonymous_user = AnonymousUser
|
||||
self.anonymous_user.is_authenticated = False
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
self.settings = models.SiteSettings.objects.create(id=1)
|
||||
|
||||
def test_login_get(self):
|
||||
|
@ -58,6 +60,7 @@ class AuthenticationViews(TestCase):
|
|||
"email": "aa@bb.cccc",
|
||||
},
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.views.authentication.login"):
|
||||
response = view(request)
|
||||
self.assertEqual(models.User.objects.count(), 2)
|
||||
|
@ -74,6 +77,7 @@ class AuthenticationViews(TestCase):
|
|||
"register/",
|
||||
{"localname": "nutria ", "password": "mouseword", "email": "aa@bb.ccc"},
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.views.authentication.login"):
|
||||
response = view(request)
|
||||
self.assertEqual(models.User.objects.count(), 2)
|
||||
|
@ -153,6 +157,7 @@ class AuthenticationViews(TestCase):
|
|||
"invite_code": "testcode",
|
||||
},
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.views.authentication.login"):
|
||||
response = view(request)
|
||||
self.assertEqual(models.User.objects.count(), 2)
|
||||
|
|
|
@ -17,6 +17,7 @@ class AuthorViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -33,12 +34,14 @@ class AuthorViews(TestCase):
|
|||
content_type=ContentType.objects.get_for_model(models.User),
|
||||
).id
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.work = models.Work.objects.create(title="Test Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=self.work,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_author_page(self):
|
||||
|
|
|
@ -14,6 +14,7 @@ class BlockViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
|
@ -31,6 +32,7 @@ class BlockViews(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_block_get(self, _):
|
||||
|
|
|
@ -23,6 +23,7 @@ class BookViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -39,12 +40,14 @@ class BookViews(TestCase):
|
|||
content_type=ContentType.objects.get_for_model(models.User),
|
||||
).id
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.work = models.Work.objects.create(title="Test Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=self.work,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_book_page(self):
|
||||
|
@ -71,6 +74,7 @@ class BookViews(TestCase):
|
|||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
result = view(request, self.book.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
|
@ -85,8 +89,11 @@ class BookViews(TestCase):
|
|||
form.data["last_edited_by"] = self.local_user.id
|
||||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
view(request, self.book.id)
|
||||
|
||||
self.book.refresh_from_db()
|
||||
self.assertEqual(self.book.title, "New Title")
|
||||
|
||||
|
@ -101,6 +108,7 @@ class BookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
result = view(request, self.book.id)
|
||||
result.render()
|
||||
|
||||
|
@ -120,6 +128,7 @@ class BookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
view(request, self.book.id)
|
||||
|
||||
|
@ -141,6 +150,7 @@ class BookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
view(request, self.book.id)
|
||||
self.book.refresh_from_db()
|
||||
|
@ -157,6 +167,7 @@ class BookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
view(request)
|
||||
book = models.Edition.objects.get(title="New Title")
|
||||
self.assertEqual(book.parent_work.title, "New Title")
|
||||
|
@ -172,6 +183,7 @@ class BookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
view(request)
|
||||
book = models.Edition.objects.get(title="New Title")
|
||||
self.assertEqual(book.parent_work, self.work)
|
||||
|
@ -188,6 +200,7 @@ class BookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
view(request)
|
||||
book = models.Edition.objects.get(title="New Title")
|
||||
self.assertEqual(book.parent_work.title, "New Title")
|
||||
|
@ -197,8 +210,11 @@ class BookViews(TestCase):
|
|||
def test_switch_edition(self):
|
||||
"""updates user's relationships to a book"""
|
||||
work = models.Work.objects.create(title="test work")
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
edition1 = models.Edition.objects.create(title="first ed", parent_work=work)
|
||||
edition2 = models.Edition.objects.create(title="second ed", parent_work=work)
|
||||
edition2 = models.Edition.objects.create(
|
||||
title="second ed", parent_work=work
|
||||
)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user)
|
||||
models.ShelfBook.objects.create(
|
||||
|
@ -251,6 +267,7 @@ class BookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
) as delay_mock:
|
||||
|
@ -283,6 +300,7 @@ class BookViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
) as delay_mock:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from unittest.mock import patch
|
||||
|
||||
from bookwyrm import models, views
|
||||
|
||||
|
@ -12,6 +13,7 @@ class DirectoryViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -29,6 +31,7 @@ class DirectoryViews(TestCase):
|
|||
remote_id="https://example.com/users/rat",
|
||||
discoverable=True,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_directory_page(self):
|
||||
|
|
|
@ -21,6 +21,7 @@ class EditUserViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
|
@ -31,6 +32,8 @@ class EditUserViews(TestCase):
|
|||
self.rat = models.User.objects.create_user(
|
||||
"rat@local.com", "rat@rat.rat", "password", local=True, localname="rat"
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(title="test")
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.ShelfBook.objects.create(
|
||||
|
@ -39,6 +42,7 @@ class EditUserViews(TestCase):
|
|||
shelf=self.local_user.shelf_set.first(),
|
||||
)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
self.anonymous_user = AnonymousUser
|
||||
self.anonymous_user.is_authenticated = False
|
||||
|
@ -64,6 +68,7 @@ class EditUserViews(TestCase):
|
|||
request.user = self.local_user
|
||||
|
||||
self.assertIsNone(self.local_user.name)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
) as delay_mock:
|
||||
|
@ -88,6 +93,7 @@ class EditUserViews(TestCase):
|
|||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
) as delay_mock:
|
||||
|
|
|
@ -15,6 +15,7 @@ class FederationViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
|
@ -32,6 +33,7 @@ class FederationViews(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_federation_page(self):
|
||||
|
|
|
@ -22,6 +22,7 @@ class FeedViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
|
@ -29,11 +30,13 @@ class FeedViews(TestCase):
|
|||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(
|
||||
parent_work=models.Work.objects.create(title="hi"),
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_feed(self, *_):
|
||||
|
@ -89,6 +92,9 @@ class FeedViews(TestCase):
|
|||
output = BytesIO()
|
||||
image.save(output, format=image.format)
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.preview_images.generate_edition_preview_image_task.delay"
|
||||
):
|
||||
status = models.Review.objects.create(
|
||||
content="hi",
|
||||
user=self.local_user,
|
||||
|
@ -146,6 +152,7 @@ class FeedViews(TestCase):
|
|||
|
||||
def test_get_suggested_book(self, *_):
|
||||
"""gets books the ~*~ algorithm ~*~ thinks you want to post about"""
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.ShelfBook.objects.create(
|
||||
book=self.book,
|
||||
|
|
|
@ -16,6 +16,7 @@ class BookViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -42,6 +43,7 @@ class BookViews(TestCase):
|
|||
content_type=ContentType.objects.get_for_model(models.User),
|
||||
).id
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.work = models.Work.objects.create(title="Test Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
|
@ -66,6 +68,7 @@ class BookViews(TestCase):
|
|||
|
||||
def test_handle_follow_local_manually_approves(self):
|
||||
"""send a follow request"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
rat = models.User.objects.create_user(
|
||||
"rat@local.com",
|
||||
"rat@rat.com",
|
||||
|
@ -89,6 +92,7 @@ class BookViews(TestCase):
|
|||
|
||||
def test_handle_follow_local(self):
|
||||
"""send a follow request"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
rat = models.User.objects.create_user(
|
||||
"rat@local.com",
|
||||
"rat@rat.com",
|
||||
|
|
|
@ -13,6 +13,7 @@ class GetStartedViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.mouse",
|
||||
|
@ -20,6 +21,7 @@ class GetStartedViews(TestCase):
|
|||
local=True,
|
||||
localname="mouse",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(
|
||||
parent_work=models.Work.objects.create(title="hi"),
|
||||
title="Example Edition",
|
||||
|
@ -28,6 +30,7 @@ class GetStartedViews(TestCase):
|
|||
models.Connector.objects.create(
|
||||
identifier="self", connector_file="self_connector", local=True
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_profile_view(self):
|
||||
|
@ -52,6 +55,7 @@ class GetStartedViews(TestCase):
|
|||
request.user = self.local_user
|
||||
|
||||
self.assertIsNone(self.local_user.name)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
|
||||
) as delay_mock:
|
||||
|
|
|
@ -16,6 +16,7 @@ class GoalViews(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -32,12 +33,14 @@ class GoalViews(TestCase):
|
|||
localname="rat",
|
||||
remote_id="https://example.com/users/rat",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
)
|
||||
self.anonymous_user = AnonymousUser
|
||||
self.anonymous_user.is_authenticated = False
|
||||
with patch("bookwyrm.preview_images.generate_site_preview_image_task.delay"):
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_goal_page_no_goal(self):
|
||||
|
|
|
@ -19,6 +19,7 @@ class ViewsHelpers(TestCase):
|
|||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
|
@ -28,12 +29,6 @@ class ViewsHelpers(TestCase):
|
|||
localname="mouse",
|
||||
remote_id="https://example.com/users/mouse",
|
||||
)
|
||||
self.work = models.Work.objects.create(title="Test Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Test Book",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=self.work,
|
||||
)
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
self.remote_user = models.User.objects.create_user(
|
||||
"rat",
|
||||
|
@ -45,6 +40,13 @@ class ViewsHelpers(TestCase):
|
|||
inbox="https://example.com/users/rat/inbox",
|
||||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
self.work = models.Work.objects.create(title="Test Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Test Book",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=self.work,
|
||||
)
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json")
|
||||
self.userdata = json.loads(datafile.read_bytes())
|
||||
del self.userdata["icon"]
|
||||
|
@ -142,6 +144,7 @@ class ViewsHelpers(TestCase):
|
|||
json=self.userdata,
|
||||
status=200,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.user.set_remote_server.delay"):
|
||||
result = views.helpers.handle_remote_webfinger("@mouse@example.com")
|
||||
self.assertIsInstance(result, models.User)
|
||||
|
@ -202,6 +205,7 @@ class ViewsHelpers(TestCase):
|
|||
|
||||
def test_get_annotated_users(self, _):
|
||||
"""list of people you might know"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
user_1 = models.User.objects.create_user(
|
||||
"nutria@local.com",
|
||||
"nutria@nutria.com",
|
||||
|
@ -249,6 +253,7 @@ class ViewsHelpers(TestCase):
|
|||
|
||||
def test_get_annotated_users_counts(self, _):
|
||||
"""correct counting for multiple shared attributed"""
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
user_1 = models.User.objects.create_user(
|
||||
"nutria@local.com",
|
||||
"nutria@nutria.com",
|
||||
|
@ -257,6 +262,7 @@ class ViewsHelpers(TestCase):
|
|||
localname="nutria",
|
||||
discoverable=True,
|
||||
)
|
||||
with patch("bookwyrm.preview_images.generate_user_preview_image_task.delay"):
|
||||
for i in range(3):
|
||||
user = models.User.objects.create_user(
|
||||
"{:d}@local.com".format(i),
|
||||
|
@ -268,6 +274,7 @@ class ViewsHelpers(TestCase):
|
|||
user.following.add(user_1)
|
||||
user.followers.add(self.local_user)
|
||||
|
||||
with patch("bookwyrm.preview_images.generate_edition_preview_image_task.delay"):
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
for i in range(3):
|
||||
book = models.Edition.objects.create(
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue