diff --git a/fedireads/activitypub/collection.py b/fedireads/activitypub/collection.py index 3c7019b43..3a4837ab1 100644 --- a/fedireads/activitypub/collection.py +++ b/fedireads/activitypub/collection.py @@ -120,7 +120,7 @@ def get_add_remove(user, book, shelf, action='Add'): 'object': { # TODO: document?? 'type': 'Document', - 'name': book.data['title'], + 'name': book.title, 'url': book.openlibrary_key }, 'target': { diff --git a/fedireads/activitypub/status.py b/fedireads/activitypub/status.py index 4fff94588..5d6c0c752 100644 --- a/fedireads/activitypub/status.py +++ b/fedireads/activitypub/status.py @@ -16,7 +16,7 @@ def get_review_article(review): ''' a book review formatted for a non-fedireads isntance (mastodon) ''' status = get_status(review) name = 'Review of "%s" (%d stars): %s' % ( - review.book.data['title'], + review.book.title, review.rating, review.name ) diff --git a/fedireads/books_manager.py b/fedireads/books_manager.py new file mode 100644 index 000000000..90bc6d869 --- /dev/null +++ b/fedireads/books_manager.py @@ -0,0 +1,11 @@ +''' select and call a connector for whatever book task needs doing ''' +from fedireads.connectors import OpenLibraryConnector + +openlibrary = OpenLibraryConnector() +def get_or_create_book(key): + ''' pull up a book record by whatever means possible ''' + return openlibrary.get_or_create_book(key) + +def search(query): + ''' ya ''' + return openlibrary.search(query) diff --git a/fedireads/connectors/__init__.py b/fedireads/connectors/__init__.py new file mode 100644 index 000000000..8cee368cd --- /dev/null +++ b/fedireads/connectors/__init__.py @@ -0,0 +1,3 @@ +''' bring connectors into the namespace ''' +from .settings import CONNECTORS +from .openlibrary import OpenLibraryConnector diff --git a/fedireads/connectors/abstract_connector.py b/fedireads/connectors/abstract_connector.py new file mode 100644 index 000000000..dfb6b99de --- /dev/null +++ b/fedireads/connectors/abstract_connector.py @@ -0,0 +1,60 @@ +''' functionality outline for a book data connector ''' +from abc import ABC, abstractmethod + +from fedireads.connectors import CONNECTORS + + +class AbstractConnector(ABC): + ''' generic book data connector ''' + + def __init__(self, connector_name): + # load connector settings + settings = CONNECTORS.get(connector_name) + if not settings: + raise ValueError('No connector with name "%s"' % connector_name) + + try: + self.url = settings['BASE_URL'] + self.covers_url = settings['COVERS_URL'] + self.db_field = settings['DB_KEY_FIELD'] + self.key_name = settings['KEY_NAME'] + except KeyError: + raise KeyError('Invalid connector settings') + # TODO: politeness settings + + + @abstractmethod + def search(self, query): + ''' free text search ''' + # return list of search result objs + pass + + + @abstractmethod + def get_or_create_book(self, book_id): + ''' request and format a book given an identifier ''' + # return book model obj + pass + + + @abstractmethod + def get_or_create_author(self, book_id): + ''' request and format a book given an identifier ''' + # return book model obj + pass + + + @abstractmethod + def update_book(self, book_obj): + ''' sync a book with the canonical remote copy ''' + # return book model obj + pass + + +class SearchResult(object): + ''' standardized search result object ''' + def __init__(self, title, key, author, year): + self.title = title + self.key = key + self.author = author + self.year = year diff --git a/fedireads/connectors/openlibrary.py b/fedireads/connectors/openlibrary.py new file mode 100644 index 000000000..6d4f8c739 --- /dev/null +++ b/fedireads/connectors/openlibrary.py @@ -0,0 +1,138 @@ +''' openlibrary data connector ''' +from django.core.exceptions import ObjectDoesNotExist +from django.core.files.base import ContentFile +import re +import requests + +from fedireads import models +from .abstract_connector import AbstractConnector, SearchResult + + +class OpenLibraryConnector(AbstractConnector): + ''' instantiate a connector for OL ''' + def __init__(self): + super().__init__('openlibrary') + + + def search(self, query): + ''' query openlibrary search ''' + resp = requests.get('%s/search.json' % self.url, params={'q': query}) + if not resp.ok: + resp.raise_for_status() + data = resp.json() + results = [] + + for doc in data['docs'][:5]: + key = doc['key'] + key = key.split('/')[-1] + author = doc.get('author_name') or ['Unknown'] + results.append(SearchResult( + doc.get('title'), + key, + author[0], + doc.get('first_publish_year'), + )) + return results + + + def get_or_create_book(self, olkey): + ''' pull up a book record by whatever means possible ''' + if re.match(r'^OL\d+W$', olkey): + model = models.Work + elif re.match(r'^OL\d+M$', olkey): + model = models.Edition + else: + raise ValueError('Invalid OpenLibrary ID') + + try: + book = models.Book.objects.get(openlibrary_key=olkey) + return book + except ObjectDoesNotExist: + # no book was found, so we start creating a new one + book = model(openlibrary_key=olkey) + + # load the book json from openlibrary.org + response = requests.get('%s/works/%s.json' % (self.url, olkey)) + if not response.ok: + response.raise_for_status() + + data = response.json() + + # great, we can update our book. + book.title = data['title'] + description = data.get('description') + if description: + if isinstance(description, dict): + description = description.get('value') + book.description = description + book.pages = data.get('pages') + #book.published_date = data.get('publish_date') + + # this book sure as heck better be an edition + if data.get('works'): + key = data.get('works')[0]['key'] + key = key.split('/')[-1] + work = self.get_or_create_book(key) + book.parent_work = work + book.save() + + # we also need to know the author get the cover + for author_blob in data.get('authors'): + # this id is "/authors/OL1234567A" and we want just "OL1234567A" + author_blob = author_blob.get('author', author_blob) + author_id = author_blob['key'] + author_id = author_id.split('/')[-1] + book.authors.add(self.get_or_create_author(author_id)) + + if data.get('covers') and len(data['covers']): + book.cover.save(*self.get_cover(data['covers'][0]), save=True) + + return book + + + def get_or_create_author(self, olkey): + ''' load that author ''' + if not re.match(r'^OL\d+A$', olkey): + raise ValueError('Invalid OpenLibrary author ID') + try: + author = models.Author.objects.get(openlibrary_key=olkey) + except ObjectDoesNotExist: + pass + + response = requests.get('%s/authors/%s.json' % (self.url, olkey)) + if not response.ok: + response.raise_for_status() + + data = response.json() + author = models.Author(openlibrary_key=olkey) + bio = data.get('bio') + if bio: + if isinstance(bio, dict): + bio = bio.get('value') + author.bio = bio + name = data['name'] + author.name = name + # TODO this is making some BOLD assumption + author.last_name = name.split(' ')[-1] + author.first_name = ' '.join(name.split(' ')[:-1]) + #author.born = data.get('birth_date') + #author.died = data.get('death_date') + author.save() + + return author + + + def get_cover(self, cover_id): + ''' ask openlibrary for the cover ''' + # TODO: get medium and small versions + image_name = '%s-M.jpg' % cover_id + url = '%s/b/id/%s' % (self.covers_url, image_name) + response = requests.get(url) + if not response.ok: + response.raise_for_status() + image_content = ContentFile(requests.get(url).content) + return [image_name, image_content] + + + def update_book(self, book_obj): + pass diff --git a/fedireads/connectors/settings.py b/fedireads/connectors/settings.py new file mode 100644 index 000000000..0b34a22e9 --- /dev/null +++ b/fedireads/connectors/settings.py @@ -0,0 +1,28 @@ +''' settings book data connectors ''' +CONNECTORS = { + 'openlibrary': { + 'KEY_NAME': 'olkey', + 'DB_KEY_FIELD': 'openlibrary_key', + 'POLITENESS_DELAY': 0, + 'MAX_DAILY_QUERIES': -1, + 'BASE_URL': 'https://openlibrary.org', + 'COVERS_URL': 'https://covers.openlibrary.org', + }, +} + +''' not implemented yet: + 'librarything': { + 'KEY_NAME': 'ltkey', + 'DB_KEY_FIELD': 'librarything_key', + 'POLITENESS_DELAY': 1, + 'MAX_DAILY_QUERIES': 1000, + 'BASE_URL': 'https://librarything.com', + }, + 'worldcat': { + 'KEY_NAME': 'ocn', + 'DB_KEY_FIELD': 'oclc_number', + 'POLITENESS_DELAY': 0, + 'MAX_DAILY_QUERIES': -1, + 'BASE_URL': 'https://worldcat.org', + }, +''' diff --git a/fedireads/migrations/0010_auto_20200307_0655.py b/fedireads/migrations/0010_auto_20200307_0655.py new file mode 100644 index 000000000..7fe03cc69 --- /dev/null +++ b/fedireads/migrations/0010_auto_20200307_0655.py @@ -0,0 +1,190 @@ +# Generated by Django 3.0.3 on 2020-03-07 06:55 + +import datetime +from django.db import migrations, models +import django.db.models.deletion +import fedireads.utils.fields +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('fedireads', '0009_status_published_date'), + ] + + operations = [ + migrations.CreateModel( + name='Edition', + fields=[ + ('book_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fedireads.Book')), + ('isbn', models.CharField(max_length=255, null=True, unique=True)), + ('oclc_number', models.CharField(max_length=255, null=True, unique=True)), + ('pages', models.IntegerField(null=True)), + ], + options={ + 'abstract': False, + }, + bases=('fedireads.book',), + ), + migrations.CreateModel( + name='Work', + fields=[ + ('book_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fedireads.Book')), + ('lccn', models.CharField(max_length=255, null=True, unique=True)), + ], + options={ + 'abstract': False, + }, + bases=('fedireads.book',), + ), + migrations.RemoveField( + model_name='author', + name='data', + ), + migrations.RemoveField( + model_name='book', + name='added_by', + ), + migrations.RemoveField( + model_name='book', + name='data', + ), + migrations.AddField( + model_name='author', + name='aliases', + field=fedireads.utils.fields.ArrayField(base_field=models.CharField(max_length=255), blank=True, size=None), + ), + migrations.AddField( + model_name='author', + name='bio', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='author', + name='born', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='author', + name='died', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='author', + name='first_name', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='author', + name='last_name', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='author', + name='name', + field=models.CharField(default='Unknown', max_length=255), + preserve_default=False, + ), + migrations.AddField( + model_name='author', + name='wikipedia_link', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='book', + name='description', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='book', + name='first_published_date', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='book', + name='language', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='book', + name='last_sync_date', + field=models.DateTimeField(default=datetime.datetime.now), + ), + migrations.AddField( + model_name='book', + name='librarything_key', + field=models.CharField(max_length=255, null=True, unique=True), + ), + migrations.AddField( + model_name='book', + name='local_edits', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='book', + name='local_key', + field=models.CharField(default=uuid.uuid4, max_length=255, unique=True), + ), + migrations.AddField( + model_name='book', + name='misc_identifiers', + field=fedireads.utils.fields.JSONField(null=True), + ), + migrations.AddField( + model_name='book', + name='origin', + field=models.CharField(max_length=255, null=True, unique=True), + ), + migrations.AddField( + model_name='book', + name='published_date', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='book', + name='series', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='book', + name='series_number', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='book', + name='sort_title', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='book', + name='subtitle', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='book', + name='sync', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='book', + name='title', + field=models.CharField(default='Unknown', max_length=255), + preserve_default=False, + ), + migrations.AlterField( + model_name='author', + name='openlibrary_key', + field=models.CharField(max_length=255, null=True, unique=True), + ), + migrations.AlterField( + model_name='book', + name='openlibrary_key', + field=models.CharField(max_length=255, null=True, unique=True), + ), + migrations.AddField( + model_name='book', + name='parent_work', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='fedireads.Work'), + ), + ] diff --git a/fedireads/models/__init__.py b/fedireads/models/__init__.py index 02154eeba..dc463a075 100644 --- a/fedireads/models/__init__.py +++ b/fedireads/models/__init__.py @@ -1,5 +1,5 @@ ''' bring all the models into the app namespace ''' -from .book import Shelf, ShelfBook, Book, Author -from .user import User, UserRelationship, FederatedServer +from .book import Book, Work, Edition, Author +from .shelf import Shelf, ShelfBook from .status import Status, Review, Favorite, Tag - +from .user import User, UserRelationship, FederatedServer diff --git a/fedireads/models/book.py b/fedireads/models/book.py index c067db078..3dc1f21ca 100644 --- a/fedireads/models/book.py +++ b/fedireads/models/book.py @@ -1,68 +1,65 @@ ''' database schema for books and shelves ''' +from datetime import datetime from django.db import models +from uuid import uuid4 from fedireads.settings import DOMAIN -from fedireads.utils.fields import JSONField +from fedireads.utils.fields import JSONField, ArrayField from fedireads.utils.models import FedireadsModel -class Shelf(FedireadsModel): - name = models.CharField(max_length=100) - identifier = models.CharField(max_length=100) - user = models.ForeignKey('User', on_delete=models.PROTECT) - editable = models.BooleanField(default=True) - books = models.ManyToManyField( - 'Book', - symmetrical=False, - through='ShelfBook', - through_fields=('shelf', 'book') - ) - - @property - def absolute_id(self): - ''' use shelf identifier as absolute id ''' - base_path = self.user.absolute_id - model_name = type(self).__name__.lower() - return '%s/%s/%s' % (base_path, model_name, self.identifier) - - class Meta: - unique_together = ('user', 'identifier') - - -class ShelfBook(FedireadsModel): - # many to many join table for books and shelves - book = models.ForeignKey('Book', on_delete=models.PROTECT) - shelf = models.ForeignKey('Shelf', on_delete=models.PROTECT) - added_by = models.ForeignKey( - 'User', - blank=True, - null=True, - on_delete=models.PROTECT - ) - - class Meta: - unique_together = ('book', 'shelf') - - class Book(FedireadsModel): - ''' a non-canonical copy of a work (not book) from open library ''' - openlibrary_key = models.CharField(max_length=255, unique=True) - data = JSONField() + ''' a generic book, which can mean either an edition or a work ''' + # these identifiers apply to both works and editions + openlibrary_key = models.CharField(max_length=255, unique=True, null=True) + librarything_key = models.CharField(max_length=255, unique=True, null=True) + local_key = models.CharField(max_length=255, unique=True, default=uuid4) + misc_identifiers = JSONField(null=True) + + # info about where the data comes from and where/if to sync + origin = models.CharField(max_length=255, unique=True, null=True) + local_edits = models.BooleanField(default=False) + sync = models.BooleanField(default=True) + last_sync_date = models.DateTimeField(default=datetime.now) + + # TODO: edit history + + # book/work metadata + title = models.CharField(max_length=255) + sort_title = models.CharField(max_length=255, null=True) + subtitle = models.TextField(blank=True, null=True) + description = models.TextField(blank=True, null=True) + language = models.CharField(max_length=255, null=True) + series = models.CharField(max_length=255, blank=True, null=True) + series_number = models.CharField(max_length=255, blank=True, null=True) + # TODO: include an annotation about the type of authorship (ie, translator) authors = models.ManyToManyField('Author') # TODO: also store cover thumbnail cover = models.ImageField(upload_to='covers/', blank=True, null=True) + first_published_date = models.DateTimeField(null=True) + published_date = models.DateTimeField(null=True) shelves = models.ManyToManyField( 'Shelf', symmetrical=False, through='ShelfBook', through_fields=('book', 'shelf') ) - added_by = models.ForeignKey( - 'User', - blank=True, - null=True, - on_delete=models.PROTECT - ) + # TODO: why can't I just call this work???? + parent_work = models.ForeignKey('Work', on_delete=models.PROTECT, null=True) + + +class Work(Book): + ''' a work (an abstract concept of a book that manifests in an edition) ''' + # library of congress catalog control number + lccn = models.CharField(max_length=255, unique=True, null=True) + + +class Edition(Book): + ''' an edition of a book ''' + # these identifiers only apply to work + isbn = models.CharField(max_length=255, unique=True, null=True) + oclc_number = models.CharField(max_length=255, unique=True, null=True) + pages = models.IntegerField(null=True) @property def absolute_id(self): @@ -74,6 +71,14 @@ class Book(FedireadsModel): class Author(FedireadsModel): ''' copy of an author from OL ''' - openlibrary_key = models.CharField(max_length=255) - data = JSONField() + openlibrary_key = models.CharField(max_length=255, null=True, unique=True) + wikipedia_link = models.CharField(max_length=255, blank=True, null=True) + # idk probably other keys would be useful here? + born = models.DateTimeField(null=True) + died = models.DateTimeField(null=True) + name = models.CharField(max_length=255) + last_name = models.CharField(max_length=255, null=True) + first_name = models.CharField(max_length=255, null=True) + aliases = ArrayField(models.CharField(max_length=255), blank=True) + bio = models.TextField(null=True, blank=True) diff --git a/fedireads/models/shelf.py b/fedireads/models/shelf.py new file mode 100644 index 000000000..cdd8701b1 --- /dev/null +++ b/fedireads/models/shelf.py @@ -0,0 +1,42 @@ +''' puttin' books on shelves ''' +from django.db import models + +from fedireads.utils.models import FedireadsModel + + +class Shelf(FedireadsModel): + name = models.CharField(max_length=100) + identifier = models.CharField(max_length=100) + user = models.ForeignKey('User', on_delete=models.PROTECT) + editable = models.BooleanField(default=True) + books = models.ManyToManyField( + 'Book', + symmetrical=False, + through='ShelfBook', + through_fields=('shelf', 'book') + ) + + @property + def absolute_id(self): + ''' use shelf identifier as absolute id ''' + base_path = self.user.absolute_id + model_name = type(self).__name__.lower() + return '%s/%s/%s' % (base_path, model_name, self.identifier) + + class Meta: + unique_together = ('user', 'identifier') + + +class ShelfBook(FedireadsModel): + # many to many join table for books and shelves + book = models.ForeignKey('Book', on_delete=models.PROTECT) + shelf = models.ForeignKey('Shelf', on_delete=models.PROTECT) + added_by = models.ForeignKey( + 'User', + blank=True, + null=True, + on_delete=models.PROTECT + ) + + class Meta: + unique_together = ('book', 'shelf') diff --git a/fedireads/openlibrary.py b/fedireads/openlibrary.py deleted file mode 100644 index 5a7fd7999..000000000 --- a/fedireads/openlibrary.py +++ /dev/null @@ -1,109 +0,0 @@ -''' activitystream api and books ''' -from django.core.exceptions import ObjectDoesNotExist -from django.core.files.base import ContentFile -import re -import requests - -from fedireads.models import Author, Book -from fedireads.settings import OL_URL - - -def book_search(query): - ''' look up a book ''' - response = requests.get('%s/search.json' % OL_URL, params={'q': query}) - if not response.ok: - response.raise_for_status() - data = response.json() - results = [] - - for doc in data['docs'][:5]: - key = doc['key'] - key = key.split('/')[-1] - author = doc.get('author_name') or ['Unknown'] - results.append({ - 'title': doc.get('title'), - 'olkey': key, - 'year': doc.get('first_publish_year'), - 'author': author[0], - }) - return results - - -def get_or_create_book(olkey, user=None, update=False): - ''' add a book by looking up its open library "work" key. I'm conflating - "book" and "work" here a bit; the table is called "book" in fedireads, but - in open library parlance, it's a "work," which is the canonical umbrella - item that contains all the editions ("book"s) ''' - # check if this is in the format of an OL book identifier - if not re.match(r'^OL\d+W$', olkey): - raise ValueError('Invalid OpenLibrary work ID') - - # get the existing entry from our db, if it exists - try: - book = Book.objects.get(openlibrary_key=olkey) - if not update: - return book - # we have the book, but still want to update it from OL - except ObjectDoesNotExist: - # no book was found, so we start creating a new one - book = Book(openlibrary_key=olkey) - - # load the book json from openlibrary.org - response = requests.get('%s/works/%s.json' % (OL_URL, olkey)) - if not response.ok: - response.raise_for_status() - - data = response.json() - book.data = data - - if user and user.is_authenticated: - book.added_by = user - - # great, we can update our book. - book.save() - - # we also need to know the author get the cover - for author_blob in data['authors']: - # this id starts as "/authors/OL1234567A" and we want just "OL1234567A" - author_id = author_blob['author']['key'] - author_id = author_id.split('/')[-1] - book.authors.add(get_or_create_author(author_id)) - - if data.get('covers') and len(data['covers']): - book.cover.save(*get_cover(data['covers'][0]), save=True) - - return book - - -def get_cover(cover_id): - ''' ask openlibrary for the cover ''' - # TODO: get medium and small versions - image_name = '%s-M.jpg' % cover_id - url = 'https://covers.openlibrary.org/b/id/%s' % image_name - response = requests.get(url) - if not response.ok: - response.raise_for_status() - image_content = ContentFile(requests.get(url).content) - return [image_name, image_content] - - -def get_or_create_author(olkey, update=False): - ''' load that author ''' - if not re.match(r'^OL\d+A$', olkey): - raise ValueError('Invalid OpenLibrary author ID') - try: - author = Author.objects.get(openlibrary_key=olkey) - if not update: - return author - except ObjectDoesNotExist: - pass - - response = requests.get('%s/authors/%s.json' % (OL_URL, olkey)) - if not response.ok: - response.raise_for_status() - - data = response.json() - author = Author(openlibrary_key=olkey, data=data) - author.save() - return author - diff --git a/fedireads/outgoing.py b/fedireads/outgoing.py index f91975ff7..8d5eb9f40 100644 --- a/fedireads/outgoing.py +++ b/fedireads/outgoing.py @@ -126,7 +126,7 @@ def handle_shelve(user, book, shelf): 'read': 'finished reading' }[shelf.identifier] name = user.name if user.name else user.localname - message = '%s %s %s' % (name, verb, book.data['title']) + message = '%s %s %s' % (name, verb, book.title) status = create_status(user, message, mention_books=[book]) activity = activitypub.get_status(status) @@ -150,7 +150,7 @@ def handle_unshelve(user, book, shelf): def handle_review(user, book, name, content, rating): ''' post a review ''' # validated and saves the review in the database so it has an id - review = create_review(user, book, name, content, rating) + review = create_review(user, book, name, content, rating, None) review_activity = activitypub.get_review(review) review_create_activity = activitypub.get_create(user, review_activity) diff --git a/fedireads/status.py b/fedireads/status.py index 0fe38b405..9fada1283 100644 --- a/fedireads/status.py +++ b/fedireads/status.py @@ -1,6 +1,6 @@ ''' Handle user activity ''' from fedireads import models -from fedireads.openlibrary import get_or_create_book +from fedireads.books_manager import get_or_create_book from fedireads.sanitize_html import InputHtmlParser from django.db import IntegrityError @@ -15,14 +15,17 @@ def create_review(user, possible_book, name, content, rating, published): # no ratings outside of 0-5 rating = rating if 0 <= rating <= 5 else 0 - return models.Review.objects.create( + review = models.Review( user=user, book=book, name=name, rating=rating, content=content, - published_date=published, ) + if published: + review.published_date = published + review.save() + return review def create_status(user, content, reply_parent=None, mention_books=None): diff --git a/fedireads/templates/author.html b/fedireads/templates/author.html index 191ad6bab..c89a94c5b 100644 --- a/fedireads/templates/author.html +++ b/fedireads/templates/author.html @@ -3,9 +3,9 @@ {% block content %}
-

{{ author.data.name }}

- {% if author.data.bio %} -
{{ author.data.bio | author_bio }} +

{{ author.name }}

+ {% if author.bio %} +
{{ author.bio | author_bio }}
{% endif %} {% for book in books %} diff --git a/fedireads/templates/book.html b/fedireads/templates/book.html index 60c679217..73e3c12d2 100644 --- a/fedireads/templates/book.html +++ b/fedireads/templates/book.html @@ -3,7 +3,7 @@ {% block content %}