diff --git a/.env.example b/.env.example
index e115c901..fe12d0ff 100644
--- a/.env.example
+++ b/.env.example
@@ -1,5 +1,5 @@
# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY=7(2w1sedok=aznpq)ta1mc4i%4h=xx@hxwx*o57ctsuml0x%fr
+SECRET_KEY="7(2w1sedok=aznpq)ta1mc4i%4h=xx@hxwx*o57ctsuml0x%fr"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG=false
@@ -27,7 +27,7 @@ REDIS_PASSWORD=redispassword123
CELERY_BROKER=redis://:${REDIS_PASSWORD}@redis:6379/0
CELERY_RESULT_BACKEND=redis://:${REDIS_PASSWORD}@redis:6379/0
-EMAIL_HOST='smtp.mailgun.org'
+EMAIL_HOST="smtp.mailgun.org"
EMAIL_PORT=587
EMAIL_HOST_USER=mail@your.domain.here
EMAIL_HOST_PASSWORD=emailpassword123
diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py
index 931af0a0..9a32bfc9 100644
--- a/bookwyrm/activitypub/__init__.py
+++ b/bookwyrm/activitypub/__init__.py
@@ -4,7 +4,7 @@ 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 ActivitySerializerError, tag_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 6ae7883e..18e5ccbe 100644
--- a/bookwyrm/activitypub/base_activity.py
+++ b/bookwyrm/activitypub/base_activity.py
@@ -5,7 +5,7 @@ from json import JSONEncoder
from bookwyrm import books_manager, models
from django.db.models.fields.related_descriptors \
- import ForwardManyToOneDescriptor
+ import ForwardManyToOneDescriptor, ManyToManyDescriptor
class ActivitySerializerError(ValueError):
@@ -90,6 +90,7 @@ class ActivityObject:
model_fields = [m.name for m in model._meta.get_fields()]
mapped_fields = {}
+ many_to_many_fields = {}
for mapping in model.activity_mappings:
if mapping.model_key not in model_fields:
@@ -106,7 +107,11 @@ class ActivityObject:
fk_model = model_field.field.related_model
value = resolve_foreign_key(fk_model, value)
- mapped_fields[mapping.model_key] = mapping.model_formatter(value)
+ formatted_value = mapping.model_formatter(value)
+ if isinstance(model_field, ManyToManyDescriptor):
+ many_to_many_fields[mapping.model_key] = formatted_value
+ else:
+ mapped_fields[mapping.model_key] = formatted_value
# updating an existing model isntance
@@ -114,10 +119,14 @@ class ActivityObject:
for k, v in mapped_fields.items():
setattr(instance, k, v)
instance.save()
- return instance
+ else:
+ # creating a new model instance
+ instance = model.objects.create(**mapped_fields)
- # creating a new model instance
- return model.objects.create(**mapped_fields)
+ for (model_key, values) in many_to_many_fields.items():
+ getattr(instance, model_key).set(values)
+ instance.save()
+ return instance
def serialize(self):
@@ -129,7 +138,7 @@ class ActivityObject:
def resolve_foreign_key(model, remote_id):
''' look up the remote_id on an activity json field '''
- if model in [models.Edition, models.Work]:
+ if model in [models.Edition, models.Work, models.Book]:
return books_manager.get_or_create_book(remote_id)
result = model.objects
@@ -145,3 +154,23 @@ def resolve_foreign_key(model, remote_id):
'Could not resolve remote_id in %s model: %s' % \
(model.__name__, remote_id))
return result
+
+
+def tag_formatter(tags):
+ ''' 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')
+ if not tag_type in types:
+ continue
+ remote_id = tag.get('href')
+ try:
+ item = resolve_foreign_key(types[tag_type], remote_id)
+ except ActivitySerializerError:
+ continue
+ items.append(item)
+ return items
diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py
index c2c223bd..0e7c1856 100644
--- a/bookwyrm/incoming.py
+++ b/bookwyrm/incoming.py
@@ -253,7 +253,6 @@ def handle_delete_status(activity):
status_builder.delete_status(status)
-
@app.task
def handle_favorite(activity):
''' approval of your good good post '''
diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py
index 8150d650..df6b4f75 100644
--- a/bookwyrm/models/base_model.py
+++ b/bookwyrm/models/base_model.py
@@ -72,7 +72,14 @@ class ActivitypubMixin:
value = value.remote_id
if isinstance(value, datetime):
value = value.isoformat()
- fields[mapping.activity_key] = mapping.activity_formatter(value)
+ result = mapping.activity_formatter(value)
+ if mapping.activity_key in fields and \
+ isinstance(fields[mapping.activity_key], list):
+ # there are two database fields that map to the same AP list
+ # this happens in status, which combines user and book tags
+ fields[mapping.activity_key] += result
+ else:
+ fields[mapping.activity_key] = result
if pure:
return self.pure_activity_serializer(
@@ -242,3 +249,15 @@ class ActivityMapping:
model_key: str
activity_formatter: Callable = lambda x: x
model_formatter: Callable = lambda x: x
+
+
+def tag_formatter(items, name_field, activity_type):
+ ''' helper function to format lists of foreign keys into Tags '''
+ tags = []
+ for item in items.all():
+ tags.append(activitypub.Link(
+ href=item.remote_id,
+ name=getattr(item, name_field),
+ type=activity_type
+ ))
+ return tags
diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py
index 09ceda85..1ed98515 100644
--- a/bookwyrm/models/status.py
+++ b/bookwyrm/models/status.py
@@ -7,6 +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
class Status(OrderedCollectionPageMixin, BookWyrmModel):
@@ -57,24 +58,6 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
''' structured replies block '''
return self.to_replies()
- @property
- def ap_tag(self):
- ''' references to books and/or users '''
-
- tags = []
- for book in self.mention_books.all():
- tags.append(activitypub.Link(
- href=book.remote_id,
- name=book.title,
- type='Book'
- ))
- for user in self.mention_users.all():
- tags.append(activitypub.Mention(
- href=user.remote_id,
- name=user.username,
- ))
- return tags
-
@property
def ap_status_image(self):
''' attach a book cover, if relevent '''
@@ -94,7 +77,16 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
ActivityMapping('to', 'ap_to'),
ActivityMapping('cc', 'ap_cc'),
ActivityMapping('replies', 'ap_replies'),
- ActivityMapping('tag', 'ap_tag'),
+ ActivityMapping(
+ 'tag', 'mention_books',
+ lambda x: tag_formatter(x, 'title', 'Book'),
+ activitypub.tag_formatter
+ ),
+ ActivityMapping(
+ 'tag', 'mention_users',
+ lambda x: tag_formatter(x, 'username', 'Mention'),
+ activitypub.tag_formatter
+ ),
]
# serializing to bookwyrm expanded activitypub
diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py
index 46bc2514..5adf960f 100644
--- a/bookwyrm/settings.py
+++ b/bookwyrm/settings.py
@@ -40,6 +40,7 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
+ 'django_rename_app',
'bookwyrm',
'celery',
]
diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html
index 930ca943..4ae70a4b 100644
--- a/bookwyrm/templates/layout.html
+++ b/bookwyrm/templates/layout.html
@@ -25,14 +25,18 @@
-