Don't store default edition in the dataase

This commit is contained in:
Mouse Reeve 2021-04-28 15:19:24 -07:00
parent ccf10e8012
commit 6551c59c45
14 changed files with 56 additions and 62 deletions

View file

@ -62,7 +62,6 @@ class Work(Book):
"""work instance of a book object""" """work instance of a book object"""
lccn: str = "" lccn: str = ""
defaultEdition: str = ""
editions: List[str] = field(default_factory=lambda: []) editions: List[str] = field(default_factory=lambda: [])
type: str = "Work" type: str = "Work"

View file

@ -116,8 +116,8 @@ class AbstractConnector(AbstractMinimalConnector):
remote_id remote_id
) or models.Work.find_existing_by_remote_id(remote_id) ) or models.Work.find_existing_by_remote_id(remote_id)
if existing: if existing:
if hasattr(existing, "get_default_editon"): if hasattr(existing, "default_edition"):
return existing.get_default_editon() return existing.default_edition
return existing return existing
# load the json # load the json
@ -170,10 +170,6 @@ class AbstractConnector(AbstractMinimalConnector):
edition.connector = self.connector edition.connector = self.connector
edition.save() edition.save()
if not work.default_edition:
work.default_edition = edition
work.save()
for author in self.get_authors_from_data(edition_data): for author in self.get_authors_from_data(edition_data):
edition.authors.add(author) edition.authors.add(author)
if not edition.authors.exists() and work.authors.exists(): if not edition.authors.exists() and work.authors.exists():

View file

@ -6,12 +6,8 @@ from .abstract_connector import AbstractMinimalConnector, SearchResult
class Connector(AbstractMinimalConnector): class Connector(AbstractMinimalConnector):
"""this is basically just for search""" """this is basically just for search"""
def get_or_create_book(self, remote_id): def get_or_create_book(self, remote_id, work=None):
edition = activitypub.resolve_remote_id(remote_id, model=models.Edition) return activitypub.resolve_remote_id(remote_id, model=models.Edition)
work = edition.parent_work
work.default_edition = work.get_default_edition()
work.save()
return edition
def parse_search_data(self, data): def parse_search_data(self, data):
return data return data

View file

@ -3,7 +3,7 @@ from functools import reduce
import operator import operator
from django.contrib.postgres.search import SearchRank, SearchVector from django.contrib.postgres.search import SearchRank, SearchVector
from django.db.models import Count, F, Q from django.db.models import Count, OuterRef, Subquery, F, Q
from bookwyrm import models from bookwyrm import models
from .abstract_connector import AbstractConnector, SearchResult from .abstract_connector import AbstractConnector, SearchResult
@ -47,7 +47,16 @@ class Connector(AbstractConnector):
# when there are multiple editions of the same work, pick the default. # when there are multiple editions of the same work, pick the default.
# it would be odd for this to happen. # it would be odd for this to happen.
results = results.filter(parent_work__default_edition__id=F("id")) or results
default_editions = models.Edition.objects.filter(
parent_work=OuterRef("parent_work")
).order_by("-edition_rank")
results = (
results.annotate(
default_id=Subquery(default_editions.values("id")[:1])
).filter(default_id=F("id"))
or results
)
search_results = [] search_results = []
for result in results: for result in results:
@ -112,7 +121,15 @@ def search_identifiers(query, *filters):
# when there are multiple editions of the same work, pick the default. # when there are multiple editions of the same work, pick the default.
# it would be odd for this to happen. # it would be odd for this to happen.
return results.filter(parent_work__default_edition__id=F("id")) or results default_editions = models.Edition.objects.filter(
parent_work=OuterRef("parent_work")
).order_by("-edition_rank")
return (
results.annotate(default_id=Subquery(default_editions.values("id")[:1])).filter(
default_id=F("id")
)
or results
)
def search_title_author(query, min_confidence, *filters): def search_title_author(query, min_confidence, *filters):
@ -140,10 +157,10 @@ def search_title_author(query, min_confidence, *filters):
for work_id in set(editions_of_work): for work_id in set(editions_of_work):
editions = results.filter(parent_work=work_id) editions = results.filter(parent_work=work_id)
default = editions.filter(parent_work__default_edition=F("id")) default = editions.order_by("-edition_rank").first()
default_rank = default.first().rank if default.exists() else 0 default_rank = default.rank if default else 0
# if mutliple books have the top rank, pick the default edition # if mutliple books have the top rank, pick the default edition
if default_rank == editions.first().rank: if default_rank == editions.first().rank:
yield default.first() yield default
else: else:
yield editions.first() yield editions.first()

View file

@ -0,0 +1,17 @@
# Generated by Django 3.2 on 2021-04-28 22:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0071_merge_0063_auto_20210407_0045_0070_auto_20210423_0121"),
]
operations = [
migrations.RemoveField(
model_name="work",
name="default_edition",
),
]

View file

