From dab0aeffb2a3e9d500c43f484d453ba2b8064c67 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 Nov 2020 12:48:41 -0800 Subject: [PATCH 01/12] Adds image attachment field to status model --- bookwyrm/migrations/0012_status_images.py | 19 +++++++++++++++++++ bookwyrm/models/status.py | 5 +++++ bookwyrm/status.py | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 bookwyrm/migrations/0012_status_images.py diff --git a/bookwyrm/migrations/0012_status_images.py b/bookwyrm/migrations/0012_status_images.py new file mode 100644 index 000000000..3b440bfd9 --- /dev/null +++ b/bookwyrm/migrations/0012_status_images.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.7 on 2020-11-23 20:44 + +import bookwyrm.utils.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0011_auto_20201113_1727'), + ] + + operations = [ + migrations.AddField( + model_name='status', + name='images', + field=bookwyrm.utils.fields.ArrayField(base_field=models.ImageField(upload_to='status/'), default=list, size=None), + ), + ] diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 1ed98515a..50e46bd6f 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -5,6 +5,7 @@ from django.db import models from model_utils.managers import InheritanceManager from bookwyrm import activitypub +from bookwyrm.utils.fields import ArrayField from .base_model import ActivitypubMixin, OrderedCollectionPageMixin from .base_model import ActivityMapping, BookWyrmModel, PrivacyLevels from .base_model import tag_formatter @@ -17,6 +18,10 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): mention_users = models.ManyToManyField('User', related_name='mention_user') mention_books = models.ManyToManyField( 'Edition', related_name='mention_book') + images = ArrayField( + models.ImageField(upload_to='status/'), + default=list + ) local = models.BooleanField(default=True) privacy = models.CharField( max_length=255, diff --git a/bookwyrm/status.py b/bookwyrm/status.py index 4baaed6d3..c950f4ab3 100644 --- a/bookwyrm/status.py +++ b/bookwyrm/status.py @@ -13,7 +13,7 @@ def delete_status(status): def create_status(activity): - ''' unfortunately, it's not QUITE as simple as deserialiing it ''' + ''' unfortunately, it's not QUITE as simple as deserializing it ''' # render the json into an activity object serializer = activitypub.activity_objects[activity['type']] activity = serializer(**activity) From 5526b4773e94946e94adc066d4bc3ca1843981d5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 Nov 2020 12:56:05 -0800 Subject: [PATCH 02/12] Formatter for converting model images to AP Images Replaces reduntant properties on user and book models --- bookwyrm/models/base_model.py | 22 ++++++++++++++++++++++ bookwyrm/models/book.py | 15 +++++---------- bookwyrm/models/user.py | 19 ++++++------------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index df6b4f755..2883e97ad 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -261,3 +261,25 @@ def tag_formatter(items, name_field, activity_type): type=activity_type )) return tags + + +def image_formatter(image, default_path=None): + ''' convert images into activitypub json ''' + if image: + url = image.url + elif default_path: + url = default_path + else: + return None + url = 'https://%s%s' % (DOMAIN, url) + return activitypub.Image(url=url) + + +def image_attachments_formatter(images): + ''' create a list of image attachemnts ''' + if not isinstance(images, list): + images = [images] + attachments = [] + for image in images: + attachments.append(image_formatter(image)) + return attachments diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index d0702a3e1..c4981a540 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -12,6 +12,7 @@ from bookwyrm.utils.fields import ArrayField from .base_model import ActivityMapping, BookWyrmModel from .base_model import ActivitypubMixin, OrderedCollectionPageMixin +from .base_model import image_attachments_formatter class Book(ActivitypubMixin, BookWyrmModel): ''' a generic book, which can mean either an edition or a work ''' @@ -60,15 +61,6 @@ class Book(ActivitypubMixin, BookWyrmModel): ''' the activitypub serialization should be a list of author ids ''' return [a.remote_id for a in self.authors.all()] - @property - def ap_cover(self): - ''' an image attachment ''' - if not self.cover or not hasattr(self.cover, 'url'): - return [] - return [activitypub.Image( - url='https://%s%s' % (DOMAIN, self.cover.url), - )] - @property def ap_parent_work(self): ''' reference the work via local id not remote ''' @@ -106,7 +98,10 @@ class Book(ActivitypubMixin, BookWyrmModel): ActivityMapping('lccn', 'lccn'), ActivityMapping('editions', 'editions_path'), - ActivityMapping('attachment', 'ap_cover'), + ActivityMapping( + 'attachment', 'cover', + image_attachments_formatter + ), ] def save(self, *args, **kwargs): diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 0c7d1a182..2bea373ba 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -10,8 +10,8 @@ from bookwyrm.models.shelf import Shelf from bookwyrm.models.status import Status from bookwyrm.settings import DOMAIN from bookwyrm.signatures import create_key_pair -from .base_model import OrderedCollectionPageMixin -from .base_model import ActivityMapping +from .base_model import ActivityMapping, OrderedCollectionPageMixin +from .base_model import image_formatter class User(OrderedCollectionPageMixin, AbstractUser): @@ -78,16 +78,6 @@ class User(OrderedCollectionPageMixin, AbstractUser): ''' generates url for activitypub followers page ''' return '%s/followers' % self.remote_id - @property - def ap_icon(self): - ''' send default icon if one isn't set ''' - if self.avatar: - url = self.avatar.url - else: - url = '/static/images/default_avi.jpg' - url = 'https://%s%s' % (DOMAIN, url) - return activitypub.Image(url=url) - @property def ap_public_key(self): ''' format the public key block for activitypub ''' @@ -122,7 +112,10 @@ class User(OrderedCollectionPageMixin, AbstractUser): activity_formatter=lambda x: {'sharedInbox': x}, model_formatter=lambda x: x.get('sharedInbox') ), - ActivityMapping('icon', 'ap_icon'), + ActivityMapping( + 'icon', 'avatar', + lambda x: image_formatter(x, '/static/images/default_avi.jpg') + ), ActivityMapping( 'manuallyApprovesFollowers', 'manually_approves_followers' From e2debd855c328d35d84ad305b3950f0ab52147bb Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 Nov 2020 13:43:46 -0800 Subject: [PATCH 03/12] Convert activitypub Image into model ImageField --- bookwyrm/activitypub/__init__.py | 3 ++- bookwyrm/activitypub/base_activity.py | 37 ++++++++++++++++++++++++--- bookwyrm/models/user.py | 3 ++- bookwyrm/remote_user.py | 20 --------------- 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index 9a32bfc96..bd1a02662 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -4,7 +4,8 @@ import sys from .base_activity import ActivityEncoder, Image, PublicKey, Signature from .base_activity import Link, Mention -from .base_activity import ActivitySerializerError, tag_formatter +from .base_activity import ActivitySerializerError +from .base_activity import tag_formatter, image_formatter from .note import Note, GeneratedNote, Article, Comment, Review, Quotation from .note import Tombstone from .interaction import Boost, Like diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 18e5ccbe4..65752105d 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -1,11 +1,15 @@ ''' basics for an activitypub serializer ''' from dataclasses import dataclass, fields, MISSING from json import JSONEncoder +from uuid import uuid4 -from bookwyrm import books_manager, models - +from django.core.files.base import ContentFile from django.db.models.fields.related_descriptors \ import ForwardManyToOneDescriptor, ManyToManyDescriptor +from django.db.models.fields.files import ImageFileDescriptor +import requests + +from bookwyrm import books_manager, models class ActivitySerializerError(ValueError): @@ -91,6 +95,7 @@ class ActivityObject: model_fields = [m.name for m in model._meta.get_fields()] mapped_fields = {} many_to_many_fields = {} + image_fields = {} for mapping in model.activity_mappings: if mapping.model_key not in model_fields: @@ -110,12 +115,13 @@ class ActivityObject: formatted_value = mapping.model_formatter(value) if isinstance(model_field, ManyToManyDescriptor): many_to_many_fields[mapping.model_key] = formatted_value + elif isinstance(model_field, ImageFileDescriptor): + image_fields[mapping.model_key] = formatted_value else: mapped_fields[mapping.model_key] = formatted_value - - # updating an existing model isntance if instance: + # updating an existing model isntance for k, v in mapped_fields.items(): setattr(instance, k, v) instance.save() @@ -123,9 +129,14 @@ class ActivityObject: # creating a new model instance instance = model.objects.create(**mapped_fields) + # add many-to-many fields for (model_key, values) in many_to_many_fields.items(): getattr(instance, model_key).set(values) instance.save() + + # add images + for (model_key, value) in image_fields.items(): + getattr(instance, model_key).save(*value, save=True) return instance @@ -174,3 +185,21 @@ def tag_formatter(tags): continue items.append(item) return items + + +def image_formatter(image_json): + ''' helper function to load images and format them for a model ''' + url = image_json.get('url') + if not url: + return None + + try: + response = requests.get(url) + except ConnectionError: + return None + if not response.ok: + return None + + image_name = str(uuid4()) + '.' + url.split('.')[-1] + image_content = ContentFile(response.content) + return [image_name, image_content] diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 2bea373ba..b38a4b192 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -114,7 +114,8 @@ class User(OrderedCollectionPageMixin, AbstractUser): ), ActivityMapping( 'icon', 'avatar', - lambda x: image_formatter(x, '/static/images/default_avi.jpg') + lambda x: image_formatter(x, '/static/images/default_avi.jpg'), + activitypub.image_formatter ), ActivityMapping( 'manuallyApprovesFollowers', diff --git a/bookwyrm/remote_user.py b/bookwyrm/remote_user.py index 9178451b4..9aa6079ec 100644 --- a/bookwyrm/remote_user.py +++ b/bookwyrm/remote_user.py @@ -25,11 +25,6 @@ def get_or_create_remote_user(actor): user = create_remote_user(data) user.federated_server = get_or_create_remote_server(actor_parts.netloc) user.save() - - avatar = get_avatar(data) - if avatar: - user.avatar.save(*avatar) - if user.bookwyrm_user: get_remote_reviews.delay(user.id) return user @@ -69,21 +64,6 @@ def refresh_remote_user(user): activity.to_model(models.User, instance=user) -def get_avatar(data): - ''' find the icon attachment and load the image from the remote sever ''' - icon_blob = data.get('icon') - if not icon_blob or not icon_blob.get('url'): - return None - - response = requests.get(icon_blob['url']) - if not response.ok: - return None - - image_name = str(uuid4()) + '.' + icon_blob['url'].split('.')[-1] - image_content = ContentFile(response.content) - return [image_name, image_content] - - @app.task def get_remote_reviews(user_id): ''' ingest reviews by a new remote bookwyrm user ''' From 17fca8181ba82f87d52ac75208579e23013a703d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 Nov 2020 13:50:14 -0800 Subject: [PATCH 04/12] Simplify user creation code --- bookwyrm/remote_user.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/bookwyrm/remote_user.py b/bookwyrm/remote_user.py index 9aa6079ec..23a805b38 100644 --- a/bookwyrm/remote_user.py +++ b/bookwyrm/remote_user.py @@ -1,9 +1,7 @@ ''' manage remote users ''' from urllib.parse import urlparse -from uuid import uuid4 import requests -from django.core.files.base import ContentFile from django.db import transaction from bookwyrm import activitypub, models @@ -22,7 +20,7 @@ def get_or_create_remote_user(actor): actor_parts = urlparse(actor) with transaction.atomic(): - user = create_remote_user(data) + user = activitypub.Person(**data).to_model(models.User) user.federated_server = get_or_create_remote_server(actor_parts.netloc) user.save() if user.bookwyrm_user: @@ -50,12 +48,6 @@ def fetch_user_data(actor): return data -def create_remote_user(data): - ''' parse the activitypub actor data into a user ''' - actor = activitypub.Person(**data) - return actor.to_model(models.User) - - def refresh_remote_user(user): ''' get updated user data from its home instance ''' data = fetch_user_data(user.remote_id) From 88e470571761eae9275d3bcbc262c2a378312f5b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 Nov 2020 20:42:05 -0800 Subject: [PATCH 05/12] Use attachment database table --- bookwyrm/migrations/0012_attachment.py | 28 +++++++++++++++++++++++ bookwyrm/migrations/0012_status_images.py | 19 --------------- bookwyrm/models/status.py | 15 ++++++++---- 3 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 bookwyrm/migrations/0012_attachment.py delete mode 100644 bookwyrm/migrations/0012_status_images.py diff --git a/bookwyrm/migrations/0012_attachment.py b/bookwyrm/migrations/0012_attachment.py new file mode 100644 index 000000000..47b2885b8 --- /dev/null +++ b/bookwyrm/migrations/0012_attachment.py @@ -0,0 +1,28 @@ +# Generated by Django 3.0.7 on 2020-11-24 00:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0011_auto_20201113_1727'), + ] + + operations = [ + migrations.CreateModel( + name='Attachment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('remote_id', models.CharField(max_length=255, null=True)), + ('image', models.ImageField(blank=True, null=True, upload_to='status/')), + ('status', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='bookwyrm.Status')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/bookwyrm/migrations/0012_status_images.py b/bookwyrm/migrations/0012_status_images.py deleted file mode 100644 index 3b440bfd9..000000000 --- a/bookwyrm/migrations/0012_status_images.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.0.7 on 2020-11-23 20:44 - -import bookwyrm.utils.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('bookwyrm', '0011_auto_20201113_1727'), - ] - - operations = [ - migrations.AddField( - model_name='status', - name='images', - field=bookwyrm.utils.fields.ArrayField(base_field=models.ImageField(upload_to='status/'), default=list, size=None), - ), - ] diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 50e46bd6f..4136e0853 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -5,7 +5,6 @@ from django.db import models from model_utils.managers import InheritanceManager from bookwyrm import activitypub -from bookwyrm.utils.fields import ArrayField from .base_model import ActivitypubMixin, OrderedCollectionPageMixin from .base_model import ActivityMapping, BookWyrmModel, PrivacyLevels from .base_model import tag_formatter @@ -18,10 +17,6 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): mention_users = models.ManyToManyField('User', related_name='mention_user') mention_books = models.ManyToManyField( 'Edition', related_name='mention_book') - images = ArrayField( - models.ImageField(upload_to='status/'), - default=list - ) local = models.BooleanField(default=True) privacy = models.CharField( max_length=255, @@ -150,6 +145,16 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): super().save(*args, **kwargs) +class Attachment(BookWyrmModel): + ''' an image (or, in the future, video etc) associated with a status ''' + status = models.ForeignKey( + 'Status', + on_delete=models.CASCADE, + related_name='items' + ) + image = models.ImageField(upload_to='status/', null=True, blank=True) + + class GeneratedNote(Status): ''' these are app-generated messages about user activity ''' @property From ad7ce6595b59dda1d8f6fb658fed25682152a4ec Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 24 Nov 2020 11:25:07 -0800 Subject: [PATCH 06/12] Receive and save incoming images --- bookwyrm/activitypub/__init__.py | 3 +- bookwyrm/activitypub/base_activity.py | 49 ++++++++++++++++++++------ bookwyrm/migrations/0012_attachment.py | 4 +-- bookwyrm/models/__init__.py | 2 +- bookwyrm/models/base_model.py | 9 ++--- bookwyrm/models/book.py | 4 ++- bookwyrm/models/status.py | 20 +++++++---- 7 files changed, 62 insertions(+), 29 deletions(-) diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index bd1a02662..852db345c 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -5,7 +5,8 @@ import sys from .base_activity import ActivityEncoder, Image, PublicKey, Signature from .base_activity import Link, Mention from .base_activity import ActivitySerializerError -from .base_activity import tag_formatter, image_formatter +from .base_activity import tag_formatter +from .base_activity import image_formatter, image_attachments_formatter from .note import Note, GeneratedNote, Article, Comment, Review, Quotation from .note import Tombstone from .interaction import Boost, Like diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 65752105d..f6a3f9b1e 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -5,7 +5,8 @@ from uuid import uuid4 from django.core.files.base import ContentFile from django.db.models.fields.related_descriptors \ - import ForwardManyToOneDescriptor, ManyToManyDescriptor + import ForwardManyToOneDescriptor, ManyToManyDescriptor, \ + ReverseManyToOneDescriptor from django.db.models.fields.files import ImageFileDescriptor import requests @@ -95,6 +96,7 @@ class ActivityObject: model_fields = [m.name for m in model._meta.get_fields()] mapped_fields = {} many_to_many_fields = {} + one_to_many_fields = {} image_fields = {} for mapping in model.activity_mappings: @@ -107,15 +109,20 @@ class ActivityObject: value = getattr(self, mapping.activity_key) model_field = getattr(model, mapping.model_key) - # remote_id -> foreign key resolver - if isinstance(model_field, ForwardManyToOneDescriptor) and value: - fk_model = model_field.field.related_model - value = resolve_foreign_key(fk_model, value) - formatted_value = mapping.model_formatter(value) - if isinstance(model_field, ManyToManyDescriptor): + if isinstance(model_field, ForwardManyToOneDescriptor) and \ + formatted_value: + # foreign key remote id reolver + fk_model = model_field.field.related_model + reference = resolve_foreign_key(fk_model, formatted_value) + mapped_fields[mapping.model_key] = reference + elif isinstance(model_field, ManyToManyDescriptor): many_to_many_fields[mapping.model_key] = formatted_value + elif isinstance(model_field, ReverseManyToOneDescriptor): + # attachments on statuses, for example + one_to_many_fields[mapping.model_key] = formatted_value elif isinstance(model_field, ImageFileDescriptor): + # image fields need custom handling image_fields[mapping.model_key] = formatted_value else: mapped_fields[mapping.model_key] = formatted_value @@ -137,6 +144,18 @@ class ActivityObject: # add images for (model_key, value) in image_fields.items(): getattr(instance, model_key).save(*value, save=True) + + # add one to many fields + for (model_key, values) in one_to_many_fields.items(): + items = [] + for item in values: + # the reference id wasn't available at creation time + setattr(item, instance.__class__.__name__.lower(), instance) + item.save() + items.append(item) + if items: + getattr(instance, model_key).set(items) + instance.save() return instance @@ -167,15 +186,14 @@ def resolve_foreign_key(model, remote_id): return result -def tag_formatter(tags): +def tag_formatter(tags, tag_type): ''' helper function to extract foreign keys from tag activity json ''' items = [] types = { 'Book': models.Book, 'Mention': models.User, } - for tag in tags: - tag_type = tag.get('type') + for tag in [t for t in tags if t.get('type') == tag_type]: if not tag_type in types: continue remote_id = tag.get('href') @@ -203,3 +221,14 @@ def image_formatter(image_json): image_name = str(uuid4()) + '.' + url.split('.')[-1] image_content = ContentFile(response.content) return [image_name, image_content] + + +def image_attachments_formatter(images_json): + ''' deserialize a list of images ''' + attachments = [] + for image in images_json: + attachment = models.Attachment() + image_field = image_formatter(image) + attachment.image.save(*image_field, save=False) + attachments.append(attachment) + return attachments diff --git a/bookwyrm/migrations/0012_attachment.py b/bookwyrm/migrations/0012_attachment.py index 47b2885b8..2af545a86 100644 --- a/bookwyrm/migrations/0012_attachment.py +++ b/bookwyrm/migrations/0012_attachment.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.7 on 2020-11-24 00:41 +# Generated by Django 3.0.7 on 2020-11-24 17:46 from django.db import migrations, models import django.db.models.deletion @@ -19,7 +19,7 @@ class Migration(migrations.Migration): ('updated_date', models.DateTimeField(auto_now=True)), ('remote_id', models.CharField(max_length=255, null=True)), ('image', models.ImageField(blank=True, null=True, upload_to='status/')), - ('status', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='bookwyrm.Status')), + ('status', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='bookwyrm.Status')), ], options={ 'abstract': False, diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index d147197d2..8a93b805a 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -7,7 +7,7 @@ from .connector import Connector from .relationship import UserFollows, UserFollowRequest, UserBlocks from .shelf import Shelf, ShelfBook from .status import Status, GeneratedNote, Review, Comment, Quotation -from .status import Favorite, Boost, Notification, ReadThrough +from .status import Attachment, Favorite, Boost, Notification, ReadThrough from .tag import Tag from .user import User from .federated_server import FederatedServer diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index 2883e97ad..e56d21f66 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -276,10 +276,5 @@ def image_formatter(image, default_path=None): def image_attachments_formatter(images): - ''' create a list of image attachemnts ''' - if not isinstance(images, list): - images = [images] - attachments = [] - for image in images: - attachments.append(image_formatter(image)) - return attachments + ''' create a list of image attachments ''' + return [image_formatter(i) for i in images] diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index c4981a540..6edfece80 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -100,7 +100,9 @@ class Book(ActivitypubMixin, BookWyrmModel): ActivityMapping('editions', 'editions_path'), ActivityMapping( 'attachment', 'cover', - image_attachments_formatter + # this expects an iterable and the field is just an image + lambda x: image_attachments_formatter([x]), + lambda x: activitypub.image_attachments_formatter(x)[0] ), ] diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 4136e0853..dcf4c4715 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -7,7 +7,7 @@ from model_utils.managers import InheritanceManager from bookwyrm import activitypub from .base_model import ActivitypubMixin, OrderedCollectionPageMixin from .base_model import ActivityMapping, BookWyrmModel, PrivacyLevels -from .base_model import tag_formatter +from .base_model import tag_formatter, image_attachments_formatter class Status(OrderedCollectionPageMixin, BookWyrmModel): @@ -80,13 +80,18 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): ActivityMapping( 'tag', 'mention_books', lambda x: tag_formatter(x, 'title', 'Book'), - activitypub.tag_formatter + lambda x: activitypub.tag_formatter(x, 'Book') ), ActivityMapping( 'tag', 'mention_users', lambda x: tag_formatter(x, 'username', 'Mention'), - activitypub.tag_formatter + lambda x: activitypub.tag_formatter(x, 'Mention') ), + ActivityMapping( + 'attachment', 'attachments', + lambda x: image_attachments_formatter(x.all()), + activitypub.image_attachments_formatter + ) ] # serializing to bookwyrm expanded activitypub @@ -140,9 +145,10 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): def save(self, *args, **kwargs): ''' update user active time ''' - self.user.last_active_date = timezone.now() - self.user.save() - super().save(*args, **kwargs) + if self.user.local: + self.user.last_active_date = timezone.now() + self.user.save() + return super().save(*args, **kwargs) class Attachment(BookWyrmModel): @@ -150,7 +156,7 @@ class Attachment(BookWyrmModel): status = models.ForeignKey( 'Status', on_delete=models.CASCADE, - related_name='items' + related_name='attachments' ) image = models.ImageField(upload_to='status/', null=True, blank=True) From 45a0bd79fd0f45af03732b0b1c525a18853e79f5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 24 Nov 2020 11:28:17 -0800 Subject: [PATCH 07/12] Very basic image display --- bookwyrm/templates/snippets/status_content.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bookwyrm/templates/snippets/status_content.html b/bookwyrm/templates/snippets/status_content.html index d597c7072..eac372711 100644 --- a/bookwyrm/templates/snippets/status_content.html +++ b/bookwyrm/templates/snippets/status_content.html @@ -18,6 +18,13 @@ {% if status.content and status.status_type != 'GeneratedNote' and status.status_type != 'Boost' %} {% include 'snippets/trimmed_text.html' with full=status.content|safe %} {% endif %} + {% if status.attachments %} +
+ {% for attachment in status.attachments.all %} + + {% endfor %} +
+ {% endif %} {% if not hide_book %} From ff5a0f7bc20078bb9339b5e18a5fd0e234f94a9d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 24 Nov 2020 11:44:13 -0800 Subject: [PATCH 08/12] Adds alt text to status images --- bookwyrm/activitypub/base_activity.py | 3 ++- bookwyrm/migrations/0012_attachment.py | 3 ++- bookwyrm/models/status.py | 1 + bookwyrm/templates/snippets/status_content.html | 2 +- bw-dev | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index f6a3f9b1e..62fce70b2 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -227,7 +227,8 @@ def image_attachments_formatter(images_json): ''' deserialize a list of images ''' attachments = [] for image in images_json: - attachment = models.Attachment() + caption = image.get('name') + attachment = models.Attachment(caption=caption) image_field = image_formatter(image) attachment.image.save(*image_field, save=False) attachments.append(attachment) diff --git a/bookwyrm/migrations/0012_attachment.py b/bookwyrm/migrations/0012_attachment.py index 2af545a86..495538517 100644 --- a/bookwyrm/migrations/0012_attachment.py +++ b/bookwyrm/migrations/0012_attachment.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.7 on 2020-11-24 17:46 +# Generated by Django 3.0.7 on 2020-11-24 19:39 from django.db import migrations, models import django.db.models.deletion @@ -19,6 +19,7 @@ class Migration(migrations.Migration): ('updated_date', models.DateTimeField(auto_now=True)), ('remote_id', models.CharField(max_length=255, null=True)), ('image', models.ImageField(blank=True, null=True, upload_to='status/')), + ('caption', models.TextField(blank=True, null=True)), ('status', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='bookwyrm.Status')), ], options={ diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index dcf4c4715..6f534f506 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -159,6 +159,7 @@ class Attachment(BookWyrmModel): related_name='attachments' ) image = models.ImageField(upload_to='status/', null=True, blank=True) + caption = models.TextField(null=True, blank=True) class GeneratedNote(Status): diff --git a/bookwyrm/templates/snippets/status_content.html b/bookwyrm/templates/snippets/status_content.html index eac372711..834549ea2 100644 --- a/bookwyrm/templates/snippets/status_content.html +++ b/bookwyrm/templates/snippets/status_content.html @@ -21,7 +21,7 @@ {% if status.attachments %}
{% for attachment in status.attachments.all %} - + {{ attachment.caption }} {% endfor %}
{% endif %} diff --git a/bw-dev b/bw-dev index 22ca9412e..6d74924ff 100755 --- a/bw-dev +++ b/bw-dev @@ -61,7 +61,7 @@ case "$1" in ;; migrate) execweb python manage.py rename_app fedireads bookwyrm - execweb python manage.py migrate + execweb python manage.py "$@" ;; bash) execweb bash From 6cc1e241fe488b721b91cf7af6abeed807820007 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 24 Nov 2020 12:07:00 -0800 Subject: [PATCH 09/12] display images at a reasonable size --- bookwyrm/static/css/format.css | 3 +++ bookwyrm/templates/snippets/status_content.html | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/format.css index db3c20ef3..9e8a24ba9 100644 --- a/bookwyrm/static/css/format.css +++ b/bookwyrm/static/css/format.css @@ -1,4 +1,7 @@ /* --- --- */ +.image { + overflow: hidden; +} .navbar .logo { max-height: 50px; } diff --git a/bookwyrm/templates/snippets/status_content.html b/bookwyrm/templates/snippets/status_content.html index 834549ea2..e71588ad1 100644 --- a/bookwyrm/templates/snippets/status_content.html +++ b/bookwyrm/templates/snippets/status_content.html @@ -19,10 +19,18 @@ {% include 'snippets/trimmed_text.html' with full=status.content|safe %} {% endif %} {% if status.attachments %} -
- {% for attachment in status.attachments.all %} - {{ attachment.caption }} - {% endfor %} +
+
+ {% for attachment in status.attachments.all %} +
+
+ + {{ attachment.caption }} + +
+
+ {% endfor %} +
{% endif %}
From 9bbedc5d9af7f2f956bf4947002efa28b19c862d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 24 Nov 2020 13:25:28 -0800 Subject: [PATCH 10/12] Fixes field causing form invalidation when editing books --- bookwyrm/migrations/0012_auto_20201124_2125.py | 18 ++++++++++++++++++ bookwyrm/models/book.py | 2 +- bookwyrm/templates/edit_book.html | 5 +++++ bookwyrm/view_actions.py | 8 +++++++- 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 bookwyrm/migrations/0012_auto_20201124_2125.py diff --git a/bookwyrm/migrations/0012_auto_20201124_2125.py b/bookwyrm/migrations/0012_auto_20201124_2125.py new file mode 100644 index 000000000..bbc4942a5 --- /dev/null +++ b/bookwyrm/migrations/0012_auto_20201124_2125.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-11-24 21:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0011_auto_20201113_1727'), + ] + + operations = [ + migrations.AlterField( + model_name='book', + name='origin_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index d0702a3e1..4bee8c6ca 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -15,7 +15,7 @@ from .base_model import ActivitypubMixin, OrderedCollectionPageMixin class Book(ActivitypubMixin, BookWyrmModel): ''' a generic book, which can mean either an edition or a work ''' - origin_id = models.CharField(max_length=255, null=True) + origin_id = models.CharField(max_length=255, null=True, blank=True) # these identifiers apply to both works and editions openlibrary_key = models.CharField(max_length=255, blank=True, null=True) librarything_key = models.CharField(max_length=255, blank=True, null=True) diff --git a/bookwyrm/templates/edit_book.html b/bookwyrm/templates/edit_book.html index bd89ac0a9..ee1be6979 100644 --- a/bookwyrm/templates/edit_book.html +++ b/bookwyrm/templates/edit_book.html @@ -20,6 +20,11 @@ +{% if login_form.non_field_errors %} +
+

