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 FileLinkForm(CustomForm):
class Meta: class Meta:
model = models.FileLink model = models.FileLink
fields = ["url", "filetype", "book", "added_by"] fields = ["url", "filetype", "availability", "book", "added_by"]
class EditionForm(CustomForm): 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) return super().save(*args, **kwargs)
AvailabilityChoices = [
("free", _("Free")),
("purchase", _("Purchasable")),
("loan", _("Available for loan")),
]
class FileLink(Link): class FileLink(Link):
"""a link to a file""" """a link to a file"""
book = models.ForeignKey( book = models.ForeignKey(
"Book", on_delete=models.CASCADE, related_name="file_links", null=True "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 = [ StatusChoices = [

View file

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

View file

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

View file

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

View file

@ -33,9 +33,10 @@
<th>{% trans "Added by" %}</th> <th>{% trans "Added by" %}</th>
<th>{% trans "Filetype" %}</th> <th>{% trans "Filetype" %}</th>
<th>{% trans "Domain" %}</th> <th>{% trans "Domain" %}</th>
<th>{% trans "Actions" %}</th> <th>{% trans "Status" %}</th>
<th colspan="2">{% trans "Actions" %}</th>
</tr> </tr>
{% for link in book.file_links.all %} {% for link in links %}
<tr> <tr>
<td class="overflow-wrap-anywhere"> <td class="overflow-wrap-anywhere">
<a href="{{ link.url }}" target="_blank" rel="noopener">{{ link.url }}</a> <a href="{{ link.url }}" target="_blank" rel="noopener">{{ link.url }}</a>
@ -47,13 +48,44 @@
{{ link.filelink.filetype }} {{ link.filelink.filetype }}
</td> </td>
<td> <td>
{{ link.domain.name }} ({{ link.domain.get_status_display }}) {{ link.domain.name }}
<p> <p>
<a href="{% url 'report-link' link.added_by.id link.id %}">{% trans "Report spam" %}</a> <a href="{% url 'report-link' link.added_by.id link.id %}">{% trans "Report spam" %}</a>
</p> </p>
</td> </td>
<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 %} {% csrf_token %}
<button class="button is-danger is-light" type="submit">Delete link</button> <button class="button is-danger is-light" type="submit">Delete link</button>
</form> </form>

View file

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

View file

@ -67,6 +67,7 @@ class LinkViews(TestCase):
form.data["filetype"] = "HTML" form.data["filetype"] = "HTML"
form.data["book"] = self.book.id form.data["book"] = self.book.id
form.data["added_by"] = self.local_user.id form.data["added_by"] = self.local_user.id
form.data["availability"] = "loan"
request = self.factory.post("", form.data) request = self.factory.post("", form.data)
request.user = self.local_user request.user = self.local_user
@ -87,3 +88,59 @@ class LinkViews(TestCase):
self.book.refresh_from_db() self.book.refresh_from_db()
self.assertEqual(self.book.file_links.first(), link) 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" rf"{BOOK_PATH}/filelink/?$", views.BookFileLinks.as_view(), name="file-link"
), ),
re_path( re_path(
rf"{BOOK_PATH}/filelink/(?P<link_id>\d+)/delete/?$", rf"{BOOK_PATH}/filelink/(?P<link_id>\d+)/?$",
views.BookFileLinks.as_view(), views.BookFileLinks.as_view(),
name="file-link", name="file-link",
), ),
re_path(
rf"{BOOK_PATH}/filelink/(?P<link_id>\d+)/delete/?$",
views.delete_link,
name="file-link-delete",
),
re_path( re_path(
rf"{BOOK_PATH}/filelink/add/?$", rf"{BOOK_PATH}/filelink/add/?$",
views.AddFileLink.as_view(), views.AddFileLink.as_view(),

View file

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

View file

@ -5,26 +5,47 @@ from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.views import View from django.views import View
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.http import require_POST
from bookwyrm import forms, models from bookwyrm import forms, models
# pylint: disable=no-self-use # 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): class BookFileLinks(View):
"""View all links""" """View all links"""
def get(self, request, book_id): def get(self, request, book_id):
"""view links""" """view links"""
book = get_object_or_404(models.Edition, id=book_id) book = get_object_or_404(models.Edition, id=book_id)
return TemplateResponse( links = book.file_links.order_by("domain__status", "created_date")
request, "book/file_links/edit_links.html", {"book": book} 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): def post(self, request, book_id, link_id):
"""Edit a link"""
link = get_object_or_404(models.FileLink, id=link_id, book=book_id)
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""" """delete link"""
link = get_object_or_404(models.FileLink, id=link_id, book=book_id) link = get_object_or_404(models.FileLink, id=link_id, book=book_id)
link.delete() link.delete()
return self.get(request, book_id) return redirect("file-link", book_id)
@method_decorator(login_required, name="dispatch") @method_decorator(login_required, name="dispatch")