Merge pull request #158 from mouse-reeve/book-search

Local search endpoint
This commit is contained in:
Mouse Reeve 2020-05-02 17:04:38 -07:00 committed by GitHub
commit ee832b82e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 113 additions and 14 deletions

View file

@ -7,13 +7,12 @@ def get_book(book):
fields = [ fields = [
'sort_title', 'sort_title',
'subtitle', 'subtitle',
'isbn', 'isbn_13',
'oclc_number', 'oclc_number',
'openlibrary_key', 'openlibrary_key',
'librarything_key', 'librarything_key',
'fedireads_key', 'fedireads_key',
'lccn', 'lccn',
'isbn',
'oclc_number', 'oclc_number',
'pages', 'pages',
'physical_format', 'physical_format',

View file

@ -31,8 +31,21 @@ def load_more_data(book_id):
def search(query): def search(query):
''' try an external datasource for books ''' ''' try an external datasource for books '''
self = self_connector()
results = self.search(query)
if len(results) >= 10:
return results
connector = get_connector() connector = get_connector()
return connector.search(query) external_results = connector.search(query)
dedupe_slug = lambda r: '%s %s %s' % (r.title, r.author, r.year)
result_index = [dedupe_slug(r) for r in results]
for result in external_results:
if dedupe_slug(result) in result_index:
continue
results.append(result)
return results
def update_book(book): def update_book(book):
''' re-sync with the original data source ''' ''' re-sync with the original data source '''
@ -40,12 +53,24 @@ def update_book(book):
connector.update_book(book) connector.update_book(book)
def get_connector(book=None): def self_connector():
''' load the connector for the local database '''
return get_connector(self=True)
def get_connector(book=None, self=False):
''' pick a book data connector ''' ''' pick a book data connector '''
if book and book.connector: if book and book.connector:
connector_info = book.connector connector_info = book.connector
elif self:
connector_info = models.Connector.objects.filter(
connector_file='self_connector'
).first()
else: else:
connector_info = models.Connector.objects.first() # only select from external connectors
connector_info = models.Connector.objects.exclude(
connector_file='self_connector'
).first()
connector = importlib.import_module( connector = importlib.import_module(
'fedireads.connectors.%s' % connector_info.connector_file 'fedireads.connectors.%s' % connector_info.connector_file

View file

@ -19,7 +19,7 @@ class Connector(AbstractConnector):
'publish_date': ('published_date', get_date), 'publish_date': ('published_date', get_date),
'first_publish_date': ('first_published_date', get_date), 'first_publish_date': ('first_published_date', get_date),
'description': ('description', get_description), 'description': ('description', get_description),
'isbn_13': ('isbn', get_first), 'isbn_13': ('isbn_13', get_first),
'oclc_numbers': ('oclc_number', get_first), 'oclc_numbers': ('oclc_number', get_first),
'lccn': ('lccn', get_first), 'lccn': ('lccn', get_first),
'languages': ('languages', get_languages), 'languages': ('languages', get_languages),
@ -80,6 +80,7 @@ class Connector(AbstractConnector):
edition_data = pick_default_edition(edition_options) edition_data = pick_default_edition(edition_options)
key = edition_data.get('key').split('/')[-1] key = edition_data.get('key').split('/')[-1]
edition = self.create_book(key, edition_data, models.Edition) edition = self.create_book(key, edition_data, models.Edition)
edition.default = True
edition.parent_work = work edition.parent_work = work
edition.save() edition.save()
else: else:
@ -98,6 +99,7 @@ class Connector(AbstractConnector):
edition.save() edition.save()
if not edition.authors and work.authors: if not edition.authors and work.authors:
edition.authors.set(work.authors.all()) edition.authors.set(work.authors.all())
edition.author_text = ', '.join(a.name for a in edition.authors)
return edition return edition
@ -118,8 +120,11 @@ class Connector(AbstractConnector):
update_from_mappings(book, data, self.book_mappings) update_from_mappings(book, data, self.book_mappings)
book.save() book.save()
for author in self.get_authors_from_data(data): authors = self.get_authors_from_data(data)
for author in authors:
book.authors.add(author) book.authors.add(author)
if authors:
book.author_text = ', '.join(a.name for a in authors)
if data.get('covers'): if data.get('covers'):
book.cover.save(*self.get_cover(data['covers'][0]), save=True) book.cover.save(*self.get_cover(data['covers'][0]), save=True)

View file

@ -1,8 +1,9 @@
''' using a fedireads instance as a source of book data ''' ''' using a fedireads instance as a source of book data '''
from django.contrib.postgres.search import SearchVector
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from fedireads import models from fedireads import models
from .abstract_connector import AbstractConnector from .abstract_connector import AbstractConnector, SearchResult
class Connector(AbstractConnector): class Connector(AbstractConnector):
@ -14,7 +15,34 @@ class Connector(AbstractConnector):
def search(self, query): def search(self, query):
''' right now you can't search fedireads sorry, but when ''' right now you can't search fedireads sorry, but when
that gets implemented it will totally rule ''' that gets implemented it will totally rule '''
return [] results = models.Edition.objects.annotate(
search=SearchVector('title', weight='A') +\
SearchVector('subtitle', weight='B') +\
SearchVector('author_text', weight='A') +\
SearchVector('isbn_13', weight='A') +\
SearchVector('isbn_10', weight='A') +\
SearchVector('openlibrary_key', weight='B') +\
SearchVector('goodreads_key', weight='B') +\
SearchVector('source_url', weight='B') +\
SearchVector('asin', weight='B') +\
SearchVector('oclc_number', weight='B') +\
SearchVector('description', weight='C') +\
SearchVector('series', weight='C')
).filter(search=query)
results = results.filter(default=True) or results
search_results = []
for book in results[:10]:
search_results.append(
SearchResult(
book.title,
book.fedireads_key,
book.author_text,
book.published_date.year if book.published_date else None,
None
)
)
return search_results
def get_or_create_book(self, fedireads_key): def get_or_create_book(self, fedireads_key):
@ -38,3 +66,7 @@ class Connector(AbstractConnector):
def update_book(self, book_obj): def update_book(self, book_obj):
pass pass
def expand_book_data(self, book):
pass

View file

@ -0,0 +1,33 @@
# Generated by Django 3.0.3 on 2020-04-29 17:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fedireads', '0034_importjob_import_status'),
]
operations = [
migrations.RenameField(
model_name='edition',
old_name='isbn',
new_name='isbn_13',
),
migrations.AddField(
model_name='book',
name='author_text',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='edition',
name='asin',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='edition',
name='isbn_10',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View file

@ -82,6 +82,8 @@ class Book(FedireadsModel):
) )
# TODO: include an annotation about the type of authorship (ie, translator) # TODO: include an annotation about the type of authorship (ie, translator)
authors = models.ManyToManyField('Author') authors = models.ManyToManyField('Author')
# preformatted authorship string for search and easier display
author_text = models.CharField(max_length=255, blank=True, null=True)
cover = models.ImageField(upload_to='covers/', blank=True, null=True) cover = models.ImageField(upload_to='covers/', blank=True, null=True)
first_published_date = models.DateTimeField(blank=True, null=True) first_published_date = models.DateTimeField(blank=True, null=True)
published_date = models.DateTimeField(blank=True, null=True) published_date = models.DateTimeField(blank=True, null=True)
@ -123,10 +125,13 @@ class Work(Book):
class Edition(Book): class Edition(Book):
''' an edition of a book ''' ''' an edition of a book '''
# default -> this is what gets displayed for a work
default = models.BooleanField(default=False) default = models.BooleanField(default=False)
# these identifiers only apply to work # these identifiers only apply to editions, not works
isbn = models.CharField(max_length=255, blank=True, null=True) isbn_10 = models.CharField(max_length=255, blank=True, null=True)
isbn_13 = models.CharField(max_length=255, blank=True, null=True)
oclc_number = models.CharField(max_length=255, blank=True, null=True) oclc_number = models.CharField(max_length=255, blank=True, null=True)
asin = models.CharField(max_length=255, blank=True, null=True)
pages = models.IntegerField(blank=True, null=True) pages = models.IntegerField(blank=True, null=True)
physical_format = models.CharField(max_length=255, blank=True, null=True) physical_format = models.CharField(max_length=255, blank=True, null=True)
publishers = ArrayField( publishers = ArrayField(

View file

@ -61,7 +61,7 @@ class ImportItem(models.Model):
def get_book_from_db_isbn(self): def get_book_from_db_isbn(self):
''' see if we already know about the book ''' ''' see if we already know about the book '''
try: try:
return Edition.objects.get(isbn=self.isbn) return Edition.objects.get(isbn_13=self.isbn)
except Edition.DoesNotExist: except Edition.DoesNotExist:
return None return None

View file

@ -40,7 +40,7 @@
<h3>Book Identifiers</h2> <h3>Book Identifiers</h2>
<div> <div>
<p><label for="id_isbn">ISBN:</label> {{ form.isbn }} </p> <p><label for="id_isbn">ISBN 13:</label> {{ form.isbn_13 }} </p>
<p><label for="id_fedireads_key">Fedireads key:</label> {{ form.fedireads_key }} </p> <p><label for="id_fedireads_key">Fedireads key:</label> {{ form.fedireads_key }} </p>
<p><label for="id_openlibrary_key">Openlibrary key:</label> {{ form.openlibrary_key }} </p> <p><label for="id_openlibrary_key">Openlibrary key:</label> {{ form.openlibrary_key }} </p>
<p><label for="id_librarything_key">Librarything key:</label> {{ form.librarything_key }} </p> <p><label for="id_librarything_key">Librarything key:</label> {{ form.librarything_key }} </p>

View file

@ -431,7 +431,7 @@ def book_page(request, book_identifier, tab='friends'):
'path': '/book/%s' % book_identifier, 'path': '/book/%s' % book_identifier,
'cover_form': forms.CoverForm(instance=book), 'cover_form': forms.CoverForm(instance=book),
'info_fields': [ 'info_fields': [
{'name': 'ISBN', 'value': book.isbn}, {'name': 'ISBN', 'value': book.isbn_13},
{'name': 'OCLC number', 'value': book.oclc_number}, {'name': 'OCLC number', 'value': book.oclc_number},
{'name': 'OpenLibrary ID', 'value': book.openlibrary_key}, {'name': 'OpenLibrary ID', 'value': book.openlibrary_key},
{'name': 'Goodreads ID', 'value': book.goodreads_key}, {'name': 'Goodreads ID', 'value': book.goodreads_key},