{{ login_form.non_field_errors }}

+
+{% endif %}
{% csrf_token %}
diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index b7e92c89f..5c5ffa4f5 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -235,7 +235,13 @@ def edit_book(request, book_id): form = forms.EditionForm(request.POST, request.FILES, instance=book) if not form.is_valid(): - return redirect(request.headers.get('Referer', '/')) + data = { + 'title': 'Edit Book', + 'book': book, + 'form': form + } + print(form.errors) + return TemplateResponse(request, 'edit_book.html', data) form.save() outgoing.handle_update_book(request.user, book) From 1fd498c30682257ced8104a0507bdcfafae99d2f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 24 Nov 2020 13:37:35 -0800 Subject: [PATCH 11/12] Display error messages in edit book form --- bookwyrm/templates/edit_book.html | 91 ++++++++++++++++++++++++------- bookwyrm/view_actions.py | 1 - 2 files changed, 72 insertions(+), 20 deletions(-) diff --git a/bookwyrm/templates/edit_book.html b/bookwyrm/templates/edit_book.html index ee1be6979..54cefb0a7 100644 --- a/bookwyrm/templates/edit_book.html +++ b/bookwyrm/templates/edit_book.html @@ -25,6 +25,7 @@

{{ login_form.non_field_errors }}

{% endif %} + {% csrf_token %}
@@ -42,13 +43,40 @@
-
-

