mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-26 19:41:11 +00:00
Merge pull request #1834 from bookwyrm-social/links-display
Improve link filetype and add availability field
This commit is contained in:
commit
8a0ea674ea
12 changed files with 206 additions and 16 deletions
|
@ -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):
|
||||||
|
|
32
bookwyrm/migrations/0129_auto_20220117_1716.py
Normal file
32
bookwyrm/migrations/0129_auto_20220117_1716.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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 = [
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -5,28 +5,49 @@ 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):
|
||||||
"""delete link"""
|
"""Edit a 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()
|
form = forms.FileLinkForm(request.POST, instance=link)
|
||||||
|
form.save()
|
||||||
return self.get(request, book_id)
|
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(login_required, name="dispatch")
|
||||||
@method_decorator(
|
@method_decorator(
|
||||||
permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"
|
permission_required("bookwyrm.edit_book", raise_exception=True), name="dispatch"
|
||||||
|
|
Loading…
Reference in a new issue