Merge pull request #864 from mouse-reeve/delete-and-redraft

Delete and redraft
This commit is contained in:
Mouse Reeve 2021-04-04 11:00:09 -07:00 committed by GitHub
commit ea837a3879
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 253 additions and 47 deletions

View file

@ -1,18 +1,10 @@
""" Handle user activity """ """ Handle user activity """
from django.db import transaction from django.db import transaction
from django.utils import timezone
from bookwyrm import models from bookwyrm import models
from bookwyrm.sanitize_html import InputHtmlParser from bookwyrm.sanitize_html import InputHtmlParser
def delete_status(status):
""" replace the status with a tombstone """
status.deleted = True
status.deleted_date = timezone.now()
status.save()
def create_generated_note(user, content, mention_books=None, privacy="public"): def create_generated_note(user, content, mention_books=None, privacy="public"):
""" a note created by the app about user activity """ """ a note created by the app about user activity """
# sanitize input html # sanitize input html

View file

@ -0,0 +1,34 @@
{% extends 'layout.html' %}
{% load i18n %}
{% load bookwyrm_tags %}
{% block title %}{% trans "Compose status" %}{% endblock %}
{% block content %}
<header class="block content">
<h1>{% trans "Compose status" %}</h1>
</header>
{% with 0|uuid as uuid %}
<div class="box columns">
{% if book %}
<div class="column is-one-third">
<div class="block">
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book %}</a>
</div>
<h3 class="title is-6">{% include 'snippets/book_titleby.html' with book=book %}</h3>
</div>
{% endif %}
<div class="column is-two-thirds">
{% if draft.reply_parent %}
{% include 'snippets/status/status.html' with status=draft.reply_parent no_interact=True %}
{% endif %}
{% if not draft %}
{% include 'snippets/create_status.html' %}
{% else %}
{% include 'snippets/create_status_form.html' %}
{% endif %}
</div>
</div>
{% endwith %}
{% endblock %}

View file

@ -12,6 +12,7 @@
{% if not suggested_books %} {% if not suggested_books %}
<p>{% trans "There are no books here right now! Try searching for a book to get started" %}</p> <p>{% trans "There are no books here right now! Try searching for a book to get started" %}</p>
{% else %} {% else %}
{% with active_book=request.GET.book %}
<div class="tab-group"> <div class="tab-group">
<div class="tabs is-small"> <div class="tabs is-small">
<ul role="tablist"> <ul role="tablist">
@ -28,8 +29,14 @@
<div class="tabs is-small is-toggle"> <div class="tabs is-small is-toggle">
<ul> <ul>
{% for book in shelf.books %} {% for book in shelf.books %}
<li{% if shelf_counter == 1 and forloop.first %} class="is-active"{% endif %}> <li class="{% if active_book == book.id|stringformat:'d' %}is-active{% elif not active_book and shelf_counter == 1 and forloop.first %}is-active{% endif %}">
<a href="#book-{{ book.id }}" id="tab-book-{{ book.id }}" role="tab" aria-label="{{ book.title }}" aria-selected="{% if shelf_counter == 1 and forloop.first %}true{% else %}false{% endif %}" aria-controls="book-{{ book.id }}"> <a
href="{{ request.path }}?book={{ book.id }}"
id="tab-book-{{ book.id }}"
role="tab"
aria-label="{{ book.title }}"
aria-selected="{% if active_book == book.id|stringformat:'d' %}true{% elif shelf_counter == 1 and forloop.first %}true{% else %}false{% endif %}"
aria-controls="book-{{ book.id }}">
{% include 'snippets/book_cover.html' with book=book size="medium" %} {% include 'snippets/book_cover.html' with book=book size="medium" %}
</a> </a>
</li> </li>
@ -45,7 +52,13 @@
{% for shelf in suggested_books %} {% for shelf in suggested_books %}
{% with shelf_counter=forloop.counter %} {% with shelf_counter=forloop.counter %}
{% for book in shelf.books %} {% for book in shelf.books %}
<div class="suggested-tabs card" role="tabpanel" id="book-{{ book.id }}"{% if shelf_counter != 1 or not forloop.first %} hidden{% endif %} aria-labelledby="tab-book-{{ book.id }}"> <div
class="suggested-tabs card"
role="tabpanel"
id="book-{{ book.id }}"
{% if active_book and active_book == book.id|stringformat:'d' %}{% elif not active_book and shelf_counter == 1 and forloop.first %}{% else %} hidden{% endif %}
aria-labelledby="tab-book-{{ book.id }}">
<div class="card-header"> <div class="card-header">
<div class="card-header-title"> <div class="card-header-title">
<div> <div>
@ -66,6 +79,7 @@
{% endwith %} {% endwith %}
{% endfor %} {% endfor %}
</div> </div>
{% endwith %}
{% endif %} {% endif %}
{% if goal %} {% if goal %}

