Store book data

This commit is contained in:
Mouse Reeve 2020-03-06 22:56:44 -08:00
parent e45b04f22e
commit d501e707ee
18 changed files with 380 additions and 100 deletions

View file

@ -120,7 +120,7 @@ def get_add_remove(user, book, shelf, action='Add'):
'object': { 'object': {
# TODO: document?? # TODO: document??
'type': 'Document', 'type': 'Document',
'name': book.data['title'], 'name': book.title,
'url': book.openlibrary_key 'url': book.openlibrary_key
}, },
'target': { 'target': {

View file

@ -16,7 +16,7 @@ def get_review_article(review):
''' a book review formatted for a non-fedireads isntance (mastodon) ''' ''' a book review formatted for a non-fedireads isntance (mastodon) '''
status = get_status(review) status = get_status(review)
name = 'Review of "%s" (%d stars): %s' % ( name = 'Review of "%s" (%d stars): %s' % (
review.book.data['title'], review.book.title,
review.rating, review.rating,
review.name review.name
) )

View file

@ -0,0 +1,190 @@
# Generated by Django 3.0.3 on 2020-03-07 06:55
import datetime
from django.db import migrations, models
import django.db.models.deletion
import fedireads.utils.fields
import uuid
class Migration(migrations.Migration):
dependencies = [
('fedireads', '0009_status_published_date'),
]
operations = [
migrations.CreateModel(
name='Edition',
fields=[
('book_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fedireads.Book')),
('isbn', models.CharField(max_length=255, null=True, unique=True)),
('oclc_number', models.CharField(max_length=255, null=True, unique=True)),
('pages', models.IntegerField(null=True)),
],
options={
'abstract': False,
},
bases=('fedireads.book',),
),
migrations.CreateModel(
name='Work',
fields=[
('book_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fedireads.Book')),
('lccn', models.CharField(max_length=255, null=True, unique=True)),
],
options={
'abstract': False,
},
bases=('fedireads.book',),
),
migrations.RemoveField(
model_name='author',
name='data',
),
migrations.RemoveField(
model_name='book',
name='added_by',
),
migrations.RemoveField(
model_name='book',
name='data',
),
migrations.AddField(
model_name='author',
name='aliases',
field=fedireads.utils.fields.ArrayField(base_field=models.CharField(max_length=255), blank=True, size=None),
),
migrations.AddField(
model_name='author',
name='bio',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='author',
name='born',
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name='author',
name='died',
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name='author',
name='first_name',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='author',
name='last_name',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='author',
name='name',
field=models.CharField(default='Unknown', max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='author',
name='wikipedia_link',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='book',
name='description',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='book',
name='first_published_date',
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name='book',
name='language',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='book',
name='last_sync_date',
field=models.DateTimeField(default=datetime.datetime.now),
),
migrations.AddField(
model_name='book',
name='librarything_key',
field=models.CharField(max_length=255, null=True, unique=True),
),
migrations.AddField(
model_name='book',
name='local_edits',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='book',
name='local_key',
field=models.CharField(default=uuid.uuid4, max_length=255, unique=True),
),
migrations.AddField(
model_name='book',
name='misc_identifiers',
field=fedireads.utils.fields.JSONField(null=True),
),
migrations.AddField(
model_name='book',
name='origin',
field=models.CharField(max_length=255, null=True, unique=True),
),
migrations.AddField(
model_name='book',
name='published_date',
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name='book',
name='series',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='book',
name='series_number',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='book',
name='sort_title',
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name='book',
name='subtitle',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='book',
name='sync',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='book',
name='title',
field=models.CharField(default='Unknown', max_length=255),
preserve_default=False,
),
migrations.AlterField(
model_name='author',
name='openlibrary_key',
field=models.CharField(max_length=255, null=True, unique=True),
),
migrations.AlterField(
model_name='book',
name='openlibrary_key',
field=models.CharField(max_length=255, null=True, unique=True),
),
migrations.AddField(
model_name='book',
name='parent_work',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='fedireads.Work'),
),
]

