Merge pull request #761 from mouse-reeve/cover-url

Cover url
This commit is contained in:
Mouse Reeve 2021-03-19 10:54:53 -07:00 committed by GitHub
commit 17a3a06891
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 183 additions and 45 deletions

View file

@ -41,15 +41,10 @@
{% include 'snippets/shelve_button/shelve_button.html' %}
{% if request.user.is_authenticated and not book.cover %}
<div class="box p-2">
<h3 class="title is-6 mb-1">{% trans "Add cover" %}</h3>
<form name="add-cover" method="POST" action="/upload-cover/{{ book.id }}" enctype="multipart/form-data">
{% csrf_token %}
<label class="label">
<input type="file" name="cover" accept="image/*" enctype="multipart/form-data" id="id_cover" required>
</label>
<button class="button is-small is-primary" type="submit">{% trans "Add" %}</button>
</form>
<div class="block">
{% trans "Add cover" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="add-cover" controls_uid=book.id focus="modal-title-add-cover" class="is-small" %}
{% include 'book/cover_modal.html' with book=book controls_text="add-cover" controls_uid=book.id %}
</div>
{% endif %}

View file

@ -0,0 +1,36 @@
{% extends 'components/modal.html' %}
{% load i18n %}
{% block modal-title %}
{% trans "Add cover" %}
{% endblock %}
{% block modal-form-open %}
<form name="add-cover" method="POST" action="{% url 'upload-cover' book.id %}" enctype="multipart/form-data">
{% endblock %}
{% block modal-body %}
<section class="modal-card-body columns">
{% csrf_token %}
<div class="column">
<label class="label" for="id_cover">
{% trans "Upload cover:" %}
</label>
<input type="file" name="cover" accept="image/*" enctype="multipart/form-data" id="id_cover">
</div>
<div class="column">
<label class="label" for="id_cover_url">
{% trans "Load cover from url:" %}
</label>
<input class="input" name="cover-url" id="id_cover_url">
</div>
</section>
{% endblock %}
{% block modal-footer %}
<button class="button is-primary" type="submit">{% trans "Add" %}</button>
{% trans "Cancel" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with text=button_text %}
{% endblock %}
{% block modal-form-close %}</form>{% endblock %}

View file

@ -85,7 +85,7 @@
<input type="hidden" name="last_edited_by" value="{{ request.user.id }}">
<div class="columns">
<div class="column">
<div class="column is-half">
<section class="block">
<h2 class="title is-4">{% trans "Metadata" %}</h2>
<p class="mb-2"><label class="label" for="id_title">{% trans "Title:" %}</label> {{ form.title }} </p>
@ -151,15 +151,26 @@
</section>
</div>
<div class="column">
<div class="column is-half">
<h2 class="title is-4">{% trans "Cover" %}</h2>
<div class="columns">
<div class="column is-narrow">
{% include 'snippets/book_cover.html' with book=book size="small" %}
</div>
<div class="column is-narrow">
<div class="block">
<h2 class="title is-4">{% trans "Cover" %}</h2>
<p>{{ form.cover }}</p>
<p>
<label class="label" for="id_cover">{% trans "Upload cover:" %}</label>
{{ form.cover }}
</p>
{% if book %}
<p>
<label class="label" for="id_cover_url">
{% trans "Load cover from url:" %}
</label>
<input class="input" name="cover-url" id="id_cover_url">
</p>
{% endif %}
{% for error in form.cover.errors %}
<p class="help is-danger">{{ error | escape }}</p>
{% endfor %}

View file