Book Identifiers

-

{{ form.isbn_13 }}

-

{{ form.isbn_10 }}

-

{{ form.openlibrary_key }}

-

{{ form.librarything_key }}

-

{{ form.goodreads_key }}

+
+

Metadata

+

{{ form.title }}

+ {% for error in form.title.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.sort_title }}

+ {% for error in form.sort_title.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.subtitle }}

+ {% for error in form.subtitle.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.description }}

+ {% for error in form.description.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.series }}

+ {% for error in form.series.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.series_number }}

+ {% for error in form.series_number.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.first_published_date }}

+ {% for error in form.first_published_date.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.published_date }}

+ {% for error in form.published_date.errors %} +

{{ error | escape }}

+ {% endfor %}
@@ -60,6 +88,9 @@

Cover

{{ form.cover }}

+ {% for error in form.cover.errors %} +

{{ error | escape }}

+ {% endfor %}
@@ -67,22 +98,45 @@

Physical Properties

{{ form.physical_format }}

+ {% for error in form.physical_format.errors %} +

{{ error | escape }}

+ {% endfor %} + {% for error in form.physical_format.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.pages }}

+ {% for error in form.pages.errors %} +

{{ error | escape }}

+ {% endfor %} +
+ +
+

Book Identifiers

+

{{ form.isbn_13 }}

