Adds quotes

This commit is contained in:
Mouse Reeve 2020-04-08 09:40:47 -07:00
parent 5b7f29c45b
commit a88cf2b6dd
16 changed files with 190 additions and 7 deletions

View file

@ -9,6 +9,7 @@ from .shelve import get_add, get_remove
from .status import get_review, get_review_article
from .status import get_rating, get_rating_note
from .status import get_comment, get_comment_article
from .status import get_quotation, get_quotation_article
from .status import get_status, get_replies, get_replies_page
from .status import get_favorite, get_unfavorite
from .status import get_boost

View file

@ -14,6 +14,29 @@ def get_rating(review):
review.rating, review.book.title)
return status
def get_quotation(quotation):
''' fedireads json for quotations '''
status = get_status(quotation)
status['inReplyToBook'] = quotation.book.absolute_id
status['fedireadsType'] = quotation.status_type
status['quote'] = quotation.quote
return status
def get_quotation_article(quotation):
''' a book quotation formatted for a non-fedireads isntance (mastodon) '''
status = get_status(quotation)
content = '"%s"<br>-- <a href="%s">"%s"</a>)<br><br>%s' % (
quotation.quote,
quotation.book.absolute_id,
quotation.book.title,
quotation.content,
)
status['content'] = content
return status
def get_review(review):
''' fedireads json for book reviews '''
status = get_status(review)

View file

@ -53,6 +53,17 @@ class CommentForm(ModelForm):
}
class QuotationForm(ModelForm):
class Meta:
model = models.Quotation
fields = ['quote', 'content']
help_texts = {f: None for f in fields}
labels = {
'quote': 'Quote',
'content': 'Comment',
}
class ReplyForm(ModelForm):
class Meta:
model = models.Status

View file

@ -0,0 +1,26 @@
# Generated by Django 3.0.3 on 2020-04-07 00:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('fedireads', '0029_auto_20200403_1835'),
]
operations = [
migrations.CreateModel(
name='Quotation',
fields=[
('status_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fedireads.Status')),
('quote', models.TextField()),
('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fedireads.Edition')),
],
options={
'abstract': False,
},
bases=('fedireads.status',),
),
]

View file

@ -1,6 +1,7 @@
''' bring all the models into the app namespace '''
from .book import Connector, Book, Work, Edition, Author
from .shelf import Shelf, ShelfBook
from .status import Status, Review, Comment, Favorite, Boost, Tag, Notification
from .status import Status, Review, Comment, Quotation
from .status import Favorite, Boost, Tag, Notification
from .user import User, UserFollows, UserFollowRequest, UserBlocks
from .user import FederatedServer

View file

@ -56,6 +56,17 @@ class Comment(Status):
super().save(*args, **kwargs)
class Quotation(Status):
''' like a review but without a rating and transient '''
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
quote = models.TextField()
def save(self, *args, **kwargs):
self.status_type = 'Quotation'
self.activity_type = 'Note'
super().save(*args, **kwargs)
class Review(Status):
''' a book review '''
name = models.CharField(max_length=255, null=True)

View file

@ -9,7 +9,8 @@ import requests
from fedireads import activitypub
from fedireads import models
from fedireads.broadcast import get_recipients, broadcast
from fedireads.status import create_review, create_status, create_comment
from fedireads.status import create_review, create_status
from fedireads.status import create_quotation, create_comment
from fedireads.status import create_tag, create_notification, create_rating
from fedireads.remote_user import get_or_create_remote_user
@ -222,6 +223,24 @@ def handle_review(user, book, name, content, rating):
broadcast(user, article_create_activity, other_recipients)
def handle_quotation(user, book, content, quote):
''' post a review '''
# validated and saves the review in the database so it has an id
quotation = create_quotation(user, book, content, quote)
quotation_activity = activitypub.get_quotation(quotation)
quotation_create_activity = activitypub.get_create(user, quotation_activity)
fr_recipients = get_recipients(user, 'public', limit='fedireads')
broadcast(user, quotation_create_activity, fr_recipients)
# re-format the activity for non-fedireads servers
article_activity = activitypub.get_quotation_article(quotation)
article_create_activity = activitypub.get_create(user, article_activity)
other_recipients = get_recipients(user, 'public', limit='other')
broadcast(user, article_create_activity, other_recipients)
def handle_comment(user, book, content):
''' post a review '''
# validated and saves the review in the database so it has an id

View file

