Merge pull request #1834 from bookwyrm-social/links-display

Improve link filetype and add availability field
This commit is contained in:
Mouse Reeve 2022-01-17 11:08:40 -08:00 committed by GitHub
commit 8a0ea674ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 206 additions and 16 deletions

View file

@ -225,7 +225,7 @@ class LinkDomainForm(CustomForm):
class FileLinkForm(CustomForm):
class Meta:
model = models.FileLink
fields = ["url", "filetype", "book", "added_by"]
fields = ["url", "filetype", "availability", "book", "added_by"]
class EditionForm(CustomForm):

View file

@ -0,0 +1,32 @@
# Generated by Django 3.2.10 on 2022-01-17 17:16
import bookwyrm.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0128_merge_0126_auto_20220112_2315_0127_auto_20220110_2211"),
]
operations = [
migrations.AddField(
model_name="filelink",
name="availability",
field=bookwyrm.models.fields.CharField(
choices=[
("free", "Free"),
("purchase", "Purchasable"),
("loan", "Available for loan"),
],
default="free",
max_length=100,
),
),
migrations.AlterField(
model_name="filelink",
name="filetype",
field=bookwyrm.models.fields.CharField(max_length=50),
),
]

View file

@ -47,13 +47,23 @@ class Link(ActivitypubMixin, BookWyrmModel):
return super().save(*args, **kwargs)
AvailabilityChoices = [
("free", _("Free")),
("purchase", _("Purchasable")),
("loan", _("Available for loan")),
]
class FileLink(Link):
"""a link to a file"""
book = models.ForeignKey(
"Book", on_delete=models.CASCADE, related_name="file_links", null=True
)
filetype = fields.CharField(max_length=5, activitypub_field="mediaType")
filetype = fields.CharField(max_length=50, activitypub_field="mediaType")
availability = fields.CharField(
max_length=100, choices=AvailabilityChoices, default="free"
)
StatusChoices = [

View file

@ -14,7 +14,7 @@ VERSION = "0.2.0"
PAGE_LENGTH = env("PAGE_LENGTH", 15)
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
JS_CACHE = "a47cc2ca"
JS_CACHE = "76c5ff1f"
# email
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")

View file

@ -42,6 +42,8 @@
function getSuggestions(input, trie) {
// Follow the trie through the provided input
input = input.toLowerCase();
input.split("").forEach((letter) => {
if (!trie) {
return;
@ -160,6 +162,23 @@ const tries = {
},
},
},
r: {
i: {
n: {
t: {
" ": {
b: {
o: {
o: {
k: "Print book",
},
},
},
},
},
},
},
},
},
},
};

View file

@ -30,7 +30,7 @@
<input
type="text"
name="filetype"
maxlength="5"
maxlength="50"
class="input"
required=""
id="id_filetype"
@ -43,6 +43,14 @@
{% include 'snippets/form_errors.html' with errors_list=file_link_form.filetype.errors id="desc_filetype" %}
</div>
</div>
<div>
<label class="label" for="id_availability">
{% trans "Availability:" %}
</label>
<div class="select">
{{ file_link_form.availability }}
</div>
</div>
{% endblock %}

View file

@ -33,9 +33,10 @@
<th>{% trans "Added by" %}</th>
<th>{% trans "Filetype" %}</th>
<th>{% trans "Domain" %}</th>
<th>{% trans "Actions" %}</th>
<th>{% trans "Status" %}</th>
<th colspan="2">{% trans "Actions" %}</th>
</tr>
{% for link in book.file_links.all %}
{% for link in links %}
<tr>
<td class="overflow-wrap-anywhere">
<a href="{{ link.url }}" target="_blank" rel="noopener">{{ link.url }}</a>
@ -47,13 +48,44 @@
{{ link.filelink.filetype }}
</td>
<td>
{{ link.domain.name }} ({{ link.domain.get_status_display }})
{{ link.domain.name }}
<p>
<a href="{% url 'report-link' link.added_by.id link.id %}">{% trans "Report spam" %}</a>
</p>
</td>
<td>
<form name="delete-link-{{ link.id }}" class="control" method="post" action="{% url 'file-link' book.id link.id %}">
{% with status=link.domain.status %}
<span class="tag {% if status == 'blocked' %}has-background-danger{% elif status == 'approved' %}has-background-primary{% endif %}">
<span class="icon" aria-hidden="true">
<span class="icon-{% if status == 'blocked' %}x{% elif status == 'approved' %}check{% else %}lock{% endif %}"></span>
</span>
<span>
{{ link.domain.get_status_display }}
</span>
</span>
{% endwith %}
</td>
<td>
<form name="edit-link" class="control" method="post" action="{% url 'file-link' book.id link.id %}">
{% csrf_token %}
<input type="hidden" name="url" value="{{ link.form.url.value }}">
<input type="hidden" name="filetype" value="{{ link.form.filetype.value }}">
<input type="hidden" name="added_by" value="{{ link.form.added_by.value }}">
<input type="hidden" name="book" value="{{ link.form.book.value }}">
<div class="field has-addons">
<div class="control">
<div class="select">
{{ link.form.availability }}
</div>
</div>
<div class="control">
<button class="button is-primary" type="submit">{% trans "Save" %}</button>
</div>
</div>
</form>
</td>
<td>
<form name="delete-link-{{ link.id }}" class="control" method="post" action="{% url 'file-link-delete' book.id link.id %}">
{% csrf_token %}
<button class="button is-danger is-light" type="submit">Delete link</button>
</form>