@ -1,7 +1,14 @@
""" test for app action functionality """
from io import BytesIO
import pathlib
from unittest.mock import patch
from PIL import Image
import responses
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
@ -229,3 +236,59 @@ class BookViews(TestCase):
result = view(request, self.work.id)
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_upload_cover_file(self):
""" add a cover via file upload """
self.assertFalse(self.book.cover)
image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/default_avi.jpg"
)
form = forms.CoverForm(instance=self.book)
form.data["cover"] = SimpleUploadedFile(
image_file, open(image_file, "rb").read(), content_type="image/jpeg"
)
request = self.factory.post("", form.data)
request.user = self.local_user
with patch(
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
) as delay_mock:
views.upload_cover(request, self.book.id)
self.assertEqual(delay_mock.call_count, 1)
self.book.refresh_from_db()
self.assertTrue(self.book.cover)
@responses.activate
def test_upload_cover_url(self):
""" add a cover via url """
self.assertFalse(self.book.cover)
image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/default_avi.jpg"
)
image = Image.open(image_file)
output = BytesIO()
image.save(output, format=image.format)
responses.add(
responses.GET,
"http://example.com",
body=output.getvalue(),
status=200,
)
form = forms.CoverForm(instance=self.book)
form.data["cover-url"] = "http://example.com"
request = self.factory.post("", form.data)
request.user = self.local_user
with patch(
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
) as delay_mock:
views.upload_cover(request, self.book.id)
self.assertEqual(delay_mock.call_count, 1)
self.book.refresh_from_db()
self.assertTrue(self.book.cover)

View file

@ -5,6 +5,7 @@ from PIL import Image
from django.contrib.auth.models import AnonymousUser
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
@ -157,28 +158,31 @@ class UserViews(TestCase):
self.assertEqual(self.local_user.email, "wow@email.com")
# idk how to mock the upload form, got tired of triyng to make it work
# def test_edit_user_avatar(self):
# ''' use a form to update a user '''
# view = views.EditUser.as_view()
# form = forms.EditUserForm(instance=self.local_user)
# form.data['name'] = 'New Name'
# form.data['email'] = 'wow@email.com'
# image_file = pathlib.Path(__file__).parent.joinpath(
# '../../static/images/no_cover.jpg')
# image = Image.open(image_file)
# form.files['avatar'] = SimpleUploadedFile(
# image_file, open(image_file), content_type='image/jpeg')
# request = self.factory.post('', form.data, form.files)
# request.user = self.local_user
def test_edit_user_avatar(self):
""" use a form to update a user """
view = views.EditUser.as_view()
form = forms.EditUserForm(instance=self.local_user)
form.data["name"] = "New Name"
form.data["email"] = "wow@email.com"
image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/no_cover.jpg"
)
form.data["avatar"] = SimpleUploadedFile(
image_file, open(image_file, "rb").read(), content_type="image/jpeg"
)
request = self.factory.post("", form.data)
request.user = self.local_user
# with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay') \
# as delay_mock:
# view(request)
# self.assertEqual(delay_mock.call_count, 1)
# self.assertEqual(self.local_user.name, 'New Name')
# self.assertEqual(self.local_user.email, 'wow@email.com')
# self.assertIsNotNone(self.local_user.avatar)
# self.assertEqual(self.local_user.avatar.size, (120, 120))
with patch(
"bookwyrm.models.activitypub_mixin.broadcast_task.delay"
) as delay_mock:
view(request)
self.assertEqual(delay_mock.call_count, 1)
self.assertEqual(self.local_user.name, "New Name")
self.assertEqual(self.local_user.email, "wow@email.com")
self.assertIsNotNone(self.local_user.avatar)
self.assertEqual(self.local_user.avatar.width, 120)
self.assertEqual(self.local_user.avatar.height, 120)
def test_crop_avatar(self):
""" reduce that image size """

View file

