Stores book identifiers with path part

This commit is contained in:
Mouse Reeve 2020-02-10 21:09:04 -08:00
parent b1ea495d17
commit 46e38366c7
8 changed files with 48 additions and 46 deletions

View file

@ -1,6 +1,7 @@
''' activitystream api and books ''' ''' activitystream api and books '''
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
import re
import requests import requests
from fedireads.models import Author, Book from fedireads.models import Author, Book
@ -14,10 +15,13 @@ def book_search(query):
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
results = [] results = []
for doc in data['docs'][:5]: for doc in data['docs'][:5]:
key = doc['key']
key = key.split('/')[-1]
results.append({ results.append({
'title': doc['title'], 'title': doc['title'],
'olkey': doc['key'], 'olkey': key,
'year': doc['first_publish_year'], 'year': doc['first_publish_year'],
'author': doc['author_name'][0], 'author': doc['author_name'][0],
}) })
@ -25,9 +29,13 @@ def book_search(query):
def get_or_create_book(olkey, user=None, update=False): def get_or_create_book(olkey, user=None, update=False):
''' add a book ''' ''' add a book by looking up its open library "work" key. I'm conflating
# TODO: check if this is a valid open library key, and a work "book" and "work" here a bit; the table is called "book" in fedireads, but
olkey = olkey in open library parlance, it's a "work," which is the canonical umbrella
item that contains all the editions ("book"s) '''
# check if this is in the format of an OL book identifier
if not re.match(r'^OL\d+W$', olkey):
raise ValueError('Invalid OpenLibrary work ID')
# get the existing entry from our db, if it exists # get the existing entry from our db, if it exists
try: try:
@ -40,7 +48,7 @@ def get_or_create_book(olkey, user=None, update=False):
book = Book(openlibrary_key=olkey) book = Book(openlibrary_key=olkey)
# load the book json from openlibrary.org # load the book json from openlibrary.org
response = requests.get(OL_URL + olkey + '.json') response = requests.get('%s/works/%s.json' % (OL_URL, olkey))
if not response.ok: if not response.ok:
response.raise_for_status() response.raise_for_status()
@ -55,10 +63,12 @@ def get_or_create_book(olkey, user=None, update=False):
# we also need to know the author get the cover # we also need to know the author get the cover
for author_blob in data['authors']: for author_blob in data['authors']:
# this id starts as "/authors/OL1234567A" and we want just "OL1234567A"
author_id = author_blob['author']['key'] author_id = author_blob['author']['key']
author_id = author_id.split('/')[-1]
book.authors.add(get_or_create_author(author_id)) book.authors.add(get_or_create_author(author_id))
if len(data['covers']): if data['covers'] and len(data['covers']):
book.cover.save(*get_cover(data['covers'][0]), save=True) book.cover.save(*get_cover(data['covers'][0]), save=True)
return book return book
@ -76,15 +86,18 @@ def get_cover(cover_id):
return [image_name, image_content] return [image_name, image_content]
def get_or_create_author(olkey): def get_or_create_author(olkey, update=False):
''' load that author ''' ''' load that author '''
# TODO: validate that this is an author key if not re.match(r'^OL\d+A$', olkey):
raise ValueError('Invalid OpenLibrary author ID')
try: try:
author = Author.objects.get(openlibrary_key=olkey) author = Author.objects.get(openlibrary_key=olkey)
if not update:
return author
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
response = requests.get(OL_URL + olkey + '.json') response = requests.get('%s/authors/%s.json' % (OL_URL, olkey))
if not response.ok: if not response.ok:
response.raise_for_status() response.raise_for_status()

View file

@ -4,11 +4,7 @@
<div id="content"> <div id="content">
<div> <div>
<div class="book-preview"> <div class="book-preview">
<img class="book-cover" src="{% if book.cover %}/images/{{ book.cover }}{% else %}/static/images/no_cover.jpg{% endif %}"> {% include 'book_snippet.html' with book=book size=large rating=rating description=True %}
<h1>{{ book.data.title }}</h1>
by {{ book.authors.first.data.name }}
{{ rating | stars }} {{ rating }}
<blockquote>{{ book.data.description | description }}</blockquote>
</div> </div>
</div> </div>
<div class="reviews"> <div class="reviews">

View file

@ -5,7 +5,7 @@
<h1>Search results</h1> <h1>Search results</h1>
{% for result in results %} {% for result in results %}
<div> <div>
<a href="/work/{{ result.olkey }}">{{ result.title }}</a> by {{ result.author }} ({{ result.year }}) <a href="/book/{{ result.olkey }}">{{ result.title }}</a> by {{ result.author }} ({{ result.year }})
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -0,0 +1,14 @@
{% load fr_display %}
<img class="book-cover {{ size }}" src="{% if book.cover %}/images/{{ book.cover }}{% else %}/static/images/no_cover.jpg{% endif %}">
<p class="title">
<a href="/book/{{ book.openlibrary_key }}">{{ book.data.title }}</a>
</p>
<p>by <a href="/author/{{ book.author.id }}" class="author">{{ book.authors.first.data.name }}</a></p>
{% if rating %}
{{ rating | stars }} {{ rating }}
{% endif %}
{% if description %}
<blockquote>{{ book.data.description | description }}</blockquote>
{% endif %}

View file

@ -9,9 +9,7 @@
<p>Start a book!</p> <p>Start a book!</p>
{% for book in to_read.books.all %} {% for book in to_read.books.all %}
<div class="book-preview"> <div class="book-preview">
<img class="book-cover small" src="{% if book.cover %}/images/{{ book.cover }}{% else %}/static/images/no_cover.jpg{% endif %}"> {% include 'book_snippet.html' with book=book size="small" %}
<p class="title"><a href="{{ book.openlibrary_key }}">{{ book.data.title }}</a></p>
<p>by <a href="" class="author">{{ book.authors.first.data.name }}</a></p>
<form name="shelve" action="/shelve/{{ user.localname }}_currently-reading/{{ book.id }}" method="post"> <form name="shelve" action="/shelve/{{ user.localname }}_currently-reading/{{ book.id }}" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="book" value="book.id"></input> <input type="hidden" name="book" value="book.id"></input>
@ -23,9 +21,7 @@
{% for book in reading.books.all %} {% for book in reading.books.all %}
<div class="book-preview"> <div class="book-preview">
<img class="book-cover small" src="{% if book.cover %}/images/{{ book.cover }}{% else %}/static/images/no_cover.jpg{% endif %}"> {% include 'book_snippet.html' with book=book size="small" %}
<p class="title"><a href="{{ book.openlibrary_key }}">{{ book.data.title }}</a></p>
<p>by <a href="" class="author">{{ book.authors.first.data.name }}</a></p>
<form name="shelve" action="/shelve/{{ user.localname }}_read/{{ book.id }}" method="post"> <form name="shelve" action="/shelve/{{ user.localname }}_read/{{ book.id }}" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="book" value="book.id"></input> <input type="hidden" name="book" value="book.id"></input>
@ -39,13 +35,7 @@
<h2>Recently Added Books</h2> <h2>Recently Added Books</h2>
{% for book in recent_books %} {% for book in recent_books %}
<div class="book-preview"> <div class="book-preview">
<img class="book-cover small" src="{% if book.cover %}/images/{{ book.cover }}{% else %}/static/images/no_cover.jpg{% endif %}"> {% include 'book_snippet.html' with book=book size="small" %}
<p class="title">
<a href="{{ book.openlibrary_key }}">{{ book.data.title }}</a>
by
{# TODO: there should be a helper function for listing authors #}
<a href="" class="author">{{ book.authors.first.data.name }}</a>
</p>
{% if not book in user_books.all %} {% if not book in user_books.all %}
<form name="shelve" action="/shelve/{{ user.localname }}_to-read/{{ book.id }}" method="post"> <form name="shelve" action="/shelve/{{ user.localname }}_to-read/{{ book.id }}" method="post">
{% csrf_token %} {% csrf_token %}
@ -81,13 +71,7 @@
</h2> </h2>
{# TODO: wouldn't it rule if this was a reusable piece of markup? #} {# TODO: wouldn't it rule if this was a reusable piece of markup? #}
<div class="book-preview"> <div class="book-preview">
<img class="book-cover" src="{% if activity.book.cover %}/images/{{ activity.book.cover }}{% else %}/static/images/no_cover.jpg{% endif %}"> {% include 'book_snippet.html' with book=activity.book size=large description=True %}
<p class="title">
<a href="{{ activity.book.openlibrary_key }}">{{ activity.book.data.title }}</a>
by
<a href="" class="author">{{ activity.book.authors.first.data.name }}</a>
<blockquote>{{ activity.book.data.description | description }}</blockquote>
</p>
</div> </div>
<div class="interaction"><button>⭐️ Like</button></div> <div class="interaction"><button>⭐️ Like</button></div>
{% elif activity.fedireads_type == 'Review' %} {% elif activity.fedireads_type == 'Review' %}
@ -95,12 +79,7 @@
reviewed {{ activity.book.data.title }} reviewed {{ activity.book.data.title }}
</h2> </h2>
<div class="book-preview review"> <div class="book-preview review">
<img class="book-cover" src="{% if activity.book.cover %}/images/{{ activity.book.cover }}{% else %}/static/images/no_cover.jpg{% endif %}"> {% include 'book_snippet.html' with book=activity.book size=large %}
<p class="title">
<a href="{{ activity.book.openlibrary_key }}">{{ activity.book.data.title }}</a>
by
<a href="" class="author">{{ activity.book.authors.first.data.name }}</a>
</p>
<h3>{{ activity.name }}</h3> <h3>{{ activity.name }}</h3>
<p>{{ activity.rating | stars }}</p> <p>{{ activity.rating | stars }}</p>

View file

@ -28,7 +28,7 @@ urlpatterns = [
# this endpoint is both ui and fed depending on Accept type # this endpoint is both ui and fed depending on Accept type
re_path(r'^user/(?P<username>[\w@\.]+)/?$', views.user_profile), re_path(r'^user/(?P<username>[\w@\.]+)/?$', views.user_profile),
re_path(r'^user/(?P<username>\w+)/edit/?$', views.user_profile_edit), re_path(r'^user/(?P<username>\w+)/edit/?$', views.user_profile_edit),
re_path(r'^work/(?P<book_identifier>\w+)/?$', views.book_page), re_path(r'^book/(?P<book_identifier>\w+)/?$', views.book_page),
# internal action endpoints # internal action endpoints
re_path(r'^review/?$', views.review), re_path(r'^review/?$', views.review),

View file

@ -178,7 +178,7 @@ def edit_profile(request):
@login_required @login_required
def book_page(request, book_identifier): def book_page(request, book_identifier):
''' info about a book ''' ''' info about a book '''
book = openlibrary.get_or_create_book('/work/' + book_identifier) book = openlibrary.get_or_create_book(book_identifier)
# TODO: again, post privacy? # TODO: again, post privacy?
reviews = models.Review.objects.filter(book=book) reviews = models.Review.objects.filter(book=book)
rating = reviews.aggregate(Avg('rating')) rating = reviews.aggregate(Avg('rating'))
@ -226,7 +226,7 @@ def review(request):
rating = form.data.get('rating') rating = form.data.get('rating')
outgoing.handle_review(request.user, book, name, content, rating) outgoing.handle_review(request.user, book, name, content, rating)
return redirect(book_identifier) return redirect('/book/%s' % book_identifier)
@login_required @login_required

View file

@ -12,6 +12,6 @@ echo "from fedireads.models import User
User.objects.create_user('rat', 'rat@rat.com', 'ratword') User.objects.create_user('rat', 'rat@rat.com', 'ratword')
User.objects.get(id=1).followers.add(User.objects.get(id=2))" | python manage.py shell User.objects.get(id=1).followers.add(User.objects.get(id=2))" | python manage.py shell
echo "from fedireads.openlibrary import get_or_create_book echo "from fedireads.openlibrary import get_or_create_book
get_or_create_book('/work/OL1715344W') get_or_create_book('OL1715344W')
get_or_create_book('/work/OL102749W')" | python manage.py shell get_or_create_book('OL102749W')" | python manage.py shell
python manage.py runserver python manage.py runserver