View file

@ -1,5 +1,5 @@
''' bring all the models into the app namespace ''' ''' bring all the models into the app namespace '''
from .book import Shelf, ShelfBook, Book, Author from .book import Book, Work, Edition, Author
from .user import User, UserRelationship, FederatedServer from .shelf import Shelf, ShelfBook
from .status import Status, Review, Favorite, Tag from .status import Status, Review, Favorite, Tag
from .user import User, UserRelationship, FederatedServer

View file

@ -1,68 +1,65 @@
''' database schema for books and shelves ''' ''' database schema for books and shelves '''
from datetime import datetime
from django.db import models from django.db import models
from uuid import uuid4
from fedireads.settings import DOMAIN from fedireads.settings import DOMAIN
from fedireads.utils.fields import JSONField from fedireads.utils.fields import JSONField, ArrayField
from fedireads.utils.models import FedireadsModel from fedireads.utils.models import FedireadsModel
class Shelf(FedireadsModel):
name = models.CharField(max_length=100)
identifier = models.CharField(max_length=100)
user = models.ForeignKey('User', on_delete=models.PROTECT)
editable = models.BooleanField(default=True)
books = models.ManyToManyField(
'Book',
symmetrical=False,
through='ShelfBook',
through_fields=('shelf', 'book')
)
@property
def absolute_id(self):
''' use shelf identifier as absolute id '''
base_path = self.user.absolute_id
model_name = type(self).__name__.lower()
return '%s/%s/%s' % (base_path, model_name, self.identifier)
class Meta:
unique_together = ('user', 'identifier')
class ShelfBook(FedireadsModel):
# many to many join table for books and shelves
book = models.ForeignKey('Book', on_delete=models.PROTECT)
shelf = models.ForeignKey('Shelf', on_delete=models.PROTECT)
added_by = models.ForeignKey(
'User',
blank=True,
null=True,
on_delete=models.PROTECT
)
class Meta:
unique_together = ('book', 'shelf')
class Book(FedireadsModel): class Book(FedireadsModel):
''' a non-canonical copy of a work (not book) from open library ''' ''' a generic book, which can mean either an edition or a work '''
openlibrary_key = models.CharField(max_length=255, unique=True) # these identifiers apply to both works and editions
data = JSONField() openlibrary_key = models.CharField(max_length=255, unique=True, null=True)
librarything_key = models.CharField(max_length=255, unique=True, null=True)
local_key = models.CharField(max_length=255, unique=True, default=uuid4)
misc_identifiers = JSONField(null=True)
# info about where the data comes from and where/if to sync
origin = models.CharField(max_length=255, unique=True, null=True)
local_edits = models.BooleanField(default=False)
sync = models.BooleanField(default=True)
last_sync_date = models.DateTimeField(default=datetime.now)
# TODO: edit history
# book/work metadata
title = models.CharField(max_length=255)
sort_title = models.CharField(max_length=255, null=True)
subtitle = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
language = models.CharField(max_length=255, null=True)
series = models.CharField(max_length=255, blank=True, null=True)
series_number = models.CharField(max_length=255, blank=True, null=True)
# TODO: include an annotation about the type of authorship (ie, translator)
authors = models.ManyToManyField('Author') authors = models.ManyToManyField('Author')
# TODO: also store cover thumbnail # TODO: also store cover thumbnail
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(null=True)
published_date = models.DateTimeField(null=True)
shelves = models.ManyToManyField( shelves = models.ManyToManyField(
'Shelf', 'Shelf',
symmetrical=False, symmetrical=False,
through='ShelfBook', through='ShelfBook',
through_fields=('book', 'shelf') through_fields=('book', 'shelf')
) )
added_by = models.ForeignKey( # TODO: why can't I just call this work????
'User', parent_work = models.ForeignKey('Work', on_delete=models.PROTECT, null=True)
blank=True,
null=True,
on_delete=models.PROTECT class Work(Book):
) ''' a work (an abstract concept of a book that manifests in an edition) '''
# library of congress catalog control number
lccn = models.CharField(max_length=255, unique=True, null=True)
class Edition(Book):
''' an edition of a book '''
# these identifiers only apply to work
isbn = models.CharField(max_length=255, unique=True, null=True)
oclc_number = models.CharField(max_length=255, unique=True, null=True)
pages = models.IntegerField(null=True)
@property @property
def absolute_id(self): def absolute_id(self):
@ -74,6 +71,14 @@ class Book(FedireadsModel):
class Author(FedireadsModel): class Author(FedireadsModel):
''' copy of an author from OL ''' ''' copy of an author from OL '''
openlibrary_key = models.CharField(max_length=255) openlibrary_key = models.CharField(max_length=255, null=True, unique=True)
data = JSONField() wikipedia_link = models.CharField(max_length=255, blank=True, null=True)
# idk probably other keys would be useful here?
born = models.DateTimeField(null=True)
died = models.DateTimeField(null=True)
name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255, null=True)
first_name = models.CharField(max_length=255, null=True)
aliases = ArrayField(models.CharField(max_length=255), blank=True)
bio = models.TextField(null=True, blank=True)