@ -222,6 +222,9 @@ body {
width: 30rem;
height: 10rem;
}
.review-form.quote-form textarea#id_content {
height: 4rem;
}
@ -578,12 +581,33 @@ input:checked ~ .compose-suggestion {
blockquote {
white-space: pre-line;
}
blockquote .icon-quote-open {
float: left;
blockquote .icon-quote-open, blockquote .icon-quote-close, .quote .icon-quote-open, .quote .icon-quote-close {
font-size: 2rem;
margin-right: 0.5rem;
color: #888;
}
blockquote .icon-quote-open, .quote .icon-quote-close {
float: left;
}
.quote {
margin-bottom: 2em;
position: relative;
}
.quote .icon-quote-open, .quote .icon-quote-close {
position: absolute;
}
.quote .icon-quote-open {
top: -0.5rem;
}
.quote .icon-quote-close {
right: 0;
bottom: 1.5rem;
}
.quote blockquote {
background-color: white;
margin: 1em;
padding: 1em;
}
.interaction {
background-color: #B2DBBF;

View file

@ -56,6 +56,38 @@ def create_review(user, book, name, content, rating):
)
def create_quotation_from_activity(author, activity):
''' parse an activity json blob into a status '''
book = activity['inReplyToBook']
book = book.split('/')[-1]
quote = activity.get('quote')
content = activity.get('content')
published = activity.get('published')
remote_id = activity['id']
quotation = create_quotation(author, book, content, quote)
quotation.published_date = published
quotation.remote_id = remote_id
quotation.save()
return quotation
def create_quotation(user, possible_book, content, quote):
''' a quotation has been added '''
# throws a value error if the book is not found
book = get_or_create_book(possible_book)
content = sanitize(content)
quote = sanitize(quote)
return models.Quotation.objects.create(
user=user,
book=book,
content=content,
quote=quote,
)
def create_comment_from_activity(author, activity):
''' parse an activity json blob into a status '''
book = activity['inReplyToBook']

View file

@ -15,8 +15,8 @@ a <a href="/book/{{ book.fedireads_key }}">{{ book.title }}</a>
<div class="tab" data-id="tab-comment-{{ book.id }}" data-category="tab-option-{{ book.id }}">
<a href="{{ book.absolute_id }}/comment" onclick="tabChange(event)">Comment</a>
</div>
<div class="tab" data-id="tab-quote-{{ book.id }}" data-category="tab-option-{{ book.id }}">
Quote
<div class="tab" data-id="tab-quotation-{{ book.id }}" data-category="tab-option-{{ book.id }}">
<a href="{{ book.absolute_id }}/quotation" onclick="tabChange(event)">Quote</a>
</div>
</div>
@ -38,4 +38,11 @@ a <a href="/book/{{ book.fedireads_key }}">{{ book.title }}</a>
{{ comment_form.as_p }}
<button type="submit">post comment</button>
</form>
<form class="hidden tab-option-{{ book.id }} review-form quote-form" name="quotation" action="/quotate/" method="post" id="tab-quotation-{{ book.id }}">
{% csrf_token %}
<input type="hidden" name="book" value="{{ book.fedireads_key }}"></input>
{{ quotation_form.as_p }}
<button typr="submit">post quote</button>
</form>
</div>

View file

@ -40,6 +40,16 @@
</h3>
{% endif %}
{% if status.quote %}
<div class="quote">
<span class="icon icon-quote-open"></span>
<blockquote>{{ status.quote }}</blockquote>
<span class="icon icon-quote-close"></span>
<p> &mdash; {% include 'snippets/book_titleby.html' with book=status.book %}</p>
</div>
{% endif %}
{% if status.content and status.status_type != 'Update' and status.status_type != 'Boost' %}
<blockquote>{{ status.content | safe }}</blockquote>
{% endif %}

View file

@ -11,6 +11,8 @@
reviewed <a href="{{ status.book.absolute_id }}">{{ status.book.title }}</a>
{% elif status.status_type == 'Comment' %}
commented on <a href="{{ status.book.absolute_id }}">{{ status.book.title }}</a>
{% elif status.status_type == 'Quotation' %}
quoted <a href="{{ status.book.absolute_id }}">{{ status.book.title }}</a>
{% elif status.status_type == 'Boost' %}
boosted
{% elif status.reply_parent %}

View file

@ -80,6 +80,7 @@ urlpatterns = [
re_path(r'^rate/?$', actions.rate),
re_path(r'^review/?$', actions.review),
re_path(r'^quotate/?$', actions.quotate),
re_path(r'^comment/?$', actions.comment),
re_path(r'^tag/?$', actions.tag),
re_path(r'^untag/?$', actions.untag),

View file

@ -228,6 +228,21 @@ def review(request):
return redirect('/book/%s' % book_identifier)
@login_required
def quotate(request):
''' create a book quotation '''
form = forms.QuotationForm(request.POST)
book_identifier = request.POST.get('book')
if not form.is_valid():
return redirect('/book/%s' % book_identifier)
quote = form.cleaned_data.get('quote')
content = form.cleaned_data.get('content')
outgoing.handle_quotation(request.user, book_identifier, content, quote)
return redirect('/book/%s' % book_identifier)
@login_required
def comment(request):
''' create a book comment '''

View file

@ -92,6 +92,7 @@ def home_tab(request, tab):
],
'active_tab': tab,
'review_form': forms.ReviewForm(),
'quotation_form': forms.QuotationForm(),
'comment_form': forms.CommentForm(),
'next': next_page if activity_count > (page_size * page) else None,
'prev': prev_page if page > 1 else None,

1
fr-dev
View file

@ -36,6 +36,5 @@ case "$1" in
;;
*)
echo "Unrecognised command. Try: up, initdb, resetdb,makemigrations, migrate, shell, dbshell "
docker-compose build
;;
esac