mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-12-16 21:26:33 +00:00
Merge branch 'main' into markdown
This commit is contained in:
commit
117e6a08b2
13 changed files with 196 additions and 54 deletions
|
@ -35,6 +35,7 @@ class Note(ActivityObject):
|
|||
tag: List[Link] = field(default_factory=lambda: [])
|
||||
attachment: List[Document] = field(default_factory=lambda: [])
|
||||
sensitive: bool = False
|
||||
updated: str = None
|
||||
type: str = "Note"
|
||||
|
||||
|
||||
|
|
|
@ -69,8 +69,9 @@ class Update(Verb):
|
|||
|
||||
def action(self):
|
||||
"""update a model instance from the dataclass"""
|
||||
if self.object:
|
||||
self.object.to_model(allow_create=False)
|
||||
if not self.object:
|
||||
return
|
||||
self.object.to_model(allow_create=False)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Generated by Django 3.2.5 on 2021-10-15 00:28
|
||||
# Generated by Django 3.2.5 on 2021-10-15 15:54
|
||||
|
||||
from django.db import migrations, models
|
||||
import bookwyrm.models.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -12,7 +13,7 @@ class Migration(migrations.Migration):
|
|||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="status",
|
||||
name="edited",
|
||||
field=models.BooleanField(default=False),
|
||||
name="edited_date",
|
||||
field=bookwyrm.models.fields.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
]
|
|
@ -44,7 +44,9 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
|||
published_date = fields.DateTimeField(
|
||||
default=timezone.now, activitypub_field="published"
|
||||
)
|
||||
edited = models.BooleanField(default=False)
|
||||
edited_date = fields.DateTimeField(
|
||||
blank=True, null=True, activitypub_field="updated"
|
||||
)
|
||||
deleted = models.BooleanField(default=False)
|
||||
deleted_date = models.DateTimeField(blank=True, null=True)
|
||||
favorites = models.ManyToManyField(
|
||||
|
|
|
@ -9,12 +9,12 @@ from django.utils.translation import gettext_lazy as _
|
|||
env = Env()
|
||||
env.read_env()
|
||||
DOMAIN = env("DOMAIN")
|
||||
VERSION = "0.0.1"
|
||||
VERSION = "0.1.0"
|
||||
|
||||
PAGE_LENGTH = env("PAGE_LENGTH", 15)
|
||||
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
|
||||
|
||||
JS_CACHE = "c02929b1"
|
||||
JS_CACHE = "3eb4edb1"
|
||||
|
||||
# email
|
||||
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
|
||||
|
|
|
@ -509,6 +509,20 @@ ol.ordered-list li::before {
|
|||
border-left: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
/* Breadcrumbs
|
||||
******************************************************************************/
|
||||
|
||||
.breadcrumb li:first-child * {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.breadcrumb li > * {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0 0.75em;
|
||||
}
|
||||
|
||||
/* Dimensions
|
||||
* @todo These could be in rem.
|
||||
******************************************************************************/
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% spaceless %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if book.isbn13 or book.oclc_number or book.asin %}
|
||||
{% if book.isbn_13 or book.oclc_number or book.asin %}
|
||||
<dl>
|
||||
{% if book.isbn_13 %}
|
||||
<div class="is-flex">
|
||||
|
|
|
@ -1,7 +1,19 @@
|
|||
{% spaceless %}
|
||||
|
||||
{% load humanize %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if total_pages %}
|
||||
{% blocktrans with page=page|intcomma total_pages=total_pages|intcomma %}page {{ page }} of {{ total_pages }}{% endblocktrans %}
|
||||
|
||||
{% blocktrans trimmed with page=page|intcomma total_pages=total_pages|intcomma %}
|
||||
page {{ page }} of {{ total_pages }}
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
{% blocktrans with page=page|intcomma %}page {{ page }}{% endblocktrans %}
|
||||
|
||||
{% blocktrans trimmed with page=page|intcomma %}
|
||||
page {{ page }}
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
{% endspaceless %}
|
||||
|
|
|
@ -30,22 +30,39 @@
|
|||
</span>
|
||||
|
||||
{% include "snippets/status/header_content.html" %}
|
||||
{% if status.edited %}
|
||||
<small>(edited)</small>
|
||||
{% endif %}
|
||||
</h3>
|
||||
<p class="is-size-7 is-flex is-align-items-center">
|
||||
<a href="{{ status.remote_id }}{% if status.user.local %}#anchor-{{ status.id }}{% endif %}">{{ status.published_date|published_date }}</a>
|
||||
{% if status.progress %}
|
||||
<span class="ml-1">
|
||||
{% if status.progress_mode == 'PG' %}
|
||||
({% include 'snippets/page_text.html' with page=status.progress total_pages=status.book.pages %})
|
||||
{% else %}
|
||||
({{ status.progress }}%)
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% include 'snippets/privacy-icons.html' with item=status %}
|
||||
</p>
|
||||
<div class="breadcrumb has-dot-separator is-small">
|
||||
<ul class="is-flex is-align-items-center">
|
||||
<li>
|
||||
<a href="{{ status.remote_id }}{% if status.user.local %}#anchor-{{ status.id }}{% endif %}">
|
||||
{{ status.published_date|published_date }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if status.edited_date %}
|
||||
<li>
|
||||
<span>
|
||||
{% blocktrans with date=status.edited_date|published_date %}edited {{ date }}{% endblocktrans %}
|
||||
</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if status.progress %}
|
||||
<li class="ml-1">
|
||||
<span>
|
||||
{% if status.progress_mode == 'PG' %}
|
||||
{% include 'snippets/page_text.html' with page=status.progress total_pages=status.book.pages %}
|
||||
{% else %}
|
||||
{{ status.progress }}%
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li>
|
||||
{% include 'snippets/privacy-icons.html' with item=status %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block card-bonus %}
|
||||
{% if request.user.is_authenticated and not moderation_mode %}
|
||||
{% if request.user.is_authenticated and not moderation_mode and not no_interact %}
|
||||
{% with status.id|uuid as uuid %}
|
||||
<section class="reply-panel is-hidden" id="show_comment_{{ status.id }}">
|
||||
<div class="card-footer">
|
||||
|
|
|
@ -37,9 +37,9 @@ class InboxUpdate(TestCase):
|
|||
outbox="https://example.com/users/rat/outbox",
|
||||
)
|
||||
|
||||
self.create_json = {
|
||||
self.update_json = {
|
||||
"id": "hi",
|
||||
"type": "Create",
|
||||
"type": "Update",
|
||||
"actor": "hi",
|
||||
"to": ["https://www.w3.org/ns/activitystreams#public"],
|
||||
"cc": ["https://example.com/user/mouse/followers"],
|
||||
|
@ -54,26 +54,20 @@ class InboxUpdate(TestCase):
|
|||
book_list = models.List.objects.create(
|
||||
name="hi", remote_id="https://example.com/list/22", user=self.local_user
|
||||
)
|
||||
activity = {
|
||||
"type": "Update",
|
||||
"to": [],
|
||||
"cc": [],
|
||||
"actor": "hi",
|
||||
"id": "sdkjf",
|
||||
"object": {
|
||||
"id": "https://example.com/list/22",
|
||||
"type": "BookList",
|
||||
"totalItems": 1,
|
||||
"first": "https://example.com/list/22?page=1",
|
||||
"last": "https://example.com/list/22?page=1",
|
||||
"name": "Test List",
|
||||
"owner": "https://example.com/user/mouse",
|
||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"cc": ["https://example.com/user/mouse/followers"],
|
||||
"summary": "summary text",
|
||||
"curation": "curated",
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
},
|
||||
activity = self.update_json
|
||||
activity["object"] = {
|
||||
"id": "https://example.com/list/22",
|
||||
"type": "BookList",
|
||||
"totalItems": 1,
|
||||
"first": "https://example.com/list/22?page=1",
|
||||
"last": "https://example.com/list/22?page=1",
|
||||
"name": "Test List",
|
||||
"owner": "https://example.com/user/mouse",
|
||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"cc": ["https://example.com/user/mouse/followers"],
|
||||
"summary": "summary text",
|
||||
"curation": "curated",
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
}
|
||||
views.inbox.activity_task(activity)
|
||||
book_list.refresh_from_db()
|
||||
|
@ -176,3 +170,26 @@ class InboxUpdate(TestCase):
|
|||
)
|
||||
book = models.Work.objects.get(id=book.id)
|
||||
self.assertEqual(book.title, "Piranesi")
|
||||
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_update_status(self, *_):
|
||||
"""edit a status"""
|
||||
status = models.Status.objects.create(user=self.remote_user, content="hi")
|
||||
|
||||
datafile = pathlib.Path(__file__).parent.joinpath("../../data/ap_note.json")
|
||||
status_data = json.loads(datafile.read_bytes())
|
||||
status_data["id"] = status.remote_id
|
||||
status_data["updated"] = "2021-12-13T05:09:29Z"
|
||||
|
||||
activity = self.update_json
|
||||
activity["object"] = status_data
|
||||
|
||||
with patch("bookwyrm.activitypub.base_activity.set_related_field.delay"):
|
||||
views.inbox.activity_task(activity)
|
||||
|
||||
status.refresh_from_db()
|
||||
self.assertEqual(status.content, "test content in note")
|
||||
self.assertEqual(status.edited_date.year, 2021)
|
||||
self.assertEqual(status.edited_date.month, 12)
|
||||
self.assertEqual(status.edited_date.day, 13)
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.test.client import RequestFactory
|
|||
|
||||
from bookwyrm import forms, models, views
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from bookwyrm.tests.validate_html import validate_html
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
|
@ -70,6 +71,7 @@ class StatusViews(TestCase):
|
|||
self.assertEqual(status.content, "<p>hi</p>")
|
||||
self.assertEqual(status.user, self.local_user)
|
||||
self.assertEqual(status.book, self.book)
|
||||
self.assertIsNone(status.edited_date)
|
||||
|
||||
def test_handle_status_reply(self, *_):
|
||||
"""create a status in reply to an existing status"""
|
||||
|
@ -346,3 +348,75 @@ http://www.fish.com/"""
|
|||
self.assertEqual(activity["object"]["type"], "Tombstone")
|
||||
status.refresh_from_db()
|
||||
self.assertTrue(status.deleted)
|
||||
|
||||
def test_edit_status_get(self, *_):
|
||||
"""load the edit status view"""
|
||||
view = views.EditStatus.as_view()
|
||||
status = models.Comment.objects.create(
|
||||
content="status", user=self.local_user, book=self.book
|
||||
)
|
||||
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
result = view(request, status.id)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_edit_status_get_reply(self, *_):
|
||||
"""load the edit status view"""
|
||||
view = views.EditStatus.as_view()
|
||||
parent = models.Comment.objects.create(
|
||||
content="parent status", user=self.local_user, book=self.book
|
||||
)
|
||||
status = models.Status.objects.create(
|
||||
content="reply", user=self.local_user, reply_parent=parent
|
||||
)
|
||||
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
result = view(request, status.id)
|
||||
validate_html(result.render())
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_create_status_edit_success(self, mock, *_):
|
||||
"""update an existing status"""
|
||||
status = models.Status.objects.create(content="status", user=self.local_user)
|
||||
self.assertIsNone(status.edited_date)
|
||||
view = views.CreateStatus.as_view()
|
||||
form = forms.CommentForm(
|
||||
{
|
||||
"content": "hi",
|
||||
"user": self.local_user.id,
|
||||
"book": self.book.id,
|
||||
"privacy": "public",
|
||||
}
|
||||
)
|
||||
request = self.factory.post("", form.data)
|
||||
request.user = self.local_user
|
||||
|
||||
view(request, "comment", existing_status_id=status.id)
|
||||
activity = json.loads(mock.call_args_list[1][0][1])
|
||||
self.assertEqual(activity["type"], "Update")
|
||||
self.assertEqual(activity["object"]["id"], status.remote_id)
|
||||
|
||||
status.refresh_from_db()
|
||||
self.assertEqual(status.content, "<p>hi</p>")
|
||||
self.assertIsNotNone(status.edited_date)
|
||||
|
||||
def test_create_status_edit_permission_denied(self, *_):
|
||||
"""update an existing status"""
|
||||
status = models.Status.objects.create(content="status", user=self.local_user)
|
||||
view = views.CreateStatus.as_view()
|
||||
form = forms.CommentForm(
|
||||
{
|
||||
"content": "hi",
|
||||
"user": self.local_user.id,
|
||||
"book": self.book.id,
|
||||
"privacy": "public",
|
||||
}
|
||||
)
|
||||
request = self.factory.post("", form.data)
|
||||
request.user = self.remote_user
|
||||
|
||||
with self.assertRaises(PermissionDenied):
|
||||
view(request, "comment", existing_status_id=status.id)
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError
|
|||
from django.http import HttpResponse, HttpResponseBadRequest, Http404
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.decorators.http import require_POST
|
||||
|
@ -33,8 +34,9 @@ class EditStatus(View):
|
|||
)
|
||||
status.raise_not_editable(request.user)
|
||||
|
||||
status_type = "reply" if status.reply_parent else status.status_type.lower()
|
||||
data = {
|
||||
"type": status.status_type.lower(),
|
||||
"type": status_type,
|
||||
"book": getattr(status, "book", None),
|
||||
"draft": status,
|
||||
}
|
||||
|
@ -54,13 +56,14 @@ class CreateStatus(View):
|
|||
|
||||
def post(self, request, status_type, existing_status_id=None):
|
||||
"""create status of whatever type"""
|
||||
created = not existing_status_id
|
||||
existing_status = None
|
||||
if existing_status_id:
|
||||
existing_status = get_object_or_404(
|
||||
models.Status.objects.select_subclasses(), id=existing_status_id
|
||||
)
|
||||
existing_status.raise_not_editable(request.user)
|
||||
existing_status.edited = True
|
||||
existing_status.edited_date = timezone.now()
|
||||
|
||||
status_type = status_type[0].upper() + status_type[1:]
|
||||
|
||||
|
@ -112,7 +115,7 @@ class CreateStatus(View):
|
|||
if hasattr(status, "quote"):
|
||||
status.quote = to_markdown(status.quote)
|
||||
|
||||
status.save(created=True)
|
||||
status.save(created=created)
|
||||
|
||||
# update a readthorugh, if needed
|
||||
try:
|
||||
|
|
Loading…
Reference in a new issue