@ -1,7 +1,7 @@
""" database schema for books and shelves """ """ database schema for books and shelves """
import re import re
from django.db import models, transaction from django.db import models
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
from bookwyrm import activitypub from bookwyrm import activitypub
@ -143,10 +143,6 @@ class Work(OrderedCollectionPageMixin, Book):
lccn = fields.CharField( lccn = fields.CharField(
max_length=255, blank=True, null=True, deduplication_field=True max_length=255, blank=True, null=True, deduplication_field=True
) )
# this has to be nullable but should never be null
default_edition = fields.ForeignKey(
"Edition", on_delete=models.PROTECT, null=True, load_remote=False
)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""set some fields on the edition object""" """set some fields on the edition object"""
@ -155,18 +151,10 @@ class Work(OrderedCollectionPageMixin, Book):
edition.save() edition.save()
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
def get_default_edition(self): @property
def default_edition(self):
"""in case the default edition is not set""" """in case the default edition is not set"""
return self.default_edition or self.editions.order_by("-edition_rank").first() return self.editions.order_by("-edition_rank").first()
@transaction.atomic()
def reset_default_edition(self):
"""sets a new default edition based on computed rank"""
self.default_edition = None
# editions are re-ranked implicitly
self.save()
self.default_edition = self.get_default_edition()
self.save()
def to_edition_list(self, **kwargs): def to_edition_list(self, **kwargs):
"""an ordered collection of editions""" """an ordered collection of editions"""
@ -220,15 +208,8 @@ class Edition(Book):
activity_serializer = activitypub.Edition activity_serializer = activitypub.Edition
name_field = "title" name_field = "title"
def get_rank(self, ignore_default=False): def get_rank(self):
"""calculate how complete the data is on this edition""" """calculate how complete the data is on this edition"""
if (
not ignore_default
and self.parent_work
and self.parent_work.default_edition == self
):
# default edition has the highest rank
return 20
rank = 0 rank = 0
rank += int(bool(self.cover)) * 3 rank += int(bool(self.cover)) * 3
rank += int(bool(self.isbn_13)) rank += int(bool(self.isbn_13))

View file

@ -17,8 +17,6 @@ class ConnectorManager(TestCase):
self.edition = models.Edition.objects.create( self.edition = models.Edition.objects.create(
title="Example Edition", parent_work=self.work, isbn_10="0000000000" title="Example Edition", parent_work=self.work, isbn_10="0000000000"
) )
self.work.default_edition = self.edition
self.work.save()
self.connector = models.Connector.objects.create( self.connector = models.Connector.objects.create(
identifier="test_connector", identifier="test_connector",

View file

@ -84,11 +84,11 @@ class SelfConnector(TestCase):
title="Edition 1 Title", parent_work=work title="Edition 1 Title", parent_work=work
) )
edition_2 = models.Edition.objects.create( edition_2 = models.Edition.objects.create(
title="Edition 2 Title", parent_work=work title="Edition 2 Title",
parent_work=work,
edition_rank=20, # that's default babey
) )
edition_3 = models.Edition.objects.create(title="Fish", parent_work=work) edition_3 = models.Edition.objects.create(title="Fish", parent_work=work)
work.default_edition = edition_2
work.save()
# pick the best edition # pick the best edition
results = self.connector.search("Edition 1 Title") results = self.connector.search("Edition 1 Title")

View file

@ -84,9 +84,3 @@ class Book(TestCase):
self.first_edition.description = "hi" self.first_edition.description = "hi"
self.first_edition.save() self.first_edition.save()
self.assertEqual(self.first_edition.edition_rank, 1) self.assertEqual(self.first_edition.edition_rank, 1)
# default edition
self.work.default_edition = self.first_edition
self.work.save()
self.first_edition.refresh_from_db()
self.assertEqual(self.first_edition.edition_rank, 20)

View file

@ -2,7 +2,7 @@
from django.test import TestCase from django.test import TestCase
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from bookwyrm import models, settings from bookwyrm import models
class ReadThrough(TestCase): class ReadThrough(TestCase):
@ -19,8 +19,6 @@ class ReadThrough(TestCase):
self.edition = models.Edition.objects.create( self.edition = models.Edition.objects.create(
title="Example Edition", parent_work=self.work title="Example Edition", parent_work=self.work
) )
self.work.default_edition = self.edition
self.work.save()
self.readthrough = models.ReadThrough.objects.create( self.readthrough = models.ReadThrough.objects.create(
user=self.user, book=self.edition user=self.user, book=self.edition

View file

@ -20,8 +20,6 @@ class ReadThrough(TestCase):
self.edition = models.Edition.objects.create( self.edition = models.Edition.objects.create(
title="Example Edition", parent_work=self.work title="Example Edition", parent_work=self.work
) )
self.work.default_edition = self.edition
self.work.save()
self.user = models.User.objects.create_user( self.user = models.User.objects.create_user(
"cinco", "cinco@example.com", "seissiete", local=True, localname="cinco" "cinco", "cinco@example.com", "seissiete", local=True, localname="cinco"

View file

@ -27,7 +27,7 @@ class Author(View):
).distinct() ).distinct()
data = { data = {
"author": author, "author": author,
"books": [b.get_default_edition() for b in books], "books": [b.default_edition for b in books],
} }
return TemplateResponse(request, "author.html", data) return TemplateResponse(request, "author.html", data)

View file

@ -39,7 +39,7 @@ class Book(View):
return ActivitypubResponse(book.to_activity()) return ActivitypubResponse(book.to_activity())
if isinstance(book, models.Work): if isinstance(book, models.Work):
book = book.get_default_edition() book = book.default_edition
if not book or not book.parent_work: if not book or not book.parent_work:
return HttpResponseNotFound() return HttpResponseNotFound()

View file

@ -123,7 +123,7 @@ def get_edition(book_id):
"""look up a book in the db and return an edition""" """look up a book in the db and return an edition"""
book = models.Book.objects.select_subclasses().get(id=book_id) book = models.Book.objects.select_subclasses().get(id=book_id)
if isinstance(book, models.Work): if isinstance(book, models.Work):
book = book.get_default_edition() book = book.default_edition
return book return book