Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2020-12-17 13:26:42 -08:00
commit c5b48f521a
16 changed files with 97 additions and 61 deletions

View file

@ -38,7 +38,7 @@ class Edition(Book):
isbn13: str = ''
oclcNumber: str = ''
asin: str = ''
pages: str = ''
pages: int = None
physicalFormat: str = ''
publishers: List[str] = field(default_factory=lambda: [])

View file

@ -53,7 +53,7 @@ class Comment(Note):
@dataclass(init=False)
class Review(Comment):
''' a full book review '''
name: str
name: str = None
rating: int = None
type: str = 'Review'

View file

@ -52,12 +52,29 @@ class Book(ActivitypubMixin, BookWyrmModel):
authors = fields.ManyToManyField('Author')
# preformatted authorship string for search and easier display
author_text = models.CharField(max_length=255, blank=True, null=True)
cover = fields.ImageField(upload_to='covers/', blank=True, null=True)
cover = fields.ImageField(
upload_to='covers/', blank=True, null=True, alt_field='alt_text')
first_published_date = fields.DateTimeField(blank=True, null=True)
published_date = fields.DateTimeField(blank=True, null=True)
objects = InheritanceManager()
@property
def edition_info(self):
''' properties of this edition, as a string '''
items = [
self.physical_format,
self.languages[0] + ' language' if self.languages and \
self.languages[0] != 'English' else None,
str(self.published_date.year) if self.published_date else None,
]
return ', '.join(i for i in items if i)
@property
def alt_text(self):
''' image alt test '''
return '%s cover (%s)' % (self.title, self.edition_info)
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):
@ -92,7 +109,8 @@ class Work(OrderedCollectionPageMixin, Book):
default_edition = fields.ForeignKey(
'Edition',
on_delete=models.PROTECT,
null=True
null=True,
load_remote=False
)
def get_default_edition(self):

View file

@ -100,12 +100,19 @@ class ActivitypubFieldMixin:
class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin):
''' default (de)serialization for foreign key and one to one '''
def __init__(self, *args, load_remote=True, **kwargs):
self.load_remote = load_remote
super().__init__(*args, **kwargs)
def field_from_activity(self, value):
if not value:
return None
related_model = self.related_model
if isinstance(value, dict) and value.get('id'):
if not self.load_remote:
# only look in the local database
return related_model.find_existing(value)
# this is an activitypub object, which we can deserialize
activity_serializer = related_model.activity_serializer
return activity_serializer(**value).to_model(related_model)
@ -116,6 +123,9 @@ class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin):
# we don't know what this is, ignore it
return None
# gets or creates the model field from the remote id
if not self.load_remote:
# only look in the local database
return related_model.find_existing_by_remote_id(value)
return activitypub.resolve_remote_id(related_model, value)
@ -295,18 +305,22 @@ class TagField(ManyToManyField):
return items
def image_serializer(value):
def image_serializer(value, alt):
''' helper for serializing images '''
if value and hasattr(value, 'url'):
url = value.url
else:
return None
url = 'https://%s%s' % (DOMAIN, url)
return activitypub.Image(url=url)
return activitypub.Image(url=url, name=alt)
class ImageField(ActivitypubFieldMixin, models.ImageField):
''' activitypub-aware image field '''
def __init__(self, *args, alt_field=None, **kwargs):
self.alt_field = alt_field
super().__init__(*args, **kwargs)
# pylint: disable=arguments-differ
def set_field_from_activity(self, instance, data, save=True):
''' helper function for assinging a value to the field '''
@ -316,9 +330,19 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
return
getattr(instance, self.name).save(*formatted, save=save)
def set_activity_from_field(self, activity, instance):
value = getattr(instance, self.name)
if value is None:
return
alt_text = getattr(instance, self.alt_field)
formatted = self.field_to_activity(value, alt_text)
def field_to_activity(self, value):
return image_serializer(value)
key = self.get_activitypub_field()
activity[key] = formatted
def field_to_activity(self, value, alt=None):
return image_serializer(value, alt)
def field_from_activity(self, value):

View file

@ -86,11 +86,11 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
activity['name'] = self.pure_name
activity['type'] = self.pure_type
activity['attachment'] = [
image_serializer(b.cover) for b in self.mention_books.all() \
if b.cover]
image_serializer(b.cover, b.alt_text) \
for b in self.mention_books.all()[:4] if b.cover]
if hasattr(self, 'book'):
activity['attachment'].append(
image_serializer(self.book.cover)
image_serializer(self.book.cover, self.book.alt_text)
)
return activity

View file