42
fedireads/models/shelf.py Normal file
View file

@ -0,0 +1,42 @@
''' puttin' books on shelves '''
from django.db import models
from fedireads.utils.models import FedireadsModel
class Shelf(FedireadsModel):
name = models.CharField(max_length=100)
identifier = models.CharField(max_length=100)
user = models.ForeignKey('User', on_delete=models.PROTECT)
editable = models.BooleanField(default=True)
books = models.ManyToManyField(
'Book',
symmetrical=False,
through='ShelfBook',
through_fields=('shelf', 'book')
)
@property
def absolute_id(self):
''' use shelf identifier as absolute id '''
base_path = self.user.absolute_id
model_name = type(self).__name__.lower()
return '%s/%s/%s' % (base_path, model_name, self.identifier)
class Meta:
unique_together = ('user', 'identifier')
class ShelfBook(FedireadsModel):
# many to many join table for books and shelves
book = models.ForeignKey('Book', on_delete=models.PROTECT)
shelf = models.ForeignKey('Shelf', on_delete=models.PROTECT)
added_by = models.ForeignKey(
'User',
blank=True,
null=True,
on_delete=models.PROTECT
)
class Meta:
unique_together = ('book', 'shelf')

View file

@ -4,7 +4,7 @@ from django.core.files.base import ContentFile
import re import re
import requests import requests
from fedireads.models import Author, Book from fedireads import models
from fedireads.settings import OL_URL from fedireads.settings import OL_URL
@ -29,24 +29,25 @@ def book_search(query):
return results return results
def get_or_create_book(olkey, user=None, update=False): def get_or_create_book(olkey, update=False):
''' add a book by looking up its open library "work" key. I'm conflating ''' create a book or work '''
"book" and "work" here a bit; the table is called "book" in fedireads, but
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 # check if this is in the format of an OL book identifier
if not re.match(r'^OL\d+W$', olkey): if re.match(r'^OL\d+W$', olkey):
raise ValueError('Invalid OpenLibrary work ID') model = models.Work
elif re.match(r'^OL\d+M$', olkey):
model = models.Edition
else:
raise ValueError('Invalid OpenLibrary ID')
# get the existing entry from our db, if it exists # get the existing entry from our db, if it exists
try: try:
book = Book.objects.get(openlibrary_key=olkey) book = model.objects.get(openlibrary_key=olkey)
if not update: if not update:
return book return book
# we have the book, but still want to update it from OL # we have the book, but still want to update it from OL
except ObjectDoesNotExist: except ObjectDoesNotExist:
# no book was found, so we start creating a new one # no book was found, so we start creating a new one
book = Book(openlibrary_key=olkey) book = model(openlibrary_key=olkey)
# load the book json from openlibrary.org # load the book json from openlibrary.org
response = requests.get('%s/works/%s.json' % (OL_URL, olkey)) response = requests.get('%s/works/%s.json' % (OL_URL, olkey))
@ -54,18 +55,30 @@ def get_or_create_book(olkey, user=None, update=False):
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
book.data = data
if user and user.is_authenticated:
book.added_by = user
# great, we can update our book. # great, we can update our book.
book.title = data['title']
description = data.get('description')
if description:
if isinstance(description, dict):
description = description.get('value')
book.description = description
book.pages = data.get('pages')
#book.published_date = data.get('publish_date')
# this book sure as heck better be an edition
if data.get('works'):
key = data.get('works')[0]['key']
key = key.split('/')[-1]
work = get_or_create_book(key)
book.parent_work = work
book.save() book.save()
# 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.get('authors'):
# this id starts as "/authors/OL1234567A" and we want just "OL1234567A" # this id starts as "/authors/OL1234567A" and we want just "OL1234567A"
author_id = author_blob['author']['key'] author_blob = author_blob.get('author', author_blob)
author_id = author_blob['key']
author_id = author_id.split('/')[-1] author_id = author_id.split('/')[-1]
book.authors.add(get_or_create_author(author_id)) book.authors.add(get_or_create_author(author_id))
@ -92,7 +105,7 @@ def get_or_create_author(olkey, update=False):
if not re.match(r'^OL\d+A$', olkey): if not re.match(r'^OL\d+A$', olkey):
raise ValueError('Invalid OpenLibrary author ID') raise ValueError('Invalid OpenLibrary author ID')
try: try:
author = Author.objects.get(openlibrary_key=olkey) author = models.Author.objects.get(openlibrary_key=olkey)
if not update: if not update:
return author return author
except ObjectDoesNotExist: except ObjectDoesNotExist:
@ -103,7 +116,20 @@ def get_or_create_author(olkey, update=False):
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
author = Author(openlibrary_key=olkey, data=data) author = models.Author(openlibrary_key=olkey)
bio = data.get('bio')
if bio:
if isinstance(bio, dict):
bio = bio.get('value')
author.bio = bio
name = data['name']
author.name = name
# TODO this is making some BOLD assumption
author.last_name = name.split(' ')[-1]
author.first_name = ' '.join(name.split(' ')[:-1])
#author.born = data.get('birth_date')
#author.died = data.get('death_date')
author.save() author.save()
return author return author

View file

@ -126,7 +126,7 @@ def handle_shelve(user, book, shelf):
'read': 'finished reading' 'read': 'finished reading'
}[shelf.identifier] }[shelf.identifier]
name = user.name if user.name else user.localname name = user.name if user.name else user.localname
message = '%s %s %s' % (name, verb, book.data['title']) message = '%s %s %s' % (name, verb, book.title)
status = create_status(user, message, mention_books=[book]) status = create_status(user, message, mention_books=[book])
activity = activitypub.get_status(status) activity = activitypub.get_status(status)
@ -150,7 +150,7 @@ def handle_unshelve(user, book, shelf):
def handle_review(user, book, name, content, rating): def handle_review(user, book, name, content, rating):
''' post a review ''' ''' post a review '''
# validated and saves the review in the database so it has an id # validated and saves the review in the database so it has an id
review = create_review(user, book, name, content, rating) review = create_review(user, book, name, content, rating, None)
review_activity = activitypub.get_review(review) review_activity = activitypub.get_review(review)
review_create_activity = activitypub.get_create(user, review_activity) review_create_activity = activitypub.get_create(user, review_activity)

View file

@ -15,14 +15,17 @@ def create_review(user, possible_book, name, content, rating, published):
# no ratings outside of 0-5 # no ratings outside of 0-5
rating = rating if 0 <= rating <= 5 else 0 rating = rating if 0 <= rating <= 5 else 0
return models.Review.objects.create( review = models.Review(
user=user, user=user,
book=book, book=book,
name=name, name=name,
rating=rating, rating=rating,
content=content, content=content,
published_date=published,
) )
if published:
review.published_date = published
review.save()
return review
def create_status(user, content, reply_parent=None, mention_books=None): def create_status(user, content, reply_parent=None, mention_books=None):

View file

@ -3,9 +3,9 @@
{% block content %} {% block content %}
<div id="content"> <div id="content">
<div> <div>
<h2>{{ author.data.name }}</h2> <h2>{{ author.name }}</h2>
{% if author.data.bio %} {% if author.bio %}
<blockquote>{{ author.data.bio | author_bio }} <blockquote>{{ author.bio | author_bio }}
</blockquote> </blockquote>
{% endif %} {% endif %}
{% for book in books %} {% for book in books %}

View file

@ -3,7 +3,7 @@
{% block content %} {% block content %}
<div id="sidebar"> <div id="sidebar">
<div> <div>
<h2><q>{{ book.data.title }}</q> and You</h2> <h2><q>{{ book.title }}</q> and You</h2>
<p>{% if shelf %}On shelf <q>{{ shelf.name }}</q>{% endif %}</p> <p>{% if shelf %}On shelf <q>{{ shelf.name }}</q>{% endif %}</p>
{% include 'snippets/shelve-button.html' with book=book pulldown=True %} {% include 'snippets/shelve-button.html' with book=book pulldown=True %}
@ -27,13 +27,15 @@
<div id="content"> <div id="content">
<div> <div>
<h2><q>{{ book.data.title }}</q> by <h2><q>{{ book.title }}</q> by
{% include 'snippets/authors.html' with book=book %}</h2> {% include 'snippets/authors.html' with book=book %}</h2>
{% if book.parent_work %}<p>Edition of <a href="/book/{{ book.parent_work.openlibrary_key }}">{{ book.parent_work.title }}</a></p>{% endif %}
<div class="book-preview"> <div class="book-preview">
{% include 'snippets/book_cover.html' with book=book size=large %} {% include 'snippets/book_cover.html' with book=book size=large %}
<p>{{ active_tab }} rating: {{ rating | stars }}</p> <p>{{ active_tab }} rating: {{ rating | stars }}</p>
{% if description %} {% if description %}
<blockquote>{{ book.data.description | description }}</blockquote> <blockquote>{{ book.description | description }}</blockquote>
{% endif %} {% endif %}
<div> <div>
<div id="tag-cloud"> <div id="tag-cloud">

View file

@ -1 +1 @@
by <a href="/author/{{ book.authors.first.openlibrary_key }}" class="author">{{ book.authors.first.data.name }}</a> <a href="/author/{{ book.authors.first.openlibrary_key }}" class="author">{{ book.authors.first.name }}</a>

View file

@ -1,10 +1,10 @@
{% load fr_display %} {% load fr_display %}
{% include 'snippets/book_cover.html' with book=book %} {% include 'snippets/book_cover.html' with book=book %}
<p class="title"> <p class="title">
<a href="/book/{{ book.openlibrary_key }}">{{ book.data.title }}</a> <a href="/book/{{ book.openlibrary_key }}">{{ book.title }}</a>
</p> </p>
<p> <p>
{% include 'snippets/authors.html' with book=book %} by {% include 'snippets/authors.html' with book=book %}
</p> </p>
{% if rating %} {% if rating %}
@ -12,7 +12,7 @@
{% endif %} {% endif %}
{% if description %} {% if description %}
<blockquote>{{ book.data.description | description }}</blockquote> <blockquote>{{ book.description | description }}</blockquote>
{% endif %} {% endif %}
{% include 'snippets/shelve-button.html' with book=book pulldown=shelf_pulldown %} {% include 'snippets/shelve-button.html' with book=book pulldown=shelf_pulldown %}

View file

@ -33,19 +33,19 @@
{% include 'snippets/book_cover.html' with book=book %} {% include 'snippets/book_cover.html' with book=book %}
</td> </td>
<td> <td>
<a href="/book/{{ book.openlibrary_key }}">{{ book.data.title }}</a> <a href="/book/{{ book.openlibrary_key }}">{{ book.title }}</a>
</td> </td>
<td> <td>
{{ book.authors.first.data.name }} {{ book.authors.first.data.name }}
</td> </td>
<td> <td>
{{ book.data.first_publish_date }} {{ book.first_publish_date }}
</td> </td>
<td> <td>
{{ book.added_date | naturalday }} {{ book.added_date | naturalday }}
</td> </td>
<td> <td>
<a href="https://openlibrary.org{{ book.data.key }}" target="_blank">OpenLibrary</a> <a href="https://openlibrary.org{{ book.key }}" target="_blank">OpenLibrary</a>
</td> </td>
{% if ratings %} {% if ratings %}
<td> <td>

View file

@ -1,7 +1,7 @@
{% load fr_display %} {% load fr_display %}
<div class="update"> <div class="update">
{% if activity.status_type == 'Review' %} {% if activity.status_type == 'Review' %}
{% include 'snippets/status_banner.html' with content="reviewed <i>"|add:activity.book.data.title|add:"</i>" %} {% include 'snippets/status_banner.html' with content="reviewed <i>"|add:activity.book.title|add:"</i>" %}
<div class="book-preview review"> <div class="book-preview review">
{% include 'snippets/book.html' with book=activity.book size=large %} {% include 'snippets/book.html' with book=activity.book size=large %}

View file

@ -11,6 +11,7 @@ def dict_key(d, k):
'''Returns the given key from a dictionary.''' '''Returns the given key from a dictionary.'''
return d.get(k) or 0 return d.get(k) or 0
@register.filter(name='stars') @register.filter(name='stars')
def stars(number): def stars(number):
''' turn integers into stars ''' ''' turn integers into stars '''
@ -20,16 +21,19 @@ def stars(number):
number = 0 number = 0
return ('' * number) + '' * (5 - number) return ('' * number) + '' * (5 - number)
@register.filter(name='description') @register.filter(name='description')
def description_format(description): def description_format(description):
''' handle the various OL description formats ''' ''' handle the various OL description formats '''
if isinstance(description, dict) and 'value' in description: if not description:
description = description['value'] return ''
if '----------' in description: if '----------' in description:
description = description.split('----------')[0] description = description.split('----------')[0]
return description.strip() return description.strip()
@register.filter(name='author_bio') @register.filter(name='author_bio')
def bio_format(bio): def bio_format(bio):
''' clean up OL author bios ''' ''' clean up OL author bios '''

View file

@ -1,3 +1,4 @@
''' base model with default fields '''
from django.db import models from django.db import models
from fedireads.settings import DOMAIN from fedireads.settings import DOMAIN

View file

@ -190,26 +190,33 @@ def book_page(request, book_identifier, tab='friends'):
''' info about a book ''' ''' info about a book '''
book = openlibrary.get_or_create_book(book_identifier) book = openlibrary.get_or_create_book(book_identifier)
user_reviews = models.Review.objects.filter(user=request.user, book=book).all() 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)
user_reviews = book_reviews.filter(
user=request.user,
).all()
if tab == 'friends': if tab == 'friends':
reviews = models.Review.objects.filter( reviews = book_reviews.filter(
Q(user__followers=request.user, privacy='public') | \ Q(user__followers=request.user, privacy='public') | \
Q(user=request.user) | \
Q(mention_users=request.user), Q(mention_users=request.user),
book=book,
) )
elif tab == 'local': elif tab == 'local':
reviews = models.Review.objects.filter( reviews = book_reviews.filter(
Q(privacy='public') | \ Q(privacy='public') | \
Q(mention_users=request.user), Q(mention_users=request.user),
user__local=True, user__local=True,
book=book,
) )
else: else:
reviews = models.Review.objects.filter( reviews = book_reviews.filter(
Q(privacy='public') | \ Q(privacy='public') | \
Q(mention_users=request.user), Q(mention_users=request.user),
book=book,
) )
try: try: