From f7fb9dde99c56778bacd2473215454ca5d7db30e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 30 Mar 2020 14:12:18 -0700 Subject: [PATCH] Differentiate works and editions more clearly --- fedireads/connectors/openlibrary.py | 19 +++--- .../migrations/0027_auto_20200330_2042.py | 67 +++++++++++++++++++ fedireads/models/book.py | 20 ++++-- fedireads/models/shelf.py | 4 +- fedireads/models/status.py | 10 +-- fedireads/views.py | 19 ++++-- 6 files changed, 109 insertions(+), 30 deletions(-) create mode 100644 fedireads/migrations/0027_auto_20200330_2042.py diff --git a/fedireads/connectors/openlibrary.py b/fedireads/connectors/openlibrary.py index f33a8a4f9..d34167bda 100644 --- a/fedireads/connectors/openlibrary.py +++ b/fedireads/connectors/openlibrary.py @@ -76,7 +76,7 @@ class Connector(AbstractConnector): return self.update_from_data(book, data) - def update_from_data(self, book, data=None): + def update_from_data(self, book, data, work=None): ''' update a book from a json blob ''' mappings = { 'publish_date': ('published_date', get_date), @@ -98,12 +98,13 @@ class Connector(AbstractConnector): book.save() # 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 + if not work: + work = None + 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 if isinstance(book, models.Work): # load editions of a work @@ -128,7 +129,7 @@ class Connector(AbstractConnector): response = requests.get( '%s/works/%s/editions.json' % (self.url, work.openlibrary_key)) edition_data = response.json() - for data in edition_data.get('entries', []): + for data in edition_data.get('entries', [])[:5]: try: olkey = data['key'].split('/')[-1] except KeyError: @@ -140,7 +141,7 @@ class Connector(AbstractConnector): continue except ObjectDoesNotExist: book = models.Edition.objects.create(openlibrary_key=olkey) - self.update_from_data(book, data) + self.update_from_data(book, data, work=work) set_default_edition(work) diff --git a/fedireads/migrations/0027_auto_20200330_2042.py b/fedireads/migrations/0027_auto_20200330_2042.py new file mode 100644 index 000000000..4fcbe73cc --- /dev/null +++ b/fedireads/migrations/0027_auto_20200330_2042.py @@ -0,0 +1,67 @@ +# Generated by Django 3.0.3 on 2020-03-30 20:42 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('fedireads', '0026_auto_20200330_1943'), + ] + + operations = [ + migrations.RemoveField( + model_name='book', + name='parent_work', + ), + migrations.RemoveField( + model_name='book', + name='shelves', + ), + migrations.AddField( + model_name='edition', + name='parent_work', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='fedireads.Work'), + ), + migrations.AddField( + model_name='edition', + name='shelves', + field=models.ManyToManyField(through='fedireads.ShelfBook', to='fedireads.Shelf'), + ), + migrations.AlterField( + model_name='comment', + name='book', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fedireads.Edition'), + ), + migrations.AlterField( + model_name='notification', + name='related_book', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='fedireads.Edition'), + ), + migrations.AlterField( + model_name='review', + name='book', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fedireads.Edition'), + ), + migrations.AlterField( + model_name='shelf', + name='books', + field=models.ManyToManyField(through='fedireads.ShelfBook', to='fedireads.Edition'), + ), + migrations.AlterField( + model_name='shelfbook', + name='book', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fedireads.Edition'), + ), + migrations.AlterField( + model_name='status', + name='mention_books', + field=models.ManyToManyField(related_name='mention_book', to='fedireads.Edition'), + ), + migrations.AlterField( + model_name='tag', + name='book', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fedireads.Edition'), + ), + ] diff --git a/fedireads/models/book.py b/fedireads/models/book.py index 383098bc2..e209b6b6f 100644 --- a/fedireads/models/book.py +++ b/fedireads/models/book.py @@ -84,13 +84,6 @@ class Book(FedireadsModel): cover = models.ImageField(upload_to='covers/', blank=True, null=True) first_published_date = models.DateTimeField(blank=True, null=True) published_date = models.DateTimeField(blank=True, null=True) - shelves = models.ManyToManyField( - 'Shelf', - symmetrical=False, - through='ShelfBook', - through_fields=('book', 'shelf') - ) - parent_work = models.ForeignKey('Work', on_delete=models.PROTECT, null=True) objects = InheritanceManager() @property @@ -100,6 +93,12 @@ class Book(FedireadsModel): model_name = type(self).__name__.lower() return '%s/%s/%s' % (base_path, model_name, self.openlibrary_key) + def save(self, *args, **kwargs): + ''' can't be abstract for query reasons, but you shouldn't USE it ''' + if not isinstance(self, Edition) and not isinstance(self, Work): + raise ValueError('Books should be added as Editions or Works') + super().save(*args, **kwargs) + def __repr__(self): return "<{} key={!r} title={!r}>".format( self.__class__, @@ -125,6 +124,13 @@ class Edition(Book): publishers = ArrayField( models.CharField(max_length=255), blank=True, default=list ) + shelves = models.ManyToManyField( + 'Shelf', + symmetrical=False, + through='ShelfBook', + through_fields=('book', 'shelf') + ) + parent_work = models.ForeignKey('Work', on_delete=models.PROTECT, null=True) class Author(FedireadsModel): diff --git a/fedireads/models/shelf.py b/fedireads/models/shelf.py index cdd8701b1..32ea7273e 100644 --- a/fedireads/models/shelf.py +++ b/fedireads/models/shelf.py @@ -10,7 +10,7 @@ class Shelf(FedireadsModel): user = models.ForeignKey('User', on_delete=models.PROTECT) editable = models.BooleanField(default=True) books = models.ManyToManyField( - 'Book', + 'Edition', symmetrical=False, through='ShelfBook', through_fields=('shelf', 'book') @@ -29,7 +29,7 @@ class Shelf(FedireadsModel): class ShelfBook(FedireadsModel): # many to many join table for books and shelves - book = models.ForeignKey('Book', on_delete=models.PROTECT) + book = models.ForeignKey('Edition', on_delete=models.PROTECT) shelf = models.ForeignKey('Shelf', on_delete=models.PROTECT) added_by = models.ForeignKey( 'User', diff --git a/fedireads/models/status.py b/fedireads/models/status.py index 0bdec6d7b..b2d213ae2 100644 --- a/fedireads/models/status.py +++ b/fedireads/models/status.py @@ -15,7 +15,7 @@ class Status(FedireadsModel): status_type = models.CharField(max_length=255, default='Note') content = models.TextField(blank=True, null=True) mention_users = models.ManyToManyField('User', related_name='mention_user') - mention_books = models.ManyToManyField('Book', related_name='mention_book') + mention_books = models.ManyToManyField('Edition', related_name='mention_book') activity_type = models.CharField(max_length=255, default='Note') local = models.BooleanField(default=True) privacy = models.CharField(max_length=255, default='public') @@ -49,7 +49,7 @@ class Status(FedireadsModel): class Comment(Status): ''' like a review but without a rating and transient ''' name = models.CharField(max_length=255) - book = models.ForeignKey('Book', on_delete=models.PROTECT) + book = models.ForeignKey('Edition', on_delete=models.PROTECT) def save(self, *args, **kwargs): self.status_type = 'Comment' @@ -60,7 +60,7 @@ class Comment(Status): class Review(Status): ''' a book review ''' name = models.CharField(max_length=255) - book = models.ForeignKey('Book', on_delete=models.PROTECT) + book = models.ForeignKey('Edition', on_delete=models.PROTECT) rating = models.IntegerField( default=0, validators=[MinValueValidator(0), MaxValueValidator(5)] @@ -92,7 +92,7 @@ class Favorite(FedireadsModel): class Tag(FedireadsModel): ''' freeform tags for books ''' user = models.ForeignKey('User', on_delete=models.PROTECT) - book = models.ForeignKey('Book', on_delete=models.PROTECT) + book = models.ForeignKey('Edition', on_delete=models.PROTECT) name = models.CharField(max_length=100) identifier = models.CharField(max_length=100) @@ -113,7 +113,7 @@ class Notification(FedireadsModel): ''' you've been tagged, liked, followed, etc ''' user = models.ForeignKey('User', on_delete=models.PROTECT) related_book = models.ForeignKey( - 'Book', on_delete=models.PROTECT, null=True) + 'Edition', on_delete=models.PROTECT, null=True) related_user = models.ForeignKey( 'User', on_delete=models.PROTECT, null=True, related_name='related_user') diff --git a/fedireads/views.py b/fedireads/views.py index ec71f8e05..c18d1c583 100644 --- a/fedireads/views.py +++ b/fedireads/views.py @@ -55,14 +55,14 @@ def home_tab(request, tab): shelves.append({ 'name': 'Recently added', 'identifier': None, - 'books': models.Book.objects.order_by( + 'books': models.Edition.objects.order_by( '-created_date' )[:6 - size], 'count': 6 - size, }) # allows us to check if a user has shelved a book - user_books = models.Book.objects.filter(shelves__user=request.user).all() + user_books = models.Edition.objects.filter(shelves__user=request.user).all() # status updates for your follow network following = models.User.objects.filter( @@ -322,11 +322,16 @@ def book_page(request, book_identifier, tab='friends'): return JsonResponse(activitypub.get_book(book)) if isinstance(book, models.Work): - book_reviews = models.Review.objects.filter( - Q(book=book) | Q(book__parent_work=book), - ) - else: - book_reviews = models.Review.objects.filter(book=book) + book = models.Edition.objects.filter( + parent_work=book, + default=True + ).first() + if not book: + book = models.Edition.objects.filter( + parent_work=book, + ).first() + + book_reviews = models.Review.objects.filter(book=book) if request.user.is_authenticated: user_reviews = book_reviews.filter(