View file

@ -1,5 +1,12 @@
{% load i18n %} {% load i18n %}
<div class="control{% if not parent_status.content_warning %} hidden{% endif %}" id="spoilers-{{ uuid }}"> <div class="control{% if not parent_status.content_warning and not draft.content_warning %} hidden{% endif %}" id="spoilers-{{ uuid }}">
<label class="is-sr-only" for="id_content_warning-{{ uuid }}">{% trans "Spoiler alert:" %}</label> <label class="is-sr-only" for="id_content_warning-{{ uuid }}">{% trans "Spoiler alert:" %}</label>
<input type="text" name="content_warning" maxlength="255" class="input" id="id_content_warning-{{ uuid }}" placeholder="{% trans 'Spoilers ahead!' %}"{% if parent_status.content_warning %} value="{{ parent_status.content_warning }}"{% endif %}> <input
type="text"
name="content_warning"
maxlength="255"
class="input"
id="id_content_warning-{{ uuid }}"
placeholder="{% trans 'Spoilers ahead!' %}"
value="{% firstof draft.content_warning parent_status.content_warning '' %}">
</div> </div>

View file

@ -2,36 +2,62 @@
{% load i18n %} {% load i18n %}
{% load bookwyrm_tags %} {% load bookwyrm_tags %}
{% with status_type=request.GET.status_type %}
<div class="tab-group"> <div class="tab-group">
<div class="tabs is-boxed" role="tablist"> <div class="tabs is-boxed" role="tablist">
<ul> <ul>
<li class="is-active"> <li class="{% if status_type == 'review' or not status_type %}is-active{% endif %}">
<a href="#review-{{ book.id }}" id="tab-review-{{ book.id }}" role="tab" aria-selected="true" aria-controls="review-{{ book.id }}" data-category="tab-option-{{ book.id }}">{% trans "Review" %}</a> <a
href="{{ request.path }}?status_type=review&book={{ book.id }}"
id="tab-review-{{ book.id }}"
role="tab"
aria-selected="{% if status_type == 'review' or not status_type %}true{% else %}false{% endif %}"
aria-controls="review-{{ book.id }}"
data-category="tab-option-{{ book.id }}">
{% trans "Review" %}
</a>
</li> </li>
<li> <li class="{% if status_type == 'comment' %}is-active{% endif %}">
<a href="#comment-{{ book.id}}" id="tab-comment-{{ book.id }}" role="tab" aria-selected="false" aria-controls="comment-{{ book.id}}" data-category="tab-option-{{ book.id }}">{% trans "Comment" %}</a> <a
href="{{ request.path }}?status_type=comment&book={{ book.id}}"
id="tab-comment-{{ book.id }}"
role="tab"
aria-selected="{% if status_type == 'comment' %}true{% else %}false{% endif %}"
aria-controls="comment-{{ book.id}}"
data-category="tab-option-{{ book.id }}">
{% trans "Comment" %}
</a>
</li> </li>
<li> <li class="{% if status_type == 'quote' %}is-active{% endif %}">
<a href="#quote-{{ book.id }}" id="tab-quote-{{ book.id }}" role="tab" aria-selected="false" aria-controls="quote-{{ book.id }}" data-category="tab-option-{{ book.id }}">{% trans "Quote" %}</a> <a
href="{{ request.path }}?status_type=quote&book={{ book.id }}"
id="tab-quote-{{ book.id }}"
role="tab"
aria-selected="{% if status_type == 'quote' %}true{% else %}false{% endif %}"
aria-controls="quote-{{ book.id }}"
data-category="tab-option-{{ book.id }}">
{% trans "Quote" %}
</a>
</li> </li>
</ul> </ul>
</div> </div>
<div class="tab-option-{{ book.id }}" id="review-{{ book.id }}" role="tabpanel" aria-labelledby="tab-review-{{ book.id }}"> <div class="tab-option-{{ book.id }}" id="review-{{ book.id }}" role="tabpanel" aria-labelledby="tab-review-{{ book.id }}" {% if status_type and status_type != "review" %}hidden{% endif %}>
{% with 0|uuid as uuid %} {% with 0|uuid as uuid %}
{% include 'snippets/create_status_form.html' with type='review' %} {% include 'snippets/create_status_form.html' with type='review' %}
{% endwith %} {% endwith %}
</div> </div>
<div class="tab-option-{{ book.id }}" id="comment-{{ book.id }}" role="tabpanel" aria-labelledby="tab-comment-{{ book.id }}" hidden> <div class="tab-option-{{ book.id }}" id="comment-{{ book.id }}" role="tabpanel" aria-labelledby="tab-comment-{{ book.id }}" {% if status_type != "comment" %}hidden{% endif %}>
{% with 0|uuid as uuid %} {% with 0|uuid as uuid %}
{% include 'snippets/create_status_form.html' with type="comment" placeholder="Some thoughts on '"|add:book.title|add:"'" %} {% include 'snippets/create_status_form.html' with type="comment" placeholder="Some thoughts on '"|add:book.title|add:"'" %}
{% endwith %} {% endwith %}
</div> </div>
<div class="tab-option-{{ book.id }}" id="quote-{{ book.id }}" role="tabpanel" aria-labelledby="tab-quote-{{ book.id }}" hidden> <div class="tab-option-{{ book.id }}" id="quote-{{ book.id }}" role="tabpanel" aria-labelledby="tab-quote-{{ book.id }}" {% if status_type != "quote" %}hidden{% endif %}>
{% with 0|uuid as uuid %} {% with 0|uuid as uuid %}
{% include 'snippets/create_status_form.html' with type="quotation" placeholder="An excerpt from '"|add:book.title|add:"'" %} {% include 'snippets/create_status_form.html' with type="quotation" placeholder="An excerpt from '"|add:book.title|add:"'" %}
{% endwith %} {% endwith %}
</div> </div>
</div> </div>
{% endwith %}

