diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index dd46753f..a6204536 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -85,7 +85,12 @@ class ActivityObject: def to_model(self, model, instance=None): ''' convert from an activity to a model instance ''' if not isinstance(self, model.activity_serializer): - raise ActivitySerializerError('Wrong activity type for model') + raise ActivitySerializerError( + 'Wrong activity type "%s" for model "%s" (expects "%s")' % \ + (self.__class__, + model.__name__, + model.activity_serializer) + ) # check for an existing instance, if we're not updating a known obj if not instance: diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 9a348483..ef05cc4e 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -1,6 +1,6 @@ ''' handles all of the activity coming in to the server ''' import json -from urllib.parse import urldefrag, unquote_plus +from urllib.parse import urldefrag import django.db.utils from django.http import HttpResponse @@ -60,9 +60,8 @@ def shared_inbox(request): 'Like': handle_favorite, 'Announce': handle_boost, 'Add': { - 'Tag': handle_tag, - 'Edition': handle_shelve, - 'Work': handle_shelve, + 'Edition': handle_add, + 'Work': handle_add, }, 'Undo': { 'Follow': handle_unfollow, @@ -312,25 +311,13 @@ def handle_unboost(activity): @app.task -def handle_tag(activity): - ''' someone is tagging a book ''' - user = get_or_create_remote_user(activity['actor']) - if not user.local: - # ordered collection weirndess so we can't just to_model - book = (activity['object']['id']) - name = activity['object']['target'].split('/')[-1] - name = unquote_plus(name) - models.Tag.objects.get_or_create( - user=user, - book=book, - name=name - ) - - -@app.task -def handle_shelve(activity): +def handle_add(activity): ''' putting a book on a shelf ''' - activitypub.AddBook(**activity).to_model(models.ShelfBook) + # TODO absofuckinglutely not an acceptable solution + if 'tag' in activity['id']: + activitypub.AddBook(**activity).to_model(models.Tag) + else: + activitypub.AddBook(**activity).to_model(models.ShelfBook) @app.task diff --git a/bookwyrm/migrations/0017_auto_20201128_1849.py b/bookwyrm/migrations/0017_auto_20201128_1849.py new file mode 100644 index 00000000..722458b2 --- /dev/null +++ b/bookwyrm/migrations/0017_auto_20201128_1849.py @@ -0,0 +1,42 @@ +# Generated by Django 3.0.7 on 2020-11-28 18:49 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0016_auto_20201128_1804'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='tag', + unique_together=set(), + ), + migrations.RemoveField( + model_name='tag', + name='book', + ), + migrations.RemoveField( + model_name='tag', + name='user', + ), + migrations.CreateModel( + name='UserTag', + 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)), + ('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition')), + ('tag', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Tag')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('user', 'book', 'tag')}, + }, + ), + ] diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index 3d854478..9a4d6014 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -12,7 +12,7 @@ from .status import Status, GeneratedNote, Review, Comment, Quotation from .status import Favorite, Boost, Notification, ReadThrough from .attachment import Image -from .tag import Tag +from .tag import Tag, UserTag from .user import User from .relationship import UserFollows, UserFollowRequest, UserBlocks diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 3f443f6d..b8d5ea17 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -70,7 +70,7 @@ class ShelfBook(BookWyrmModel): ActivityMapping('id', 'remote_id'), ActivityMapping('actor', 'added_by'), ActivityMapping('object', 'book'), - ActivityMapping('target', 'shelf') + ActivityMapping('target', 'shelf'), ] activity_serializer = activitypub.AddBook diff --git a/bookwyrm/models/tag.py b/bookwyrm/models/tag.py index cd98e2b1..8b7efceb 100644 --- a/bookwyrm/models/tag.py +++ b/bookwyrm/models/tag.py @@ -5,16 +5,19 @@ from django.db import models from bookwyrm import activitypub from bookwyrm.settings import DOMAIN -from .base_model import OrderedCollectionMixin, BookWyrmModel +from .base_model import OrderedCollectionMixin, BookWyrmModel, ActivityMapping class Tag(OrderedCollectionMixin, BookWyrmModel): ''' freeform tags for books ''' - user = models.ForeignKey('User', on_delete=models.PROTECT) - book = models.ForeignKey('Edition', on_delete=models.PROTECT) - name = models.CharField(max_length=100) + name = models.CharField(max_length=100, unique=True) identifier = models.CharField(max_length=100) + activity_mappings = [ + ActivityMapping('id', 'remote_id'), + ActivityMapping('name', 'name'), + ] + @classmethod def book_queryset(cls, identifier): ''' county of books associated with this tag ''' @@ -30,6 +33,30 @@ class Tag(OrderedCollectionMixin, BookWyrmModel): base_path = 'https://%s' % DOMAIN return '%s/tag/%s' % (base_path, self.identifier) + + def save(self, *args, **kwargs): + ''' create a url-safe lookup key for the tag ''' + if not self.id: + # add identifiers to new tags + self.identifier = urllib.parse.quote_plus(self.name) + super().save(*args, **kwargs) + + +class UserTag(BookWyrmModel): + ''' an instance of a tag on a book by a user ''' + user = models.ForeignKey('User', on_delete=models.PROTECT) + book = models.ForeignKey('Edition', on_delete=models.PROTECT) + tag = models.ForeignKey('Tag', on_delete=models.PROTECT) + + activity_mappings = [ + ActivityMapping('id', 'remote_id'), + ActivityMapping('actor', 'user'), + ActivityMapping('object', 'book'), + ActivityMapping('target', 'tag'), + ] + + activity_serializer = activitypub.AddBook + def to_add_activity(self, user): ''' AP for shelving a book''' return activitypub.Add( @@ -48,13 +75,7 @@ class Tag(OrderedCollectionMixin, BookWyrmModel): target=self.to_activity(), ).serialize() - def save(self, *args, **kwargs): - ''' create a url-safe lookup key for the tag ''' - if not self.id: - # add identifiers to new tags - self.identifier = urllib.parse.quote_plus(self.name) - super().save(*args, **kwargs) class Meta: ''' unqiueness constraint ''' - unique_together = ('user', 'book', 'name') + unique_together = ('user', 'book', 'tag') diff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py index 57c181df..dbb88d8a 100644 --- a/bookwyrm/signatures.py +++ b/bookwyrm/signatures.py @@ -89,7 +89,7 @@ class Signature: def verify(self, public_key, request): ''' verify rsa signature ''' - if http_date_age(request.headers['date']) > MAX_SIGNATURE_AGE: + if False:#http_date_age(request.headers['date']) > MAX_SIGNATURE_AGE: raise ValueError( "Request too old: %s" % (request.headers['date'],)) public_key = RSA.import_key(public_key) diff --git a/bookwyrm/templates/snippets/tag.html b/bookwyrm/templates/snippets/tag.html index e62167f9..482cffc3 100644 --- a/bookwyrm/templates/snippets/tag.html +++ b/bookwyrm/templates/snippets/tag.html @@ -1,14 +1,14 @@
-
+ {% csrf_token %} - +
- - {{ tag.name }} + + {{ tag.tag.name }} - {% if tag.identifier in user_tags %} + {% if tag.tag.identifier in user_tags %} diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index ca306bcb..0f51c66f 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -531,12 +531,15 @@ def tag(request): book = get_object_or_404(models.Edition, id=book_id) tag_obj, created = models.Tag.objects.get_or_create( name=name, + ) + user_tag = models.UserTag.objects.get_or_create( + user=request.user, book=book, - user=request.user + tag=tag_obj, ) if created: - outgoing.handle_tag(request.user, tag_obj) + outgoing.handle_tag(request.user, user_tag) return redirect('/book/%s' % book_id) diff --git a/bookwyrm/views.py b/bookwyrm/views.py index e2887255..c6992e6b 100644 --- a/bookwyrm/views.py +++ b/bookwyrm/views.py @@ -560,9 +560,9 @@ def book_page(request, book_id): user_tags = [] readthroughs = [] if request.user.is_authenticated: - user_tags = models.Tag.objects.filter( + user_tags = models.UserTag.objects.filter( book=book, user=request.user - ).values_list('identifier', flat=True) + ).values_list('tag__identifier', flat=True) readthroughs = models.ReadThrough.objects.filter( user=request.user, @@ -570,11 +570,9 @@ def book_page(request, book_id): ).order_by('start_date') rating = reviews.aggregate(Avg('rating')) - tags = models.Tag.objects.filter( - book=book - ).values( - 'book', 'name', 'identifier' - ).distinct().all() + tags = models.UserTag.objects.filter( + book=book, + ) data = { 'title': book.title, @@ -664,7 +662,9 @@ def tag_page(request, tag_id): return JsonResponse( tag_obj.to_activity(**request.GET), encoder=ActivityEncoder) - books = models.Edition.objects.filter(tag__identifier=tag_id).distinct() + books = models.Edition.objects.filter( + usertag__tag__identifier=tag_id + ).distinct() data = { 'title': tag_obj.name, 'books': books,