@ -152,7 +152,9 @@ urlpatterns = [
re_path(r"^create-book/?$", views.EditBook.as_view()),
re_path(r"^create-book/confirm?$", views.ConfirmEditBook.as_view()),
re_path(r"%s/editions(.json)?/?$" % book_path, views.Editions.as_view()),
re_path(r"^upload-cover/(?P<book_id>\d+)/?$", views.upload_cover),
re_path(
r"^upload-cover/(?P<book_id>\d+)/?$", views.upload_cover, name="upload-cover"
),
re_path(r"^add-description/(?P<book_id>\d+)/?$", views.add_description),
re_path(r"^resolve-book/?$", views.resolve_book),
re_path(r"^switch-edition/?$", views.switch_edition),

View file

@ -1,6 +1,9 @@
""" the good stuff! the books! """
from uuid import uuid4
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.postgres.search import SearchRank, SearchVector
from django.core.files.base import ContentFile
from django.core.paginator import Paginator
from django.db import transaction
from django.db.models import Avg, Q
@ -14,6 +17,7 @@ from django.views.decorators.http import require_POST
from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.connectors import connector_manager
from bookwyrm.connectors.abstract_connector import get_image
from bookwyrm.settings import PAGE_LENGTH
from .helpers import is_api_request, get_activity_feed, get_edition
from .helpers import privacy_filter
@ -97,7 +101,7 @@ class Book(View):
"readthroughs": readthroughs,
"path": "/book/%s" % book_id,
}
return TemplateResponse(request, "book.html", data)
return TemplateResponse(request, "book/book.html", data)
@method_decorator(login_required, name="dispatch")
@ -115,7 +119,7 @@ class EditBook(View):
if not book.description:
book.description = book.parent_work.description
data = {"book": book, "form": forms.EditionForm(instance=book)}
return TemplateResponse(request, "edit_book.html", data)
return TemplateResponse(request, "book/edit_book.html", data)
def post(self, request, book_id=None):
""" edit a book cool """
@ -125,7 +129,7 @@ class EditBook(View):
data = {"book": book, "form": form}
if not form.is_valid():
return TemplateResponse(request, "edit_book.html", data)
return TemplateResponse(request, "book/edit_book.html", data)
add_author = request.POST.get("add_author")
# we're adding an author through a free text field
@ -169,13 +173,19 @@ class EditBook(View):
data["confirm_mode"] = True
# this isn't preserved because it isn't part of the form obj
data["remove_authors"] = request.POST.getlist("remove_authors")
return TemplateResponse(request, "edit_book.html", data)
return TemplateResponse(request, "book/edit_book.html", data)
remove_authors = request.POST.getlist("remove_authors")
for author_id in remove_authors:
book.authors.remove(author_id)
book = form.save()
book = form.save(commit=False)
url = request.POST.get("cover-url")
if url:
image = set_cover_from_url(url)
if image:
book.cover.save(*image, save=False)
book.save()
return redirect("/book/%s" % book.id)
@ -194,7 +204,7 @@ class ConfirmEditBook(View):
data = {"book": book, "form": form}
if not form.is_valid():
return TemplateResponse(request, "edit_book.html", data)
return TemplateResponse(request, "book/edit_book.html", data)
with transaction.atomic():
# save book
@ -256,18 +266,35 @@ class Editions(View):
def upload_cover(request, book_id):
""" upload a new cover """
book = get_object_or_404(models.Edition, id=book_id)
book.last_edited_by = request.user
url = request.POST.get("cover-url")
if url:
image = set_cover_from_url(url)
book.cover.save(*image)
form = forms.CoverForm(request.POST, request.FILES, instance=book)
if not form.is_valid():
return redirect("/book/%d" % book.id)
book.last_edited_by = request.user
form = forms.CoverForm(request.POST, request.FILES, instance=book)
if not form.is_valid() or not form.files.get("cover"):
return redirect("/book/%d" % book.id)
book.cover = form.files["cover"]
book.save()
return redirect("/book/%s" % book.id)
def set_cover_from_url(url):
""" load it from a url """
image_file = get_image(url)
if not image_file:
return None
image_name = str(uuid4()) + "." + url.split(".")[-1]
image_content = ContentFile(image_file.content)
return [image_name, image_content]
@login_required
@require_POST
@permission_required("bookwyrm.edit_book", raise_exception=True)

View file

@ -173,7 +173,7 @@ class EditUser(View):
# set the name to a hash
extension = form.files["avatar"].name.split(".")[-1]
filename = "%s.%s" % (uuid4(), extension)
user.avatar.save(filename, image)
user.avatar.save(filename, image, save=False)
user.save()
return redirect(user.local_path)