View file

@ -4,11 +4,11 @@
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="book" value="{{ book.id }}"> <input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="user" value="{{ request.user.id }}"> <input type="hidden" name="user" value="{{ request.user.id }}">
<input type="hidden" name="reply_parent" value="{{ reply_parent.id }}"> <input type="hidden" name="reply_parent" value="{% firstof draft.reply_parent.id reply_parent.id %}">
{% if type == 'review' %} {% if type == 'review' %}
<div class="control"> <div class="control">
<label class="label" for="id_name_{{ book.id }}_{{ type }}">{% trans "Title" %}:</label> <label class="label" for="id_name_{{ book.id }}_{{ type }}">{% trans "Title" %}:</label>
<input type="text" name="name" maxlength="255" class="input" required="" id="id_name_{{ book.id }}_{{ type }}" placeholder="My {{ type }} of '{{ book.title }}'"> <input type="text" name="name" maxlength="255" class="input" required="" id="id_name_{{ book.id }}_{{ type }}" placeholder="My {{ type }} of '{{ book.title }}'" value="{% firstof draft.name ''%}">
</div> </div>
{% endif %} {% endif %}
<div class="control"> <div class="control">
@ -28,22 +28,22 @@
<fieldset> <fieldset>
<legend class="is-sr-only">{% trans "Rating" %}</legend> <legend class="is-sr-only">{% trans "Rating" %}</legend>
{% include 'snippets/form_rate_stars.html' with book=book type=type|default:'summary' %} {% include 'snippets/form_rate_stars.html' with book=book type=type|default:'summary' default_rating=draft.rating %}
</fieldset> </fieldset>
{% endif %} {% endif %}
{% if type == 'quotation' %} {% if type == 'quotation' %}
<textarea name="quote" class="textarea" id="id_quote_{{ book.id }}_{{ type }}" placeholder="{{ placeholder }}" required></textarea> <textarea name="quote" class="textarea" id="id_quote_{{ book.id }}_{{ type }}" placeholder="{{ placeholder }}" required>{{ draft.quote|default:'' }}</textarea>
{% else %} {% else %}
{% include 'snippets/content_warning_field.html' with parent_status=status %} {% include 'snippets/content_warning_field.html' with parent_status=status %}
<textarea name="content" class="textarea" id="id_content_{{ type }}-{{ book.id }}{{reply_parent.id}}" placeholder="{{ placeholder }}" {% if type == 'reply' %} aria-label="Reply"{% endif %} required>{% if reply_parent %}{{ reply_parent|mentions:request.user }}{% endif %}{% if mentions %}@{{ mentions|username }} {% endif %}</textarea> <textarea name="content" class="textarea" id="id_content_{{ type }}-{{ book.id }}{{reply_parent.id}}" placeholder="{{ placeholder }}" {% if type == 'reply' %} aria-label="Reply"{% endif %} required>{% if reply_parent %}{{ reply_parent|mentions:request.user }}{% endif %}{% if mentions %}@{{ mentions|username }} {% endif %}{{ draft.content|default:'' }}</textarea>
{% endif %} {% endif %}
</div> </div>
{% if type == 'quotation' %} {% if type == 'quotation' %}
<div class="control"> <div class="control">
<label class="label" for="id_content_quote-{{ book.id }}">{% trans "Comment" %}:</label> <label class="label" for="id_content_quote-{{ book.id }}">{% trans "Comment" %}:</label>
{% include 'snippets/content_warning_field.html' with parent_status=status %} {% include 'snippets/content_warning_field.html' with parent_status=status %}
<textarea name="content" class="textarea is-small" id="id_content_quote-{{ book.id }}"></textarea> <textarea name="content" class="textarea is-small" id="id_content_quote-{{ book.id }}">{{ draft.content|default:'' }}</textarea>
</div> </div>
{% elif type == 'comment' %} {% elif type == 'comment' %}
<div class="control"> <div class="control">
@ -56,12 +56,12 @@
<label class="label" for="progress-{{ uuid }}">{% trans "Progress:" %}</label> <label class="label" for="progress-{{ uuid }}">{% trans "Progress:" %}</label>
<div class="field has-addons mb-0"> <div class="field has-addons mb-0">
<div class="control"> <div class="control">
<input aria-label="{% if readthrough.progress_mode == 'PG' %}Current page{% else %}Percent read{% endif %}" class="input" type="number" min="0" name="progress" size="3" value="{{ readthrough.progress|default:'' }}" id="progress-{{ uuid }}"> <input aria-label="{% if draft.progress_mode == 'PG' or readthrough.progress_mode == 'PG' %}Current page{% else %}Percent read{% endif %}" class="input" type="number" min="0" name="progress" size="3" value="{% firstof draft.progress readthrough.progress '' %}" id="progress-{{ uuid }}">
</div> </div>
<div class="control select"> <div class="control select">
<select name="progress_mode" aria-label="Progress mode"> <select name="progress_mode" aria-label="Progress mode">
<option value="PG" {% if readthrough.progress_mode == 'PG' %}selected{% endif %}>{% trans "pages" %}</option> <option value="PG" {% if draft.progress_mode == 'PG' or readthrough.progress_mode == 'PG' %}selected{% endif %}>{% trans "pages" %}</option>
<option value="PCT" {% if readthrough.progress_mode == 'PCT' %}selected{% endif %}>{% trans "percent" %}</option> <option value="PCT" {% if draft.progress_mode == 'PCT' or readthrough.progress_mode == 'PCT' %}selected{% endif %}>{% trans "percent" %}</option>
</select> </select>
</div> </div>
</div> </div>
@ -73,20 +73,25 @@
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
<input type="checkbox" class="hidden" name="sensitive" id="id_show_spoilers-{{ uuid }}" {% if status.content_warning %}checked{% endif %} aria-hidden="true"> <input type="checkbox" class="hidden" name="sensitive" id="id_show_spoilers-{{ uuid }}" {% if draft.content_warning or status.content_warning %}checked{% endif %} aria-hidden="true">
{# bottom bar #} {# bottom bar #}
<div class="columns pt-1"> <div class="columns pt-1">
<div class="field has-addons column"> <div class="field has-addons column">
<div class="control"> <div class="control">
{% trans "Include spoiler alert" as button_text %} {% trans "Include spoiler alert" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with text=button_text icon="warning is-size-4" controls_text="spoilers" controls_uid=uuid focus="id_content_warning" checkbox="id_show_spoilers" class="toggle-button" pressed=status.content_warning %} {% firstof draft.content_warning status.content_warning as pressed %}
{% include 'snippets/toggle/toggle_button.html' with text=button_text icon="warning is-size-4" controls_text="spoilers" controls_uid=uuid focus="id_content_warning" checkbox="id_show_spoilers" class="toggle-button" pressed=pressed %}
</div> </div>
<div class="control"> <div class="control">
{% if type == 'direct' %} {% if type == 'direct' %}
<input type="hidden" name="privacy" value="direct"> <input type="hidden" name="privacy" value="direct">
<button type="button" class="button" aria-label="Privacy" disabled>{% trans "Private" %}</button> <button type="button" class="button" aria-label="Privacy" disabled>{% trans "Private" %}</button>
{% else %} {% else %}
{% if draft %}
{% include 'snippets/privacy_select.html' with current=draft.privacy %}
{% else %}
{% include 'snippets/privacy_select.html' with current=reply_parent.privacy %} {% include 'snippets/privacy_select.html' with current=reply_parent.privacy %}
{% endif %}
{% endif %} {% endif %}
</div> </div>
</div> </div>

View file

@ -13,7 +13,7 @@
type="radio" type="radio"
name="rating" name="rating"
value="0" value="0"
{% if book|user_rating:user == 0 %}checked{% endif %} {% if default_rating == 0 or not default_rating %}checked{% endif %}
> >
<label class="is-sr-only" for="{{ type|slugify }}-{{ book.id }}-no-rating"> <label class="is-sr-only" for="{{ type|slugify }}-{{ book.id }}-no-rating">
@ -27,13 +27,13 @@
type="radio" type="radio"
name="rating" name="rating"
value="{{ forloop.counter }}" value="{{ forloop.counter }}"
{% if book|user_rating:user == forloop.counter %}checked{% endif %} {% if default_rating == forloop.counter %}checked{% endif %}
/> />
<label <label
class=" class="
icon icon
{% if forloop.counter <= book|user_rating:user %} {% if forloop.counter <= default_rating %}
icon-star-full icon-star-full
{% else %} {% else %}
icon-star-empty icon-star-empty

View file

@ -9,7 +9,7 @@
<input type="hidden" name="book" value="{{ book.id }}"> <input type="hidden" name="book" value="{{ book.id }}">
<input type="hidden" name="privacy" value="public"> <input type="hidden" name="privacy" value="public">
{% include 'snippets/form_rate_stars.html' with book=book classes='mb-1 has-text-warning-dark' %} {% include 'snippets/form_rate_stars.html' with book=book classes='mb-1 has-text-warning-dark' default_rating=book|user_rating:request.user %}
<div class="field has-addons hidden"> <div class="field has-addons hidden">
<div class="control"> <div class="control">

View file

@ -27,7 +27,8 @@
{% trans "Delete status" %} {% trans "Delete status" %}
</button> </button>
</form> </form>
{% elif no_interact %}
{# nothing here #}
{% elif request.user.is_authenticated %} {% elif request.user.is_authenticated %}
<div class="field has-addons"> <div class="field has-addons">
<div class="control"> <div class="control">

View file

@ -19,6 +19,16 @@
</button> </button>
</form> </form>
</li> </li>
{% if status.status_type != 'GeneratedNote' and status.status_type != 'Rating' %}
<li role="menuitem">
<form class="dropdown-item pt-0 pb-0" name="delete-{{status.id}}" action="{% url 'redraft' status.id %}" method="post">
{% csrf_token %}
<button class="button is-danger is-light is-fullwidth is-small" type="submit">
{% trans "Delete & re-draft" %}
</button>
</form>
</li>
{% endif %}
{% else %} {% else %}
{# things you can do to other people's statuses #} {# things you can do to other people's statuses #}
<li role="menuitem"> <li role="menuitem">

View file

@ -40,6 +40,7 @@ class StatusViews(TestCase):
remote_id="https://example.com/book/1", remote_id="https://example.com/book/1",
parent_work=work, parent_work=work,
) )
models.SiteSettings.objects.create()
def test_handle_status(self, _): def test_handle_status(self, _):
""" create a status """ """ create a status """
@ -166,6 +167,61 @@ class StatusViews(TestCase):
self.assertFalse(self.remote_user in reply.mention_users.all()) self.assertFalse(self.remote_user in reply.mention_users.all())
self.assertTrue(self.local_user in reply.mention_users.all()) self.assertTrue(self.local_user in reply.mention_users.all())
def test_delete_and_redraft(self, _):
""" delete and re-draft a status """
view = views.DeleteAndRedraft.as_view()
request = self.factory.post("")
request.user = self.local_user
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
status = models.Comment.objects.create(
content="hi", book=self.book, user=self.local_user
)
with patch("bookwyrm.activitystreams.ActivityStream.remove_status") as mock:
result = view(request, status.id)
self.assertTrue(mock.called)
result.render()
# make sure it was deleted
status.refresh_from_db()
self.assertTrue(status.deleted)
def test_delete_and_redraft_invalid_status_type_rating(self, _):
""" you can't redraft generated statuses """
view = views.DeleteAndRedraft.as_view()
request = self.factory.post("")
request.user = self.local_user
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
status = models.ReviewRating.objects.create(
book=self.book, rating=2.0, user=self.local_user
)
with patch("bookwyrm.activitystreams.ActivityStream.remove_status") as mock:
result = view(request, status.id)
self.assertFalse(mock.called)
self.assertEqual(result.status_code, 400)
status.refresh_from_db()
self.assertFalse(status.deleted)
def test_delete_and_redraft_invalid_status_type_generated_note(self, _):
""" you can't redraft generated statuses """
view = views.DeleteAndRedraft.as_view()
request = self.factory.post("")
request.user = self.local_user
with patch("bookwyrm.activitystreams.ActivityStream.add_status"):
status = models.GeneratedNote.objects.create(
content="hi", user=self.local_user
)
with patch("bookwyrm.activitystreams.ActivityStream.remove_status") as mock:
result = view(request, status.id)
self.assertFalse(mock.called)
self.assertEqual(result.status_code, 400)
status.refresh_from_db()
self.assertFalse(status.deleted)
def test_find_mentions(self, _): def test_find_mentions(self, _):
""" detect and look up @ mentions of users """ """ detect and look up @ mentions of users """
user = models.User.objects.create_user( user = models.User.objects.create_user(

View file

@ -197,11 +197,31 @@ urlpatterns = [
re_path(r"^block/(?P<user_id>\d+)/?$", views.Block.as_view()), re_path(r"^block/(?P<user_id>\d+)/?$", views.Block.as_view()),
re_path(r"^unblock/(?P<user_id>\d+)/?$", views.unblock), re_path(r"^unblock/(?P<user_id>\d+)/?$", views.unblock),
# statuses # statuses
re_path(r"%s(.json)?/?$" % status_path, views.Status.as_view()), re_path(r"%s(.json)?/?$" % status_path, views.Status.as_view(), name="status"),
re_path(r"%s/activity/?$" % status_path, views.Status.as_view()), re_path(r"%s/activity/?$" % status_path, views.Status.as_view(), name="status"),
re_path(r"%s/replies(.json)?/?$" % status_path, views.Replies.as_view()), re_path(
re_path(r"^post/(?P<status_type>\w+)/?$", views.CreateStatus.as_view()), r"%s/replies(.json)?/?$" % status_path, views.Replies.as_view(), name="replies"
re_path(r"^delete-status/(?P<status_id>\d+)/?$", views.DeleteStatus.as_view()), ),
re_path(
r"^post/?$",
views.CreateStatus.as_view(),
name="create-status",
),
re_path(
r"^post/(?P<status_type>\w+)/?$",
views.CreateStatus.as_view(),
name="create-status",
),
re_path(
r"^delete-status/(?P<status_id>\d+)/?$",
views.DeleteStatus.as_view(),
name="delete-status",
),
re_path(
r"^redraft-status/(?P<status_id>\d+)/?$",
views.DeleteAndRedraft.as_view(),
name="redraft",
),
# interact # interact
re_path(r"^favorite/(?P<status_id>\d+)/?$", views.Favorite.as_view()), re_path(r"^favorite/(?P<status_id>\d+)/?$", views.Favorite.as_view()),
re_path(r"^unfavorite/(?P<status_id>\d+)/?$", views.Unfavorite.as_view()), re_path(r"^unfavorite/(?P<status_id>\d+)/?$", views.Unfavorite.as_view()),

View file

@ -31,7 +31,7 @@ from .shelf import Shelf
from .shelf import create_shelf, delete_shelf from .shelf import create_shelf, delete_shelf
from .shelf import shelve, unshelve from .shelf import shelve, unshelve
from .site import Site from .site import Site
from .status import CreateStatus, DeleteStatus from .status import CreateStatus, DeleteStatus, DeleteAndRedraft
from .tag import Tag, AddTag, RemoveTag from .tag import Tag, AddTag, RemoveTag
from .updates import get_notification_count, get_unread_status_count from .updates import get_notification_count, get_unread_status_count
from .user import User, EditUser, Followers, Following from .user import User, EditUser, Followers, Following

View file

@ -3,6 +3,7 @@ import re
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponseBadRequest from django.http import HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views import View from django.views import View
from markdown import markdown from markdown import markdown
@ -10,7 +11,6 @@ from markdown import markdown
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm.sanitize_html import InputHtmlParser from bookwyrm.sanitize_html import InputHtmlParser
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN
from bookwyrm.status import delete_status
from bookwyrm.utils import regex from bookwyrm.utils import regex
from .helpers import handle_remote_webfinger from .helpers import handle_remote_webfinger
from .reading import edit_readthrough from .reading import edit_readthrough
@ -21,6 +21,12 @@ from .reading import edit_readthrough
class CreateStatus(View): class CreateStatus(View):
""" the view for *posting* """ """ the view for *posting* """
def get(self, request):
""" compose view (used for delete-and-redraft """
book = get_object_or_404(models.Edition, id=request.GET.get("book"))
data = {"book": book}
return TemplateResponse(request, "compose.html", data)
def post(self, request, status_type): def post(self, request, status_type):
""" create status of whatever type """ """ create status of whatever type """
status_type = status_type[0].upper() + status_type[1:] status_type = status_type[0].upper() + status_type[1:]
@ -69,9 +75,10 @@ class CreateStatus(View):
# update a readthorugh, if needed # update a readthorugh, if needed
edit_readthrough(request) edit_readthrough(request)
return redirect(request.headers.get("Referer", "/")) return redirect("/")
@method_decorator(login_required, name="dispatch")
class DeleteStatus(View): class DeleteStatus(View):
""" tombstone that bad boy """ """ tombstone that bad boy """
@ -84,10 +91,44 @@ class DeleteStatus(View):
return HttpResponseBadRequest() return HttpResponseBadRequest()
# perform deletion # perform deletion
delete_status(status) status.delete()
return redirect(request.headers.get("Referer", "/")) return redirect(request.headers.get("Referer", "/"))
@method_decorator(login_required, name="dispatch")
class DeleteAndRedraft(View):
""" delete a status but let the user re-create it """
def post(self, request, status_id):
""" delete and tombstone a status """
status = get_object_or_404(
models.Status.objects.select_subclasses(), id=status_id
)
if isinstance(status, (models.GeneratedNote, models.ReviewRating)):
return HttpResponseBadRequest()
# don't let people redraft other people's statuses
if status.user != request.user:
return HttpResponseBadRequest()
status_type = status.status_type.lower()
if status.reply_parent:
status_type = "reply"
data = {
"draft": status,
"type": status_type,
}
if hasattr(status, "book"):
data["book"] = status.book
elif status.mention_books:
data["book"] = status.mention_books.first()
# perform deletion
status.delete()
return TemplateResponse(request, "compose.html", data)
def find_mentions(content): def find_mentions(content):
""" detect @mentions in raw status content """ """ detect @mentions in raw status content """
if not content: if not content: