mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-20 08:31:07 +00:00
commit
62d3faa797
15 changed files with 72 additions and 35 deletions
|
@ -18,13 +18,13 @@ class PublicKey(ActivityObject):
|
||||||
class Person(ActivityObject):
|
class Person(ActivityObject):
|
||||||
''' actor activitypub json '''
|
''' actor activitypub json '''
|
||||||
preferredUsername: str
|
preferredUsername: str
|
||||||
name: str
|
|
||||||
inbox: str
|
inbox: str
|
||||||
outbox: str
|
outbox: str
|
||||||
followers: str
|
followers: str
|
||||||
summary: str
|
|
||||||
publicKey: PublicKey
|
publicKey: PublicKey
|
||||||
endpoints: Dict
|
endpoints: Dict
|
||||||
|
name: str = None
|
||||||
|
summary: str = None
|
||||||
icon: Image = field(default_factory=lambda: {})
|
icon: Image = field(default_factory=lambda: {})
|
||||||
bookwyrmUser: bool = False
|
bookwyrmUser: bool = False
|
||||||
manuallyApprovesFollowers: str = False
|
manuallyApprovesFollowers: str = False
|
||||||
|
|
24
bookwyrm/migrations/0027_auto_20201220_2007.py
Normal file
24
bookwyrm/migrations/0027_auto_20201220_2007.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 3.0.7 on 2020-12-20 20:07
|
||||||
|
|
||||||
|
import bookwyrm.models.fields
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bookwyrm', '0026_status_content_warning'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='name',
|
||||||
|
field=bookwyrm.models.fields.CharField(blank=True, max_length=100, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='user',
|
||||||
|
name='summary',
|
||||||
|
field=bookwyrm.models.fields.HtmlField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -28,11 +28,10 @@ class Author(ActivitypubMixin, BookWyrmModel):
|
||||||
bio = fields.HtmlField(null=True, blank=True)
|
bio = fields.HtmlField(null=True, blank=True)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
''' can't be abstract for query reasons, but you shouldn't USE it '''
|
''' handle remote vs origin ids '''
|
||||||
if self.id and not self.remote_id:
|
if self.id:
|
||||||
self.remote_id = self.get_remote_id()
|
self.remote_id = self.get_remote_id()
|
||||||
|
else:
|
||||||
if not self.id:
|
|
||||||
self.origin_id = self.remote_id
|
self.origin_id = self.remote_id
|
||||||
self.remote_id = None
|
self.remote_id = None
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
|
@ -83,10 +83,9 @@ class Book(ActivitypubMixin, BookWyrmModel):
|
||||||
if not isinstance(self, Edition) and not isinstance(self, Work):
|
if not isinstance(self, Edition) and not isinstance(self, Work):
|
||||||
raise ValueError('Books should be added as Editions or Works')
|
raise ValueError('Books should be added as Editions or Works')
|
||||||
|
|
||||||
if self.id and not self.remote_id:
|
if self.id:
|
||||||
self.remote_id = self.get_remote_id()
|
self.remote_id = self.get_remote_id()
|
||||||
|
else:
|
||||||
if not self.id:
|
|
||||||
self.origin_id = self.remote_id
|
self.origin_id = self.remote_id
|
||||||
self.remote_id = None
|
self.remote_id = None
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
''' models for storing different kinds of Activities '''
|
''' models for storing different kinds of Activities '''
|
||||||
from dataclasses import MISSING
|
from dataclasses import MISSING
|
||||||
|
import re
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -155,7 +157,7 @@ class Comment(Status):
|
||||||
@property
|
@property
|
||||||
def pure_content(self):
|
def pure_content(self):
|
||||||
''' indicate the book in question for mastodon (or w/e) users '''
|
''' indicate the book in question for mastodon (or w/e) users '''
|
||||||
return '<p>%s</p><p>(comment on <a href="%s">"%s"</a>)</p>' % \
|
return '%s<p>(comment on <a href="%s">"%s"</a>)</p>' % \
|
||||||
(self.content, self.book.remote_id, self.book.title)
|
(self.content, self.book.remote_id, self.book.title)
|
||||||
|
|
||||||
activity_serializer = activitypub.Comment
|
activity_serializer = activitypub.Comment
|
||||||
|
@ -171,8 +173,10 @@ class Quotation(Status):
|
||||||
@property
|
@property
|
||||||
def pure_content(self):
|
def pure_content(self):
|
||||||
''' indicate the book in question for mastodon (or w/e) users '''
|
''' indicate the book in question for mastodon (or w/e) users '''
|
||||||
return '<p>"%s"<br>-- <a href="%s">"%s"</a></p><p>%s</p>' % (
|
quote = re.sub(r'^<p>', '<p>"', self.quote)
|
||||||
self.quote,
|
quote = re.sub(r'</p>$', '"</p>', quote)
|
||||||
|
return '%s <p>-- <a href="%s">"%s"</a></p>%s' % (
|
||||||
|
quote,
|
||||||
self.book.remote_id,
|
self.book.remote_id,
|
||||||
self.book.title,
|
self.book.title,
|
||||||
self.content,
|
self.content,
|
||||||
|
|
|
@ -42,7 +42,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
outbox = fields.RemoteIdField(unique=True)
|
outbox = fields.RemoteIdField(unique=True)
|
||||||
summary = fields.HtmlField(default='')
|
summary = fields.HtmlField(null=True, blank=True)
|
||||||
local = models.BooleanField(default=False)
|
local = models.BooleanField(default=False)
|
||||||
bookwyrm_user = fields.BooleanField(default=True)
|
bookwyrm_user = fields.BooleanField(default=True)
|
||||||
localname = models.CharField(
|
localname = models.CharField(
|
||||||
|
@ -51,7 +51,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
unique=True
|
unique=True
|
||||||
)
|
)
|
||||||
# name is your display name, which you can change at will
|
# name is your display name, which you can change at will
|
||||||
name = fields.CharField(max_length=100, default='')
|
name = fields.CharField(max_length=100, null=True, blank=True)
|
||||||
avatar = fields.ImageField(
|
avatar = fields.ImageField(
|
||||||
upload_to='avatars/', blank=True, null=True,
|
upload_to='avatars/', blank=True, null=True,
|
||||||
activitypub_field='icon', alt_field='alt_text')
|
activitypub_field='icon', alt_field='alt_text')
|
||||||
|
@ -100,7 +100,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
@property
|
@property
|
||||||
def display_name(self):
|
def display_name(self):
|
||||||
''' show the cleanest version of the user's name possible '''
|
''' show the cleanest version of the user's name possible '''
|
||||||
if self.name != '':
|
if self.name and self.name != '':
|
||||||
return self.name
|
return self.name
|
||||||
return self.localname or self.username
|
return self.localname or self.username
|
||||||
|
|
||||||
|
|
|
@ -221,8 +221,6 @@ def handle_status(user, form):
|
||||||
matches = []
|
matches = []
|
||||||
for match in re.finditer(regex.username, status.content):
|
for match in re.finditer(regex.username, status.content):
|
||||||
username = match.group().strip().split('@')[1:]
|
username = match.group().strip().split('@')[1:]
|
||||||
print(match.group())
|
|
||||||
print(len(username))
|
|
||||||
if len(username) == 1:
|
if len(username) == 1:
|
||||||
# this looks like a local user (@user), fill in the domain
|
# this looks like a local user (@user), fill in the domain
|
||||||
username.append(DOMAIN)
|
username.append(DOMAIN)
|
||||||
|
@ -251,9 +249,9 @@ def handle_status(user, form):
|
||||||
r'<a href="%s">%s</a>\g<1>' % (url, username),
|
r'<a href="%s">%s</a>\g<1>' % (url, username),
|
||||||
content)
|
content)
|
||||||
if not isinstance(status, models.GeneratedNote):
|
if not isinstance(status, models.GeneratedNote):
|
||||||
status.content = to_markown(content)
|
status.content = to_markdown(content)
|
||||||
if hasattr(status, 'quote'):
|
if hasattr(status, 'quote'):
|
||||||
status.quote = to_markown(status.quote)
|
status.quote = to_markdown(status.quote)
|
||||||
status.save()
|
status.save()
|
||||||
|
|
||||||
# notify reply parent or tagged users
|
# notify reply parent or tagged users
|
||||||
|
@ -272,7 +270,7 @@ def handle_status(user, form):
|
||||||
broadcast(user, remote_activity, software='other')
|
broadcast(user, remote_activity, software='other')
|
||||||
|
|
||||||
|
|
||||||
def to_markown(content):
|
def to_markdown(content):
|
||||||
''' catch links and convert to markdown '''
|
''' catch links and convert to markdown '''
|
||||||
content = re.sub(
|
content = re.sub(
|
||||||
r'([^(href=")])(https?:\/\/([A-Za-z\.\-_\/]+' \
|
r'([^(href=")])(https?:\/\/([A-Za-z\.\-_\/]+' \
|
||||||
|
|
|
@ -6,18 +6,18 @@
|
||||||
{% if trimmed != full %}
|
{% if trimmed != full %}
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" name="show-hide-{{ uuid }}" id="show-{{ uuid }}" class="toggle-control" checked>
|
<input type="radio" name="show-hide-{{ uuid }}" id="show-{{ uuid }}" class="toggle-control" checked>
|
||||||
<blockquote class="content toggle-content hidden">{{ trimmed }}
|
<blockquote class="content toggle-content hidden">{{ trimmed | to_markdown | safe }}
|
||||||
<label class="button is-small" for="hide-{{ uuid }}"><div role="button" tabindex="0">show more</div></label>
|
<label class="button is-small" for="hide-{{ uuid }}"><div role="button" tabindex="0">show more</div></label>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" name="show-hide-{{ uuid }}" id="hide-{{ uuid }}" class="toggle-control">
|
<input type="radio" name="show-hide-{{ uuid }}" id="hide-{{ uuid }}" class="toggle-control">
|
||||||
<blockquote class="content toggle-content hidden">{{ full }}
|
<blockquote class="content toggle-content hidden">{{ full | to_markdown | safe }}
|
||||||
<label class="button is-small" for="show-{{ uuid }}"><div role="button" tabindex="0">show less</div></label>
|
<label class="button is-small" for="show-{{ uuid }}"><div role="button" tabindex="0">show less</div></label>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<blockquote class="content">{{ full }}</blockquote>
|
<blockquote class="content">{{ full | to_markdown | safe }}</blockquote>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,14 @@
|
||||||
|
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{% if user.summary %}
|
{% if user.summary %}
|
||||||
<blockquote><span class="icon icon-quote-open"></span>{{ user.summary | safe }}</blockquote>
|
<div class="columns">
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<span class="icon icon-quote-open"></span>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<blockquote>{{ user.summary | to_markdown | safe }}</blockquote>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django import template
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
|
from bookwyrm.outgoing import to_markdown
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
@ -132,6 +133,13 @@ def time_since(date):
|
||||||
return '%ds' % delta.seconds
|
return '%ds' % delta.seconds
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="to_markdown")
|
||||||
|
def get_markdown(content):
|
||||||
|
''' convert markdown to html '''
|
||||||
|
if content:
|
||||||
|
return to_markdown(content)
|
||||||
|
return None
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def active_shelf(context, book):
|
def active_shelf(context, book):
|
||||||
''' check what shelf a user has a book on, if any '''
|
''' check what shelf a user has a book on, if any '''
|
||||||
|
|
|
@ -103,7 +103,7 @@ class BaseActivity(TestCase):
|
||||||
|
|
||||||
def test_to_model_simple_fields(self):
|
def test_to_model_simple_fields(self):
|
||||||
''' test setting simple fields '''
|
''' test setting simple fields '''
|
||||||
self.assertEqual(self.user.name, '')
|
self.assertIsNone(self.user.name)
|
||||||
|
|
||||||
activity = activitypub.Person(
|
activity = activitypub.Person(
|
||||||
id=self.user.remote_id,
|
id=self.user.remote_id,
|
||||||
|
|
|
@ -167,8 +167,7 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['type'], 'Note')
|
self.assertEqual(activity['type'], 'Note')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
activity['content'],
|
activity['content'],
|
||||||
'<p>test content</p><p>' \
|
'test content<p>(comment on <a href="%s">"Test Edition"</a>)</p>' %
|
||||||
'(comment on <a href="%s">"Test Edition"</a>)</p>' %
|
|
||||||
self.book.remote_id)
|
self.book.remote_id)
|
||||||
self.assertEqual(activity['attachment'][0].type, 'Image')
|
self.assertEqual(activity['attachment'][0].type, 'Image')
|
||||||
self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \
|
self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \
|
||||||
|
@ -198,8 +197,8 @@ class Status(TestCase):
|
||||||
self.assertEqual(activity['type'], 'Note')
|
self.assertEqual(activity['type'], 'Note')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
activity['content'],
|
activity['content'],
|
||||||
'<p>"a sickening sense"<br>-- <a href="%s">"Test Edition"</a></p>' \
|
'a sickening sense <p>-- <a href="%s">"Test Edition"</a></p>' \
|
||||||
'<p>test content</p>' % self.book.remote_id)
|
'test content' % self.book.remote_id)
|
||||||
self.assertEqual(activity['attachment'][0].type, 'Image')
|
self.assertEqual(activity['attachment'][0].type, 'Image')
|
||||||
self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \
|
self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \
|
||||||
(settings.DOMAIN, self.book.cover.url))
|
(settings.DOMAIN, self.book.cover.url))
|
||||||
|
|
|
@ -464,7 +464,7 @@ class Incoming(TestCase):
|
||||||
'data/ap_user.json')
|
'data/ap_user.json')
|
||||||
userdata = json.loads(datafile.read_bytes())
|
userdata = json.loads(datafile.read_bytes())
|
||||||
del userdata['icon']
|
del userdata['icon']
|
||||||
self.assertEqual(self.local_user.name, '')
|
self.assertIsNone(self.local_user.name)
|
||||||
incoming.handle_update_user({'object': userdata})
|
incoming.handle_update_user({'object': userdata})
|
||||||
user = models.User.objects.get(id=self.local_user.id)
|
user = models.User.objects.get(id=self.local_user.id)
|
||||||
self.assertEqual(user.name, 'MOUSE?? MOUSE!!')
|
self.assertEqual(user.name, 'MOUSE?? MOUSE!!')
|
||||||
|
|
|
@ -17,10 +17,8 @@ from django.template.response import TemplateResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.views.decorators.http import require_GET, require_POST
|
from django.views.decorators.http import require_GET, require_POST
|
||||||
|
|
||||||
from bookwyrm import books_manager
|
from bookwyrm import books_manager, forms, models, outgoing, goodreads_import
|
||||||
from bookwyrm.broadcast import broadcast
|
from bookwyrm.broadcast import broadcast
|
||||||
from bookwyrm import forms, models, outgoing
|
|
||||||
from bookwyrm import goodreads_import
|
|
||||||
from bookwyrm.emailing import password_reset_email
|
from bookwyrm.emailing import password_reset_email
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
from bookwyrm.views import get_user_from_username
|
from bookwyrm.views import get_user_from_username
|
||||||
|
@ -244,7 +242,7 @@ def edit_book(request, book_id):
|
||||||
'form': form
|
'form': form
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'edit_book.html', data)
|
return TemplateResponse(request, 'edit_book.html', data)
|
||||||
form.save()
|
book = form.save()
|
||||||
|
|
||||||
outgoing.handle_update_book(request.user, book)
|
outgoing.handle_update_book(request.user, book)
|
||||||
return redirect('/book/%s' % book.id)
|
return redirect('/book/%s' % book.id)
|
||||||
|
|
|
@ -684,7 +684,8 @@ def author_page(request, author_id):
|
||||||
if is_api_request(request):
|
if is_api_request(request):
|
||||||
return JsonResponse(author.to_activity(), encoder=ActivityEncoder)
|
return JsonResponse(author.to_activity(), encoder=ActivityEncoder)
|
||||||
|
|
||||||
books = models.Work.objects.filter(authors=author)
|
books = models.Work.objects.filter(
|
||||||
|
Q(authors=author) | Q(editions__authors=author)).distinct()
|
||||||
data = {
|
data = {
|
||||||
'title': author.name,
|
'title': author.name,
|
||||||
'author': author,
|
'author': author,
|
||||||
|
|
Loading…
Reference in a new issue