From dab0aeffb2a3e9d500c43f484d453ba2b8064c67 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 Nov 2020 12:48:41 -0800 Subject: [PATCH 1/9] 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 00000000..3b440bfd --- /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 1ed98515..50e46bd6 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 4baaed6d..c950f4ab 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 2/9] 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 df6b4f75..2883e97a 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 d0702a3e..c4981a54 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 0c7d1a18..2bea373b 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 3/9] 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 9a32bfc9..bd1a0266 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 18e5ccbe..65752105 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 2bea373b..b38a4b19 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 9178451b..9aa6079e 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 4/9] 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 9aa6079e..23a805b3 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 5/9] 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 00000000..47b2885b --- /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 3b440bfd..00000000 --- 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 50e46bd6..4136e085 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 6/9] 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 bd1a0266..852db345 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 65752105..f6a3f9b1 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 47b2885b..2af545a8 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 d147197d..8a93b805 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 2883e97a..e56d21f6 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 c4981a54..6edfece8 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 4136e085..dcf4c471 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 7/9] 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 d597c707..eac37271 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 8/9] 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 f6a3f9b1..62fce70b 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 2af545a8..49553851 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 dcf4c471..6f534f50 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 eac37271..834549ea 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 22ca9412..6d74924f 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 9/9] 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 db3c20ef..9e8a24ba 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 834549ea..e71588ad 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 %}