+ {% for error in form.isbn_13.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.isbn_10 }}

+ {% for error in form.isbn_10.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.openlibrary_key }}

+ {% for error in form.openlibrary_key.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.librarything_key }}

+ {% for error in form.librarything_key.errors %} +

{{ error | escape }}

+ {% endfor %} +

{{ form.goodreads_key }}

+ {% for error in form.goodreads_key.errors %} +

{{ error | escape }}

+ {% endfor %}
-
-

Metadata

-

{{ form.title }}

-

{{ form.sort_title }}

-

{{ form.subtitle }}

-

{{ form.description }}

-

{{ form.series }}

-

{{ form.series_number }}

-

{{ form.first_published_date }}

-

{{ form.published_date }}

-
Cancel @@ -90,4 +144,3 @@ {% endblock %} - diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index 5c5ffa4f5..ad9a12cf7 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -240,7 +240,6 @@ def edit_book(request, book_id): 'book': book, 'form': form } - print(form.errors) return TemplateResponse(request, 'edit_book.html', data) form.save() From 7b4847852a51b4ae0d0d3ea6320bb8dc7a125681 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 24 Nov 2020 13:42:47 -0800 Subject: [PATCH 12/12] Merge migrations manually --- .../{0012_auto_20201124_2125.py => 0013_book_origin_id.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename bookwyrm/migrations/{0012_auto_20201124_2125.py => 0013_book_origin_id.py} (76%) diff --git a/bookwyrm/migrations/0012_auto_20201124_2125.py b/bookwyrm/migrations/0013_book_origin_id.py similarity index 76% rename from bookwyrm/migrations/0012_auto_20201124_2125.py rename to bookwyrm/migrations/0013_book_origin_id.py index bbc4942a5..581a2406e 100644 --- a/bookwyrm/migrations/0012_auto_20201124_2125.py +++ b/bookwyrm/migrations/0013_book_origin_id.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.7 on 2020-11-24 21:25 +# Generated by Django 3.0.7 on 2020-11-24 21:42 from django.db import migrations, models @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0011_auto_20201113_1727'), + ('bookwyrm', '0012_attachment'), ] operations = [