View file

@ -30,6 +30,12 @@
<li>
<a href="{{ link.url }}" rel="noopener" target="_blank" title="{{ link.url }}" data-modal-open="{{ verify_modal }}">{{ link.name }}</a>
({{ link.filetype }})
{% if link.availability != "free" %}
<p class="help">
{{ link.get_availability_display }}
</p>
{% endif %}
</li>
{% endfor %}
</ul>
@ -46,7 +52,7 @@
<span class="icon icon-pencil" aria-hidden="true"></span>
<span>{% trans "Edit links" %}</span>
</a>
{% include 'book/file_links/add_link_modal.html' with book=book id="add-links" %}
{% endif %}
{% include 'book/file_links/add_link_modal.html' with book=book id="add-links" %}
{% endif %}

View file

@ -67,6 +67,7 @@ class LinkViews(TestCase):
form.data["filetype"] = "HTML"
form.data["book"] = self.book.id
form.data["added_by"] = self.local_user.id
form.data["availability"] = "loan"
request = self.factory.post("", form.data)
request.user = self.local_user
@ -87,3 +88,59 @@ class LinkViews(TestCase):
self.book.refresh_from_db()
self.assertEqual(self.book.file_links.first(), link)
def test_book_links(self):
"""there are so many views, this just makes sure it LOADS"""
view = views.BookFileLinks.as_view()
models.FileLink.objects.create(
book=self.book,
added_by=self.local_user,
url="https://www.hello.com",
)
request = self.factory.get("")
request.user = self.local_user
result = view(request, self.book.id)
self.assertEqual(result.status_code, 200)
validate_html(result.render())
def test_book_links_post(self):
"""there are so many views, this just makes sure it LOADS"""
link = models.FileLink.objects.create(
book=self.book,
added_by=self.local_user,
url="https://www.hello.com",
)
view = views.BookFileLinks.as_view()
form = forms.FileLinkForm()
form.data["url"] = link.url
form.data["filetype"] = "HTML"
form.data["book"] = self.book.id
form.data["added_by"] = self.local_user.id
form.data["availability"] = "loan"
request = self.factory.post("", form.data)
request.user = self.local_user
view(request, self.book.id, link.id)
link.refresh_from_db()
self.assertEqual(link.filetype, "HTML")
self.assertEqual(link.availability, "loan")
def test_delete_link(self):
"""remove a link"""
link = models.FileLink.objects.create(
book=self.book,
added_by=self.local_user,
url="https://www.hello.com",
)
form = forms.FileLinkForm()
form.data["url"] = "https://www.example.com"
form.data["filetype"] = "HTML"
form.data["book"] = self.book.id
form.data["added_by"] = self.local_user.id
form.data["availability"] = "loan"
request = self.factory.post("", form.data)
request.user = self.local_user
views.delete_link(request, self.book.id, link.id)
self.assertFalse(models.FileLink.objects.exists())

View file

@ -473,10 +473,15 @@ urlpatterns = [
rf"{BOOK_PATH}/filelink/?$", views.BookFileLinks.as_view(), name="file-link"
),
re_path(
rf"{BOOK_PATH}/filelink/(?P<link_id>\d+)/delete/?$",
rf"{BOOK_PATH}/filelink/(?P<link_id>\d+)/?$",
views.BookFileLinks.as_view(),
name="file-link",
),
re_path(
rf"{BOOK_PATH}/filelink/(?P<link_id>\d+)/delete/?$",
views.delete_link,
name="file-link-delete",
),
re_path(
rf"{BOOK_PATH}/filelink/add/?$",
views.AddFileLink.as_view(),

View file

@ -37,7 +37,7 @@ from .books.books import (
from .books.books import update_book_from_remote
from .books.edit_book import EditBook, ConfirmEditBook
from .books.editions import Editions, switch_edition
from .books.links import BookFileLinks, AddFileLink
from .books.links import BookFileLinks, AddFileLink, delete_link
# landing
from .landing.about import about, privacy, conduct

View file

@ -5,28 +5,49 @@ from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.views import View
from django.utils.decorators import method_decorator
from django.views.decorators.http import require_POST
from bookwyrm import forms, models
# pylint: disable=no-self-use
@method_decorator(login_required, name="dispatch")
@method_decorator(
permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"
)
class BookFileLinks(View):
"""View all links"""
def get(self, request, book_id):
"""view links"""
book = get_object_or_404(models.Edition, id=book_id)
return TemplateResponse(
request, "book/file_links/edit_links.html", {"book": book}
)
links = book.file_links.order_by("domain__status", "created_date")
annotated_links = []
for link in links.all():
link.form = forms.FileLinkForm(instance=link)
annotated_links.append(link)
data = {"book": book, "links": annotated_links}
return TemplateResponse(request, "book/file_links/edit_links.html", data)
def post(self, request, book_id, link_id):
"""delete link"""
"""Edit a link"""
link = get_object_or_404(models.FileLink, id=link_id, book=book_id)
link.delete()
form = forms.FileLinkForm(request.POST, instance=link)
form.save()
return self.get(request, book_id)
@require_POST
@login_required
# pylint: disable=unused-argument
def delete_link(request, book_id, link_id):
"""delete link"""
link = get_object_or_404(models.FileLink, id=link_id, book=book_id)
link.delete()
return redirect("file-link", book_id)
@method_decorator(login_required, name="dispatch")
@method_decorator(
permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"