@ -53,7 +53,8 @@ class User(OrderedCollectionPageMixin, AbstractUser):
# name is your display name, which you can change at will
name = fields.CharField(max_length=100, default='')
avatar = fields.ImageField(
upload_to='avatars/', blank=True, null=True, activitypub_field='icon')
upload_to='avatars/', blank=True, null=True,
activitypub_field='icon', alt_field='alt_text')
followers = fields.ManyToManyField(
'self',
link_only=True,
@ -90,6 +91,11 @@ class User(OrderedCollectionPageMixin, AbstractUser):
last_active_date = models.DateTimeField(auto_now=True)
manually_approves_followers = fields.BooleanField(default=False)
@property
def alt_text(self):
''' alt text with username '''
return 'avatar for %s' % (self.localname or self.username)
@property
def display_name(self):
''' show the cleanest version of the user's name possible '''

View file

@ -65,6 +65,7 @@ input.toggle-control:checked ~ .modal.toggle-content {
.cover-container {
height: 250px;
width: max-content;
max-width: 250px;
}
.cover-container.is-medium {
height: 150px;

View file

@ -1,3 +1,3 @@
{% load bookwyrm_tags %}
<img class="avatar image {% if large %}is-96x96{% else %}is-32x32{% endif %}" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}" alt="avatar for {{ user|username }}">
<img class="avatar image {% if large %}is-96x96{% else %}is-32x32{% endif %}" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}" alt="{{ user.alt_text }}">

View file

@ -1,13 +1,13 @@
{% load bookwyrm_tags %}
<div class="cover-container is-{{ size }}">
{% if book.cover %}
<img class="book-cover" src="/images/{{ book.cover }}" alt="{% include 'snippets/cover_alt.html' with book=book %}">
<img class="book-cover" src="/images/{{ book.cover }}" alt="{{ book.alt_text }}">
{% else %}
<div class="no-cover book-cover">
<img class="book-cover" src="/static/images/no_cover.jpg" alt="No cover">
<div>
<p>{{ book.title }}</p>
<p>({{ book|edition_info }})</p>
<p>({{ book.edition_info }})</p>
</div>
</div>
{% endif %}

View file

@ -1,4 +1,5 @@
{% with uuid as uuid %}
{% load bookwyrm_tags %}
{% with 0|uuid as uuid %}
<div class="control">
<div>
<input type="radio" class="toggle-control" name="sensitive" value="false" id="hide-spoilers-{{ uuid }}" {% if not parent_status.content_warning %}checked{% endif %}>

View file

@ -1,2 +0,0 @@
{% load bookwyrm_tags %}
'{{ book.title }}' Cover ({{ book|edition_info }})

View file

@ -4,7 +4,9 @@
{% for i in '12345'|make_list %}
<form name="rate" action="/rate/" method="POST" onsubmit="return rate_stars(event)">
{% csrf_token %}
<input type="hidden" name="user" value="{{ request.user.id }}">
<input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="privacy" value="public">
<input type="hidden" name="rating" value="{{ forloop.counter }}">
<button type="submit" class="icon icon-star-{% if book|rating:user < forloop.counter %}empty{% else %}full{% endif %}">
<span class="is-sr-only">{{ forloop.counter }} star{{ forloop.counter | pluralize }}</span>

View file

@ -97,20 +97,6 @@ def get_boosted(boost):
).get()
@register.filter(name='edition_info')
def get_edition_info(book):
''' paperback, French language, 1982 '''
if not book:
return ''
items = [
book.physical_format if isinstance(book, models.Edition) else None,
book.languages[0] + ' language' if book.languages and \
book.languages[0] != 'English' else None,
str(book.published_date.year) if book.published_date else None,
]
return ', '.join(i for i in items if i)
@register.filter(name='book_description')
def get_book_description(book):
''' use the work's text if the book doesn't have it '''

View file

@ -1,5 +1,7 @@
''' testing models '''
from dateutil.parser import parse
from django.test import TestCase
from django.utils import timezone
from bookwyrm import models, settings
from bookwyrm.models.book import isbn_10_to_13, isbn_13_to_10
@ -56,3 +58,27 @@ class Book(TestCase):
isbn_13 = '978-1788-16167-1'
isbn_10 = isbn_13_to_10(isbn_13)
self.assertEqual(isbn_10, '178816167X')
def test_get_edition_info(self):
''' text slug about an edition '''
book = models.Edition.objects.create(title='Test Edition')
self.assertEqual(book.edition_info, '')
book.physical_format = 'worm'
book.save()
self.assertEqual(book.edition_info, 'worm')
book.languages = ['English']
book.save()
self.assertEqual(book.edition_info, 'worm')
book.languages = ['Glorbish', 'English']
book.save()
self.assertEqual(book.edition_info, 'worm, Glorbish language')
book.published_date = timezone.make_aware(parse('2020'))
book.save()
self.assertEqual(book.edition_info, 'worm, Glorbish language, 2020')
self.assertEqual(
book.alt_text, 'Test Edition cover (worm, Glorbish language, 2020)')

View file

@ -393,17 +393,19 @@ class ActivitypubFields(TestCase):
ContentFile(output.getvalue())
)
output = fields.image_serializer(user.avatar)
output = fields.image_serializer(user.avatar, alt='alt text')
self.assertIsNotNone(
re.match(
r'.*\.jpg',
output.url,
)
)
self.assertEqual(output.name, 'alt text')
self.assertEqual(output.type, 'Image')
instance = fields.ImageField()
output = fields.image_serializer(user.avatar, alt=None)
self.assertEqual(instance.field_to_activity(user.avatar), output)
responses.add(

View file

@ -158,34 +158,6 @@ class TemplateTags(TestCase):
self.assertEqual(boosted, status)
def test_get_edition_info(self):
''' text slug about an edition '''
self.assertEqual(
bookwyrm_tags.get_edition_info(self.book), '')
self.book.physical_format = 'worm'
self.book.save()
self.assertEqual(
bookwyrm_tags.get_edition_info(self.book), 'worm')
self.book.languages = ['English']
self.book.save()
self.assertEqual(
bookwyrm_tags.get_edition_info(self.book), 'worm')
self.book.languages = ['Glorbish', 'English']
self.book.save()
self.assertEqual(
bookwyrm_tags.get_edition_info(self.book),
'worm, Glorbish language')
self.book.published_date = timezone.make_aware(parse('2020'))
self.book.save()
self.assertEqual(
bookwyrm_tags.get_edition_info(self.book),
'worm, Glorbish language, 2020')
def test_get_book_description(self):
''' grab it from the edition or the parent '''
work = models.Work.objects.create(title='Test Work')