Merge branch 'main' into top-bar
This commit is contained in:
@ -74,6 +74,14 @@ class Connector(AbstractConnector):
|
||||
**{k: data.get(k) for k in ["uri", "image", "labels", "sitelinks"]},
|
||||
}
|
||||
|
||||
def search(self, query, min_confidence=None):
|
||||
"""overrides default search function with confidence ranking"""
|
||||
results = super().search(query)
|
||||
if min_confidence:
|
||||
# filter the search results after the fact
|
||||
return [r for r in results if r.confidence >= min_confidence]
|
||||
return results
|
||||
|
||||
def parse_search_data(self, data):
|
||||
return data.get("results")
|
||||
|
||||
@ -84,6 +92,9 @@ class Connector(AbstractConnector):
|
||||
if images
|
||||
else None
|
||||
)
|
||||
# a deeply messy translation of inventaire's scores
|
||||
confidence = float(search_result.get("_score", 0.1))
|
||||
confidence = 0.1 if confidence < 150 else 0.999
|
||||
return SearchResult(
|
||||
title=search_result.get("label"),
|
||||
key=self.get_remote_id(search_result.get("uri")),
|
||||
@ -92,6 +103,7 @@ class Connector(AbstractConnector):
|
||||
self.base_url, search_result.get("uri")
|
||||
),
|
||||
cover=cover,
|
||||
confidence=confidence,
|
||||
connector=self,
|
||||
)
|
||||
|
||||
|
@ -3,3 +3,4 @@
|
||||
from .importer import Importer
|
||||
from .goodreads_import import GoodreadsImporter
|
||||
from .librarything_import import LibrarythingImporter
|
||||
from .storygraph_import import StorygraphImporter
|
||||
|
34
bookwyrm/importers/storygraph_import.py
Normal file
34
bookwyrm/importers/storygraph_import.py
Normal file
@ -0,0 +1,34 @@
|
||||
""" handle reading a csv from librarything """
|
||||
import re
|
||||
import math
|
||||
|
||||
from . import Importer
|
||||
|
||||
|
||||
class StorygraphImporter(Importer):
|
||||
"""csv downloads from librarything"""
|
||||
|
||||
service = "Storygraph"
|
||||
# mandatory_fields : fields matching the book title and author
|
||||
mandatory_fields = ["Title"]
|
||||
|
||||
def parse_fields(self, entry):
|
||||
"""custom parsing for storygraph"""
|
||||
data = {}
|
||||
data["import_source"] = self.service
|
||||
data["Title"] = entry["Title"]
|
||||
data["Author"] = entry["Authors"] if "Authors" in entry else entry["Author"]
|
||||
data["ISBN13"] = entry["ISBN"]
|
||||
data["My Review"] = entry["Review"]
|
||||
if entry["Star Rating"]:
|
||||
data["My Rating"] = math.ceil(float(entry["Star Rating"]))
|
||||
else:
|
||||
data["My Rating"] = ""
|
||||
|
||||
data["Date Added"] = re.sub(r"[/]", "-", entry["Date Added"])
|
||||
data["Date Read"] = re.sub(r"[/]", "-", entry["Last Date Read"])
|
||||
|
||||
data["Exclusive Shelf"] = (
|
||||
{"read": "read", "currently-reading": "reading", "to-read": "to-read"}
|
||||
).get(entry["Read Status"], None)
|
||||
return data
|
@ -128,7 +128,9 @@ class ImportItem(models.Model):
|
||||
@property
|
||||
def rating(self):
|
||||
"""x/5 star rating for a book"""
|
||||
return int(self.data["My Rating"])
|
||||
if self.data.get("My Rating", None):
|
||||
return int(self.data["My Rating"])
|
||||
return None
|
||||
|
||||
@property
|
||||
def date_added(self):
|
||||
|
@ -1,40 +0,0 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
{% block title %}{{ author.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="block">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column">
|
||||
<h1 class="title">{{ author.name }}</h1>
|
||||
</div>
|
||||
{% if request.user.is_authenticated and perms.bookwyrm.edit_book %}
|
||||
<div class="column is-narrow">
|
||||
<a href="{{ author.local_path }}/edit">
|
||||
<span class="icon icon-pencil" title="{% trans 'Edit Author' %}">
|
||||
<span class="is-sr-only">{% trans "Edit Author" %}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block content">
|
||||
{% if author.bio %}
|
||||
{{ author.bio | to_markdown | safe }}
|
||||
{% endif %}
|
||||
|
||||
{% if author.wikipedia_link %}
|
||||
<p><a href="{{ author.wikipedia_link }}" rel=”noopener” target="_blank">{% trans "Wikipedia" %}</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<h3 class="title is-4">{% blocktrans with name=author.name %}Books by {{ name }}{% endblocktrans %}</h3>
|
||||
{% include 'snippets/book_tiles.html' with books=books %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
83
bookwyrm/templates/author/author.html
Normal file
83
bookwyrm/templates/author/author.html
Normal file
@ -0,0 +1,83 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load markdown %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}{{ author.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="block">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column">
|
||||
<h1 class="title">{{ author.name }}</h1>
|
||||
</div>
|
||||
{% if request.user.is_authenticated and perms.bookwyrm.edit_book %}
|
||||
<div class="column is-narrow">
|
||||
<a href="{{ author.local_path }}/edit">
|
||||
<span class="icon icon-pencil" title="{% trans 'Edit Author' %}" aria-hidden="True"></span>
|
||||
<span>{% trans "Edit Author" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block content columns">
|
||||
{% if author.aliases or author.born or author.died or author.wikipedia_link %}
|
||||
<div class="column is-narrow">
|
||||
<div class="box">
|
||||
<dl>
|
||||
{% if author.aliases %}
|
||||
<div class="is-flex">
|
||||
<dt class="mr-1">{% trans "Aliases:" %}</dt>
|
||||
<dd itemprop="aliases">{{ author.aliases|join:', ' }}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if author.born %}
|
||||
<div class="is-flex">
|
||||
<dt class="mr-1">{% trans "Born:" %}</dt>
|
||||
<dd itemprop="aliases">{{ author.born|naturalday }}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if author.aliases %}
|
||||
<div class="is-flex">
|
||||
<dt class="mr-1">{% trans "Died:" %}</dt>
|
||||
<dd itemprop="aliases">{{ author.died|naturalday }}</dd>
|
||||
</div>
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
{% if author.wikipedia_link %}
|
||||
<p><a href="{{ author.wikipedia_link }}" rel=”noopener” target="_blank">{% trans "Wikipedia" %}</a></p>
|
||||
{% endif %}
|
||||
{% if author.openlibrary_key %}
|
||||
<p class="mb-0">
|
||||
<a href="https://openlibrary.org/authors/{{ author.openlibrary_key }}" target="_blank" rel="noopener">{% trans "View on OpenLibrary" %}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if author.inventaire_id %}
|
||||
<p class="mb-0">
|
||||
<a href="https://inventaire.io/entity/{{ author.inventaire_id }}" target="_blank" rel="noopener">{% trans "View on Inventaire" %}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="column">
|
||||
{% if author.bio %}
|
||||
{{ author.bio|to_markdown|safe }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<h3 class="title is-4">{% blocktrans with name=author.name %}Books by {{ name }}{% endblocktrans %}</h3>
|
||||
<div class="columns is-multiline is-mobile">
|
||||
{% for book in books %}
|
||||
<div class="column is-one-fifth">
|
||||
{% include 'discover/small-book.html' with book=book %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -29,44 +29,64 @@
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h2 class="title is-4">{% trans "Metadata" %}</h2>
|
||||
<p><label class="label" for="id_name">{% trans "Name:" %}</label> {{ form.name }}</p>
|
||||
<p class="mb-2"><label class="label" for="id_name">{% trans "Name:" %}</label> {{ form.name }}</p>
|
||||
{% for error in form.name.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
|
||||
<p><label class="label" for="id_bio">{% trans "Bio:" %}</label> {{ form.bio }}</p>
|
||||
<p class="mb-2">
|
||||
<label class="label" for="id_aliases">{% trans "Aliases:" %}</label>
|
||||
{{ form.aliases }}
|
||||
<span class="help">{% trans "Separate multiple values with commas." %}</span>
|
||||
</p>
|
||||
{% for error in form.aliases.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
|
||||
<p class="mb-2"><label class="label" for="id_bio">{% trans "Bio:" %}</label> {{ form.bio }}</p>
|
||||
{% for error in form.bio.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
|
||||
<p><label class="label" for="id_wikipedia_link">{% trans "Wikipedia link:" %}</label> {{ form.wikipedia_link }}</p>
|
||||
<p class="mb-2"><label class="label" for="id_wikipedia_link">{% trans "Wikipedia link:" %}</label> {{ form.wikipedia_link }}</p>
|
||||
{% for error in form.wikipedia_link.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
|
||||
<p><label class="label" for="id_born">{% trans "Birth date:" %}</label> {{ form.born }}</p>
|
||||
<p class="mb-2">
|
||||
<label class="label" for="id_born">{% trans "Birth date:" %}</label>
|
||||
<input type="date" name="born" value="{{ form.born.value|date:'Y-m-d' }}" class="input" id="id_born">
|
||||
</p>
|
||||
{% for error in form.born.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
|
||||
<p><label class="label" for="id_died">{% trans "Death date:" %}</label> {{ form.died }}</p>
|
||||
<p class="mb-2">
|
||||
<label class="label" for="id_died">{% trans "Death date:" %}</label>
|
||||
<input type="date" name="died" value="{{ form.died.value|date:'Y-m-d' }}" class="input" id="id_died">
|
||||
</p>
|
||||
{% for error in form.died.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="column">
|
||||
<h2 class="title is-4">{% trans "Author Identifiers" %}</h2>
|
||||
<p><label class="label" for="id_openlibrary_key">{% trans "Openlibrary key:" %}</label> {{ form.openlibrary_key }}</p>
|
||||
<p class="mb-2"><label class="label" for="id_openlibrary_key">{% trans "Openlibrary key:" %}</label> {{ form.openlibrary_key }}</p>
|
||||
{% for error in form.openlibrary_key.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
|
||||
<p><label class="label" for="id_librarything_key">{% trans "Librarything key:" %}</label> {{ form.librarything_key }}</p>
|
||||
<p class="mb-2"><label class="label" for="id_inventaire_id">{% trans "Inventaire ID:" %}</label> {{ form.inventaire_id }}</p>
|
||||
{% for error in form.inventaire_id.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
|
||||
<p class="mb-2"><label class="label" for="id_librarything_key">{% trans "Librarything key:" %}</label> {{ form.librarything_key }}</p>
|
||||
{% for error in form.librarything_key.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
||||
|
||||
<p><label class="label" for="id_goodreads_key">{% trans "Goodreads key:" %}</label> {{ form.goodreads_key }}</p>
|
||||
<p class="mb-2"><label class="label" for="id_goodreads_key">{% trans "Goodreads key:" %}</label> {{ form.goodreads_key }}</p>
|
||||
{% for error in form.goodreads_key.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
{% endfor %}
|
@ -38,9 +38,8 @@
|
||||
{% if user_authenticated and can_edit_book %}
|
||||
<div class="column is-narrow">
|
||||
<a href="{{ book.id }}/edit">
|
||||
<span class="icon icon-pencil" title="{% trans "Edit Book" %}">
|
||||
<span class="is-sr-only">{% trans "Edit Book" %}</span>
|
||||
</span>
|
||||
<span class="icon icon-pencil" title="{% trans "Edit Book" %}" aria-hidden=True></span>
|
||||
<span>{% trans "Edit Book" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -163,12 +162,9 @@
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
{% trans "Add read dates" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon="plus" class="is-small" controls_text="add-readthrough" %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="plus" class="is-small" controls_text="add-readthrough" focus="add-readthrough-focus" %}
|
||||
</div>
|
||||
</header>
|
||||
{% if not readthroughs.exists %}
|
||||
<p>{% trans "You don't have any reading activity for this book." %}</p>
|
||||
{% endif %}
|
||||
<section class="is-hidden box" id="add-readthrough">
|
||||
<form name="add-readthrough" action="/create-readthrough" method="post">
|
||||
{% include 'snippets/readthrough_form.html' with readthrough=None %}
|
||||
@ -183,6 +179,9 @@
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
{% if not readthroughs.exists %}
|
||||
<p>{% trans "You don't have any reading activity for this book." %}</p>
|
||||
{% endif %}
|
||||
{% for readthrough in readthroughs %}
|
||||
{% include 'snippets/readthrough.html' with readthrough=readthrough %}
|
||||
{% endfor %}
|
||||
@ -257,7 +256,7 @@
|
||||
{% include 'snippets/stars.html' with rating=rating.rating %}
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ rating.remote_id }}">{{ rating.published_date | naturaltime }}</a>
|
||||
<a href="{{ rating.remote_id }}">{{ rating.published_date|naturaltime }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -125,7 +125,7 @@
|
||||
<p class="mb-2">
|
||||
<label class="label" for="id_publishers">{% trans "Publisher:" %}</label>
|
||||
{{ form.publishers }}
|
||||
<span class="help">{% trans "Separate multiple publishers with commas." %}</span>
|
||||
<span class="help">{% trans "Separate multiple values with commas." %}</span>
|
||||
</p>
|
||||
{% for error in form.publishers.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
@ -162,7 +162,7 @@
|
||||
{% endif %}
|
||||
<label class="label" for="id_add_author">{% trans "Add Authors:" %}</label>
|
||||
<input class="input" type="text" name="add_author" id="id_add_author" placeholder="{% trans 'John Doe, Jane Smith' %}" value="{{ add_author }}" {% if confirm_mode %}readonly{% endif %}>
|
||||
<p class="help">Separate multiple author names with commas.</p>
|
||||
<span class="help">{% trans "Separate multiple values with commas." %}</span>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
{% block title %}{% blocktrans with book_title=work.title %}Editions of {{ book_title }}{% endblocktrans %}{% endblock %}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% spaceless %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
|
||||
{% with 0|uuid as uuid %}
|
||||
<div
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block title %}{% trans "Compose status" %}{% endblock %}
|
||||
{% block content %}
|
||||
|
@ -1,5 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
{% load markdown %}
|
||||
{% load humanize %}
|
||||
|
||||
<div class="card is-stretchable">
|
||||
@ -19,7 +20,7 @@
|
||||
|
||||
<div>
|
||||
{% if user.summary %}
|
||||
{{ user.summary | to_markdown | safe | truncatechars_html:40 }}
|
||||
{{ user.summary|to_markdown|safe|truncatechars_html:40 }}
|
||||
{% else %} {% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
|
||||
{% block title %}{% trans "Welcome" %}{% endblock %}
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
{% else %}
|
||||
|
||||
<h2 class="title">{% trans "This instance is closed" %}</h2>
|
||||
<p>{{ site.registration_closed_text | safe}}</p>
|
||||
<p>{{ site.registration_closed_text|safe}}</p>
|
||||
|
||||
{% if site.allow_invite_requests %}
|
||||
{% if request_received %}
|
||||
@ -64,7 +64,7 @@
|
||||
<label for="id_request_email" class="label">{% trans "Email address:" %}</label>
|
||||
<input type="email" name="email" maxlength="255" class="input" required="" id="id_request_email">
|
||||
{% for error in request_form.email.errors %}
|
||||
<p class="help is-danger">{{ error | escape }}</p>
|
||||
<p class="help is-danger">{{ error|escape }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="submit" class="button is-link">{% trans "Submit" %}</button>
|
||||
@ -80,7 +80,7 @@
|
||||
{% include 'user/user_preview.html' with user=request.user %}
|
||||
{% if request.user.summary %}
|
||||
<div class="box content">
|
||||
{{ request.user.summary | to_markdown | safe }}
|
||||
{{ request.user.summary|to_markdown|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if book %}
|
||||
|
@ -1,7 +1,6 @@
|
||||
{% extends 'feed/feed_layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block panel %}
|
||||
|
||||
<h1 class="title">
|
||||
|
@ -1,6 +1,5 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
{% block title %}{% trans "Updates" %}{% endblock %}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
{% load humanize %}
|
||||
<div class="columns is-mobile scroll-x mb-0">
|
||||
{% for user in suggested_users %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load status_display %}
|
||||
<div class="block">
|
||||
|
||||
{% with depth=depth|add:1 %}
|
||||
|
@ -9,7 +9,7 @@
|
||||
{% if is_self and goal %}
|
||||
<div class="column is-narrow">
|
||||
{% trans "Edit Goal" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon="pencil" controls_text="show-edit-goal" focus="edit-form-header" %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="pencil" controls_text="show-edit-goal" focus="edit-form-header" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -20,6 +20,9 @@
|
||||
<option value="GoodReads" {% if current == 'GoodReads' %}selected{% endif %}>
|
||||
GoodReads (CSV)
|
||||
</option>
|
||||
<option value="Storygraph" {% if current == 'Storygraph' %}selected{% endif %}>
|
||||
Storygraph (CSV)
|
||||
</option>
|
||||
<option value="LibraryThing" {% if current == 'LibraryThing' %}selected{% endif %}>
|
||||
LibraryThing (TSV)
|
||||
</option>
|
||||
|
@ -1,6 +1,5 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}{% trans "Import Status" %}{% endblock %}
|
||||
@ -54,8 +53,8 @@
|
||||
<input class="checkbox" type="checkbox" name="import_item" value="{{ item.id }}" id="import-item-{{ item.id }}">
|
||||
<label for="import-item-{{ item.id }}">
|
||||
Line {{ item.index }}:
|
||||
<strong>{{ item.data|dict_key:'Title' }}</strong> by
|
||||
{{ item.data|dict_key:'Author' }}
|
||||
<strong>{{ item.data.Title }}</strong> by
|
||||
{{ item.data.Author }}
|
||||
</label>
|
||||
<p>
|
||||
{{ item.fail_reason }}.
|
||||
@ -90,8 +89,8 @@
|
||||
<li class="pb-1">
|
||||
<p>
|
||||
Line {{ item.index }}:
|
||||
<strong>{{ item.data|dict_key:'Title' }}</strong> by
|
||||
{{ item.data|dict_key:'Author' }}
|
||||
<strong>{{ item.data.Title }}</strong> by
|
||||
{{ item.data.Author }}
|
||||
</p>
|
||||
<p>
|
||||
{{ item.fail_reason }}.
|
||||
@ -130,10 +129,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.data|dict_key:'Title' }}
|
||||
{{ item.data.Title }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.data|dict_key:'Author' }}
|
||||
{{ item.data.Author }}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.book %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load layout %}
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{% get_lang %}">
|
||||
@ -209,7 +209,7 @@
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
{% trans 'BookWyrm is open source software. You can contribute or report issues on <a href="https://github.com/mouse-reeve/bookwyrm">GitHub</a>.' %}
|
||||
{% blocktrans %}BookWyrm's source code is freely available. You can contribute or report issues on <a href="https://github.com/mouse-reeve/bookwyrm">GitHub</a>.{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
{% if site.footer_item %}
|
||||
|
@ -1,12 +1,13 @@
|
||||
{% extends 'lists/list_layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
|
||||
{% block panel %}
|
||||
{% if request.user == list.user and pending_count %}
|
||||
<div class="block content">
|
||||
<p>
|
||||
<a href="{% url 'list-curate' list.id %}">{{ pending_count }} book{{ pending_count | pluralize }} awaiting your approval</a>
|
||||
<a href="{% url 'list-curate' list.id %}">{{ pending_count }} book{{ pending_count|pluralize }} awaiting your approval</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
<div class="columns is-multiline">
|
||||
{% for list in lists %}
|
||||
<div class="column is-one-quarter">
|
||||
|
@ -1,6 +1,5 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
{% block title %}{{ list.name }}{% endblock %}
|
||||
|
||||
@ -16,7 +15,7 @@
|
||||
{% if request.user == list.user %}
|
||||
<div class="column is-narrow">
|
||||
{% trans "Edit List" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon="pencil" controls_text="edit-list" focus="edit-list-header" %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="pencil" controls_text="edit-list" focus="edit-list-header" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Lists" %}{% endblock %}
|
||||
@ -18,7 +18,7 @@
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="column is-narrow">
|
||||
{% trans "Create List" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with controls_text="create-list" icon="plus" text=button_text focus="create-list-header" %}
|
||||
{% include 'snippets/toggle/open_button.html' with controls_text="create-list" icon_with_text="plus" text=button_text focus="create-list-header" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
@ -1,6 +1,5 @@
|
||||
{% extends 'settings/admin_layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}{% blocktrans with report_id=report.id username=report.user.username %}Report #{{ report_id }}: {{ username }}{% endblocktrans %}{% endblock %}
|
||||
@ -29,7 +28,7 @@
|
||||
<a href="{{ comment.user.local_path }}">{{ comment.user.display_name }}</a>
|
||||
</div>
|
||||
<div class="card-footer-item">
|
||||
{{ comment.created_date | naturaltime }}
|
||||
{{ comment.created_date|naturaltime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}{% trans "Notifications" %}{% endblock %}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
{% extends 'search/layout.html' %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
{% block panel %}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
{% extends 'settings/admin_layout.html' %}
|
||||
{% block title %}{{ server.server_name }}{% endblock %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
|
||||
{% block title %}{{ server.server_name }}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
{{ server.server_name }}
|
||||
@ -14,60 +15,64 @@
|
||||
|
||||
{% block panel %}
|
||||
<div class="columns">
|
||||
<section class="column is-half content">
|
||||
<section class="column is-half is-flex is-flex-direction-column">
|
||||
<h2 class="title is-4">{% trans "Details" %}</h2>
|
||||
<dl>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Software:" %}</dt>
|
||||
<dd>{{ server.application_type }}</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Version:" %}</dt>
|
||||
<dd>{{ server.application_version }}</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Status:" %}</dt>
|
||||
<dd>{{ server.status }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="box is-flex-grow-1 content">
|
||||
<dl>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Software:" %}</dt>
|
||||
<dd>{{ server.application_type }}</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Version:" %}</dt>
|
||||
<dd>{{ server.application_version }}</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Status:" %}</dt>
|
||||
<dd>{{ server.status }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="column is-half content">
|
||||
<section class="column is-half is-flex is-flex-direction-column">
|
||||
<h2 class="title is-4">{% trans "Activity" %}</h2>
|
||||
<dl>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Users:" %}</dt>
|
||||
<dd>
|
||||
{{ users.count }}
|
||||
{% if server.user_set.count %}(<a href="{% url 'settings-users' %}?server={{ server.server_name }}">{% trans "View all" %}</a>){% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Reports:" %}</dt>
|
||||
<dd>
|
||||
{{ reports.count }}
|
||||
{% if reports.count %}(<a href="{% url 'settings-reports' %}?server={{ server.server_name }}">{% trans "View all" %}</a>){% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Followed by us:" %}</dt>
|
||||
<dd>
|
||||
{{ followed_by_us.count }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Followed by them:" %}</dt>
|
||||
<dd>
|
||||
{{ followed_by_them.count }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Blocked by us:" %}</dt>
|
||||
<dd>
|
||||
{{ blocked_by_us.count }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="box is-flex-grow-1 content">
|
||||
<dl>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Users:" %}</dt>
|
||||
<dd>
|
||||
{{ users.count }}
|
||||
{% if server.user_set.count %}(<a href="{% url 'settings-users' %}?server={{ server.server_name }}">{% trans "View all" %}</a>){% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Reports:" %}</dt>
|
||||
<dd>
|
||||
{{ reports.count }}
|
||||
{% if reports.count %}(<a href="{% url 'settings-reports' %}?server={{ server.server_name }}">{% trans "View all" %}</a>){% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Followed by us:" %}</dt>
|
||||
<dd>
|
||||
{{ followed_by_us.count }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Followed by them:" %}</dt>
|
||||
<dd>
|
||||
{{ followed_by_them.count }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="is-flex">
|
||||
<dt>{% trans "Blocked by us:" %}</dt>
|
||||
<dd>
|
||||
{{ blocked_by_us.count }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -78,11 +83,11 @@
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
{% trans "Edit" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon="pencil" controls_text="edit-notes" %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="pencil" controls_text="edit-notes" %}
|
||||
</div>
|
||||
</header>
|
||||
{% if server.notes %}
|
||||
<p id="hide-edit-notes">{{ server.notes|to_markdown|safe }}</p>
|
||||
<div class="box" id="hide-edit-notes">{{ server.notes|to_markdown|safe }}</div>
|
||||
{% endif %}
|
||||
<form class="box is-hidden" method="POST" action="{% url 'settings-federated-server' server.id %}" id="edit-notes">
|
||||
{% csrf_token %}
|
||||
|
@ -6,9 +6,8 @@
|
||||
|
||||
{% block edit-button %}
|
||||
<a href="{% url 'settings-import-blocklist' %}">
|
||||
<span class="icon icon-plus" title="{% trans 'Add server' %}">
|
||||
<span class="is-sr-only">{% trans "Add server" %}</span>
|
||||
</span>
|
||||
<span class="icon icon-plus" title="{% trans 'Add server' %}" aria-hidden="True"></span>
|
||||
<span>{% trans "Add server" %}</span>
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -1,3 +1,2 @@
|
||||
{% load bookwyrm_tags %}
|
||||
<img class="avatar image {% if large %}is-96x96{% elif medium %}is-48x48{% else %}is-32x32{% endif %}" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}" {% if ariaHide %}aria-hidden="true"{% endif %} alt="{{ user.alt_text }}">
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
{% spaceless %}
|
||||
|
||||
{% load bookwyrm_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
<figure
|
||||
|
@ -1,16 +0,0 @@
|
||||
<div class="columns is-mobile is-multiline">
|
||||
{% for book in books %}
|
||||
<div class="column is-narrow">
|
||||
<div class="box is-flex is-flex-direction-column is-align-items-center">
|
||||
<div class="mb-3">
|
||||
<a href="{{ book.local_path }}">
|
||||
{% include 'snippets/book_cover.html' with book=book cover_class='is-w-l-mobile is-h-l-mobile is-w-l-tablet is-h-xl-tablet' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% include 'snippets/shelve_button/shelve_button.html' with book=book switch_mode=True %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
{% if book.authors %}
|
||||
{% blocktrans with path=book.local_path title=book|title %}<a href="{{ path }}">{{ title }}</a> by {% endblocktrans %}{% include 'snippets/authors.html' with book=book %}
|
||||
{% else %}
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load interaction %}
|
||||
{% load utilities %}
|
||||
{% load i18n %}
|
||||
|
||||
{% with status.id|uuid as uuid %}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% load humanize %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
|
||||
{% with status_type=request.GET.status_type %}
|
||||
<div class="tab-group">
|
||||
|
@ -1,4 +1,7 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
{% load status_display %}
|
||||
|
||||
{% load i18n %}
|
||||
<form class="is-flex-grow-1" name="{{ type }}" action="/post/{% if type == 'direct' %}status{% else %}{{ type }}{% endif %}" method="post" id="tab-{{ type }}-{{ book.id }}{{ reply_parent.id }}">
|
||||
{% csrf_token %}
|
||||
@ -100,7 +103,7 @@
|
||||
|
||||
{# bottom bar #}
|
||||
<input type="checkbox" class="is-hidden" name="sensitive" id="id_show_spoilers-{{ uuid }}" {% if draft.content_warning or status.content_warning %}checked{% endif %} aria-hidden="true">
|
||||
|
||||
|
||||
<div class="columns mt-1">
|
||||
<div class="field has-addons column">
|
||||
<div class="control">
|
||||
|
@ -1,5 +1,7 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load interaction %}
|
||||
{% load utilities %}
|
||||
{% load i18n %}
|
||||
|
||||
{% with status.id|uuid as uuid %}
|
||||
<form name="favorite" action="/favorite/{{ status.id }}" method="POST" class="interaction fav-{{ status.id }}-{{ uuid }} {% if request.user|liked:status %}is-hidden{% endif %}" data-id="fav-{{ status.id }}-{{ uuid }}">
|
||||
{% csrf_token %}
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
<span class="column is-narrow pb-0">
|
||||
{% trans "Show filters" as text %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=text controls_text="filters" icon="arrow-down" class="is-small" focus="filters" %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=text controls_text="filters" icon_with_text="arrow-down" class="is-small" focus="filters" %}
|
||||
{% trans "Hide filters" as text %}
|
||||
{% include 'snippets/toggle/close_button.html' with text=text controls_text="filters" icon="x" class="is-small" %}
|
||||
{% include 'snippets/toggle/close_button.html' with text=text controls_text="filters" icon_with_text="arrow-up" class="is-small" %}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% if request.user|follow_request_exists:user %}
|
||||
{% if request.user in user.follow_requests.all %}
|
||||
<div class="field is-grouped">
|
||||
<form action="/accept-follow-request/" method="POST">
|
||||
{% csrf_token %}
|
||||
|
@ -1,6 +1,5 @@
|
||||
{% spaceless %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
<div class="
|
||||
field is-grouped
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
<div class="select {{ class }}">
|
||||
{% with 0|uuid as uuid %}
|
||||
{% if not no_label %}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<input type="hidden" name="id" value="{{ readthrough.id }}">
|
||||
<input type="hidden" name="book" value="{{ book.id }}">
|
||||
<div class="field">
|
||||
<label class="label">
|
||||
<label class="label" tabindex="0" id="add-readthrough-focus">
|
||||
{% trans "Started reading" %}
|
||||
<input type="date" name="start_date" class="input" id="id_start_date-{{ readthrough.id }}" value="{{ readthrough.start_date | date:"Y-m-d" }}">
|
||||
</label>
|
||||
|
@ -1,5 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
|
||||
{% with 0|uuid as report_uuid %}
|
||||
|
||||
{% trans "Report" as button_text %}
|
||||
|
@ -1,4 +1,6 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
|
||||
{% with book.id|uuid as uuid %}
|
||||
|
@ -1,5 +1,7 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
{% load i18n %}
|
||||
|
||||
{% for shelf in shelves %}
|
||||
{% comparison_bool shelf.identifier active_shelf.shelf.identifier as is_current %}
|
||||
{% if dropdown %}<li role="menuitem" class="dropdown-item p-0">{% endif %}
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
{% load i18n %}
|
||||
|
||||
{% with status_type=status.status_type %}
|
||||
|
@ -1,6 +1,7 @@
|
||||
{% spaceless %}
|
||||
|
||||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if not hide_book %}
|
||||
|
@ -1,7 +1,6 @@
|
||||
{% extends 'components/card.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load humanize %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block card-header %}
|
||||
<div class="card-header-title has-background-white-ter is-block">
|
||||
|
@ -1,5 +1,6 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load status_display %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if not status.deleted %}
|
||||
{% if status.status_type == 'Announce' %}
|
||||
<a href="{{ status.user.local_path }}">
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
{% load status_display %}
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
|
||||
@ -29,7 +30,7 @@
|
||||
</span>
|
||||
|
||||
{% if status.status_type == 'GeneratedNote' %}
|
||||
{{ status.content | safe }}
|
||||
{{ status.content|safe }}
|
||||
{% elif status.status_type == 'Rating' %}
|
||||
{% trans "rated" %}
|
||||
{% elif status.status_type == 'Review' %}
|
||||
@ -91,7 +92,7 @@
|
||||
|
||||
</h3>
|
||||
<p class="is-size-7 is-flex is-align-items-center">
|
||||
<a href="{{ status.remote_id }}">{{ status.published_date|timesince }}</a>
|
||||
<a href="{{ status.remote_id }}">{{ status.published_date|published_date }}</a>
|
||||
{% if status.progress %}
|
||||
<span class="ml-1">
|
||||
{% if status.progress_mode == 'PG' %}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends 'components/dropdown.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block dropdown-trigger %}
|
||||
<span class="icon icon-dots-three m-0-mobile"></span>
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
{% load utilities %}
|
||||
{% load i18n %}
|
||||
|
||||
{% with 0|uuid as uuid %}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends 'components/dropdown.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block dropdown-trigger %}
|
||||
<span class="icon icon-dots-three">
|
||||
|
@ -1,7 +1,8 @@
|
||||
{% extends 'layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
{% load markdown %}
|
||||
|
||||
{% block title %}{{ user.display_name }}{% endblock %}
|
||||
|
||||
@ -23,7 +24,7 @@
|
||||
|
||||
{% if user.summary %}
|
||||
<div class="column box has-background-white-bis content">
|
||||
{{ user.summary | to_markdown | safe }}
|
||||
{{ user.summary|to_markdown|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -15,7 +15,7 @@
|
||||
{% if is_self %}
|
||||
<div class="column is-narrow">
|
||||
{% trans "Create list" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with controls_text="create-list" icon="plus" text=button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with controls_text="create-list" icon_with_text="plus" text=button_text %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends 'user/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block tabs %}
|
||||
{% with user|username as username %}
|
||||
|
@ -1,5 +1,6 @@
|
||||
{% extends 'user/layout.html' %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
{% load humanize %}
|
||||
{% load i18n %}
|
||||
|
||||
@ -36,7 +37,6 @@
|
||||
<div class="column is-narrow">
|
||||
{% trans "Create shelf" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="plus" controls_text="create-shelf-form" focus="create-shelf-form-header" %}
|
||||
|
||||
<a class="button" href="{% url 'import' %}">{% trans "Import Books" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -60,7 +60,7 @@
|
||||
{% if is_self and shelf.id %}
|
||||
<div class="column is-narrow">
|
||||
{% trans "Edit shelf" as button_text %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon="pencil" controls_text="edit-shelf-form" focus="edit-shelf-form-header" %}
|
||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="pencil" controls_text="edit-shelf-form" focus="edit-shelf-form-header" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -81,7 +81,9 @@
|
||||
<th>{% trans "Shelved" %}</th>
|
||||
<th>{% trans "Started" %}</th>
|
||||
<th>{% trans "Finished" %}</th>
|
||||
{% if ratings %}<th>{% trans "Rating" %}</th>{% endif %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<th>{% trans "Rating" %}</th>
|
||||
{% endif %}
|
||||
{% if shelf.user == request.user %}
|
||||
<th aria-hidden="true"></th>
|
||||
{% endif %}
|
||||
@ -101,18 +103,18 @@
|
||||
{% include 'snippets/authors.html' %}
|
||||
</td>
|
||||
<td data-title="{% trans "Shelved" %}">
|
||||
{{ book.created_date | naturalday }}
|
||||
{{ book.created_date|naturalday }}
|
||||
</td>
|
||||
{% latest_read_through book user as read_through %}
|
||||
<td data-title="{% trans "Started" %}">
|
||||
{{ read_through.start_date | naturalday |default_if_none:""}}
|
||||
{{ read_through.start_date|naturalday|default_if_none:""}}
|
||||
</td>
|
||||
<td data-title="{% trans "Finished" %}">
|
||||
{{ read_through.finish_date | naturalday |default_if_none:""}}
|
||||
{{ read_through.finish_date|naturalday|default_if_none:""}}
|
||||
</td>
|
||||
{% if ratings %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<td data-title="{% trans "Rating" %}">
|
||||
{% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %}
|
||||
{% include 'snippets/stars.html' with rating=book.rating %}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% if shelf.user == request.user %}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends 'user/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block title %}{{ user.display_name }}{% endblock %}
|
||||
|
||||
@ -12,9 +12,8 @@
|
||||
{% if is_self %}
|
||||
<div class="column is-narrow">
|
||||
<a href="{% url 'prefs-profile' %}">
|
||||
<span class="icon icon-pencil" title="Edit profile">
|
||||
<span class="is-sr-only">{% trans "Edit profile" %}</span>
|
||||
</span>
|
||||
<span class="icon icon-pencil" title="Edit profile" aria-hidden="true"></span>
|
||||
<span>{% trans "Edit profile" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -59,8 +58,9 @@
|
||||
<div class="columns is-mobile">
|
||||
<h2 class="title column">{% trans "User Activity" %}</h2>
|
||||
<div class="column is-narrow">
|
||||
<a class="icon icon-rss" target="_blank" href="{{ user.local_path }}/rss">
|
||||
<span class="is-sr-only">{% trans "RSS feed" %}</span>
|
||||
<a target="_blank" href="{{ user.local_path }}/rss">
|
||||
<span class="icon icon-rss" aria-hidden="true"></span>
|
||||
<span>{% trans "RSS feed" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{% load utilities %}
|
||||
{% load bookwyrm_tags %}
|
||||
|
||||
<div class="media block">
|
||||
|
@ -1,7 +1,5 @@
|
||||
{% extends 'settings/admin_layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}{{ user.username }}{% endblock %}
|
||||
{% block header %}{{ user.username }}{% endblock %}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% load i18n %}
|
||||
{% load bookwyrm_tags %}
|
||||
{% load markdown %}
|
||||
<div class="block columns">
|
||||
<div class="column is-flex is-flex-direction-column">
|
||||
<h4 class="title is-4">{% trans "User details" %}</h4>
|
||||
@ -7,7 +7,7 @@
|
||||
{% include 'user/user_preview.html' with user=user %}
|
||||
{% if user.summary %}
|
||||
<div class="box content has-background-white-ter is-shadowless">
|
||||
{{ user.summary | to_markdown | safe }}
|
||||
{{ user.summary|to_markdown|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -1,22 +1,15 @@
|
||||
""" template filters """
|
||||
from uuid import uuid4
|
||||
|
||||
from django import template, utils
|
||||
from django import template
|
||||
from django.db.models import Avg
|
||||
|
||||
from bookwyrm import models, views
|
||||
from bookwyrm.views.status import to_markdown
|
||||
from bookwyrm.templatetags.utilities import get_user_identifier
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name="dict_key")
|
||||
def dict_key(d, k):
|
||||
"""Returns the given key from a dictionary."""
|
||||
return d.get(k) or 0
|
||||
|
||||
|
||||
@register.filter(name="rating")
|
||||
def get_rating(book, user):
|
||||
"""get the overall rating of a book"""
|
||||
@ -43,119 +36,12 @@ def get_user_rating(book, user):
|
||||
return 0
|
||||
|
||||
|
||||
@register.filter(name="username")
|
||||
def get_user_identifier(user):
|
||||
"""use localname for local users, username for remote"""
|
||||
return user.localname if user.localname else user.username
|
||||
|
||||
|
||||
@register.filter(name="notification_count")
|
||||
def get_notification_count(user):
|
||||
"""how many UNREAD notifications are there"""
|
||||
return user.notification_set.filter(read=False).count()
|
||||
|
||||
|
||||
@register.filter(name="replies")
|
||||
def get_replies(status):
|
||||
"""get all direct replies to a status"""
|
||||
# TODO: this limit could cause problems
|
||||
return models.Status.objects.filter(
|
||||
reply_parent=status,
|
||||
deleted=False,
|
||||
).select_subclasses()[:10]
|
||||
|
||||
|
||||
@register.filter(name="parent")
|
||||
def get_parent(status):
|
||||
"""get the reply parent for a status"""
|
||||
return (
|
||||
models.Status.objects.filter(id=status.reply_parent_id)
|
||||
.select_subclasses()
|
||||
.get()
|
||||
)
|
||||
|
||||
|
||||
@register.filter(name="liked")
|
||||
def get_user_liked(user, status):
|
||||
"""did the given user fav a status?"""
|
||||
try:
|
||||
models.Favorite.objects.get(user=user, status=status)
|
||||
return True
|
||||
except models.Favorite.DoesNotExist:
|
||||
return False
|
||||
|
||||
|
||||
@register.filter(name="boosted")
|
||||
def get_user_boosted(user, status):
|
||||
"""did the given user fav a status?"""
|
||||
return user.id in status.boosters.all().values_list("user", flat=True)
|
||||
|
||||
|
||||
@register.filter(name="follow_request_exists")
|
||||
def follow_request_exists(user, requester):
|
||||
"""see if there is a pending follow request for a user"""
|
||||
try:
|
||||
models.UserFollowRequest.objects.filter(
|
||||
user_subject=requester,
|
||||
user_object=user,
|
||||
).get()
|
||||
return True
|
||||
except models.UserFollowRequest.DoesNotExist:
|
||||
return False
|
||||
|
||||
|
||||
@register.filter(name="boosted_status")
|
||||
def get_boosted(boost):
|
||||
"""load a boosted status. have to do this or it wont get foregin keys"""
|
||||
return (
|
||||
models.Status.objects.select_subclasses()
|
||||
.filter(id=boost.boosted_status.id)
|
||||
.get()
|
||||
)
|
||||
|
||||
|
||||
@register.filter(name="book_description")
|
||||
def get_book_description(book):
|
||||
"""use the work's text if the book doesn't have it"""
|
||||
return book.description or book.parent_work.description
|
||||
|
||||
|
||||
@register.filter(name="uuid")
|
||||
def get_uuid(identifier):
|
||||
"""for avoiding clashing ids when there are many forms"""
|
||||
return "%s%s" % (identifier, uuid4())
|
||||
|
||||
|
||||
@register.filter(name="to_markdown")
|
||||
def get_markdown(content):
|
||||
"""convert markdown to html"""
|
||||
if content:
|
||||
return to_markdown(content)
|
||||
return None
|
||||
|
||||
|
||||
@register.filter(name="mentions")
|
||||
def get_mentions(status, user):
|
||||
"""people to @ in a reply: the parent and all mentions"""
|
||||
mentions = set([status.user] + list(status.mention_users.all()))
|
||||
return (
|
||||
" ".join("@" + get_user_identifier(m) for m in mentions if not m == user) + " "
|
||||
)
|
||||
|
||||
|
||||
@register.filter(name="status_preview_name")
|
||||
def get_status_preview_name(obj):
|
||||
"""text snippet with book context for a status"""
|
||||
name = obj.__class__.__name__.lower()
|
||||
if name == "review":
|
||||
return "%s of <em>%s</em>" % (name, obj.book.title)
|
||||
if name == "comment":
|
||||
return "%s on <em>%s</em>" % (name, obj.book.title)
|
||||
if name == "quotation":
|
||||
return "%s from <em>%s</em>" % (name, obj.book.title)
|
||||
return name
|
||||
|
||||
|
||||
@register.filter(name="next_shelf")
|
||||
def get_next_shelf(current_shelf):
|
||||
"""shelf you'd use to update reading progress"""
|
||||
@ -168,17 +54,6 @@ def get_next_shelf(current_shelf):
|
||||
return "to-read"
|
||||
|
||||
|
||||
@register.filter(name="title")
|
||||
def get_title(book):
|
||||
"""display the subtitle if the title is short"""
|
||||
if not book:
|
||||
return ""
|
||||
title = book.title
|
||||
if len(title) < 6 and book.subtitle:
|
||||
title = "{:s}: {:s}".format(title, book.subtitle)
|
||||
return title
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def related_status(notification):
|
||||
"""for notifications"""
|
||||
@ -212,31 +87,6 @@ def latest_read_through(book, user):
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def active_read_through(book, user):
|
||||
"""the most recent read activity"""
|
||||
return (
|
||||
models.ReadThrough.objects.filter(
|
||||
user=user, book=book, finish_date__isnull=True
|
||||
)
|
||||
.order_by("-start_date")
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def comparison_bool(str1, str2):
|
||||
"""idk why I need to write a tag for this, it reutrns a bool"""
|
||||
return str1 == str2
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def get_lang():
|
||||
"""get current language, strip to the first two letters"""
|
||||
language = utils.translation.get_language()
|
||||
return language[0 : language.find("-")]
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def mutuals_count(context, user):
|
||||
"""how many users that you follow, follow them"""
|
||||
|
22
bookwyrm/templatetags/interaction.py
Normal file
22
bookwyrm/templatetags/interaction.py
Normal file
@ -0,0 +1,22 @@
|
||||
""" template filters for status interaction buttons """
|
||||
from django import template
|
||||
from bookwyrm import models
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name="liked")
|
||||
def get_user_liked(user, status):
|
||||
"""did the given user fav a status?"""
|
||||
try:
|
||||
models.Favorite.objects.get(user=user, status=status)
|
||||
return True
|
||||
except models.Favorite.DoesNotExist:
|
||||
return False
|
||||
|
||||
|
||||
@register.filter(name="boosted")
|
||||
def get_user_boosted(user, status):
|
||||
"""did the given user fav a status?"""
|
||||
return user.id in status.boosters.all().values_list("user", flat=True)
|
12
bookwyrm/templatetags/layout.py
Normal file
12
bookwyrm/templatetags/layout.py
Normal file
@ -0,0 +1,12 @@
|
||||
""" template filters used for creating the layout"""
|
||||
from django import template, utils
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def get_lang():
|
||||
"""get current language, strip to the first two letters"""
|
||||
language = utils.translation.get_language()
|
||||
return language[0 : language.find("-")]
|
14
bookwyrm/templatetags/markdown.py
Normal file
14
bookwyrm/templatetags/markdown.py
Normal file
@ -0,0 +1,14 @@
|
||||
""" template filters """
|
||||
from django import template
|
||||
from bookwyrm.views.status import to_markdown
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name="to_markdown")
|
||||
def get_markdown(content):
|
||||
"""convert markdown to html"""
|
||||
if content:
|
||||
return to_markdown(content)
|
||||
return None
|
59
bookwyrm/templatetags/status_display.py
Normal file
59
bookwyrm/templatetags/status_display.py
Normal file
@ -0,0 +1,59 @@
|
||||
""" template filters """
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django import template
|
||||
from django.contrib.humanize.templatetags.humanize import naturaltime, naturalday
|
||||
from django.utils import timezone
|
||||
from bookwyrm import models
|
||||
from bookwyrm.templatetags.utilities import get_user_identifier
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name="mentions")
|
||||
def get_mentions(status, user):
|
||||
"""people to @ in a reply: the parent and all mentions"""
|
||||
mentions = set([status.user] + list(status.mention_users.all()))
|
||||
return (
|
||||
" ".join("@" + get_user_identifier(m) for m in mentions if not m == user) + " "
|
||||
)
|
||||
|
||||
|
||||
@register.filter(name="replies")
|
||||
def get_replies(status):
|
||||
"""get all direct replies to a status"""
|
||||
# TODO: this limit could cause problems
|
||||
return models.Status.objects.filter(
|
||||
reply_parent=status,
|
||||
deleted=False,
|
||||
).select_subclasses()[:10]
|
||||
|
||||
|
||||
@register.filter(name="parent")
|
||||
def get_parent(status):
|
||||
"""get the reply parent for a status"""
|
||||
return (
|
||||
models.Status.objects.filter(id=status.reply_parent_id)
|
||||
.select_subclasses()
|
||||
.get()
|
||||
)
|
||||
|
||||
|
||||
@register.filter(name="boosted_status")
|
||||
def get_boosted(boost):
|
||||
"""load a boosted status. have to do this or it won't get foreign keys"""
|
||||
return models.Status.objects.select_subclasses().get(id=boost.boosted_status.id)
|
||||
|
||||
|
||||
@register.filter(name="published_date")
|
||||
def get_published_date(date):
|
||||
"""less verbose combo of humanize filters"""
|
||||
if not date:
|
||||
return ""
|
||||
now = timezone.now()
|
||||
delta = relativedelta(now, date)
|
||||
if delta.years:
|
||||
return naturalday(date)
|
||||
if delta.days:
|
||||
return naturalday(date, "M j")
|
||||
return naturaltime(date)
|
35
bookwyrm/templatetags/utilities.py
Normal file
35
bookwyrm/templatetags/utilities.py
Normal file
@ -0,0 +1,35 @@
|
||||
""" template filters for really common utilities """
|
||||
from uuid import uuid4
|
||||
from django import template
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name="uuid")
|
||||
def get_uuid(identifier):
|
||||
"""for avoiding clashing ids when there are many forms"""
|
||||
return "%s%s" % (identifier, uuid4())
|
||||
|
||||
|
||||
@register.filter(name="username")
|
||||
def get_user_identifier(user):
|
||||
"""use localname for local users, username for remote"""
|
||||
return user.localname if user.localname else user.username
|
||||
|
||||
|
||||
@register.filter(name="title")
|
||||
def get_title(book):
|
||||
"""display the subtitle if the title is short"""
|
||||
if not book:
|
||||
return ""
|
||||
title = book.title
|
||||
if len(title) < 6 and book.subtitle:
|
||||
title = "{:s}: {:s}".format(title, book.subtitle)
|
||||
return title
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def comparison_bool(str1, str2):
|
||||
"""idk why I need to write a tag for this, it reutrns a bool"""
|
||||
return str1 == str2
|
@ -2,12 +2,17 @@
|
||||
import re
|
||||
from unittest.mock import patch
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.templatetags import bookwyrm_tags
|
||||
from bookwyrm.templatetags import (
|
||||
bookwyrm_tags,
|
||||
interaction,
|
||||
markdown,
|
||||
status_display,
|
||||
utilities,
|
||||
)
|
||||
|
||||
|
||||
@patch("bookwyrm.activitystreams.ActivityStream.add_status")
|
||||
@ -33,12 +38,6 @@ class TemplateTags(TestCase):
|
||||
)
|
||||
self.book = models.Edition.objects.create(title="Test Book")
|
||||
|
||||
def test_dict_key(self, _):
|
||||
"""just getting a value out of a dict"""
|
||||
test_dict = {"a": 1, "b": 3}
|
||||
self.assertEqual(bookwyrm_tags.dict_key(test_dict, "a"), 1)
|
||||
self.assertEqual(bookwyrm_tags.dict_key(test_dict, "c"), 0)
|
||||
|
||||
def test_get_user_rating(self, _):
|
||||
"""get a user's most recent rating of a book"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
@ -52,27 +51,14 @@ class TemplateTags(TestCase):
|
||||
def test_get_user_identifer_local(self, _):
|
||||
"""fall back to the simplest uid available"""
|
||||
self.assertNotEqual(self.user.username, self.user.localname)
|
||||
self.assertEqual(bookwyrm_tags.get_user_identifier(self.user), "mouse")
|
||||
self.assertEqual(utilities.get_user_identifier(self.user), "mouse")
|
||||
|
||||
def test_get_user_identifer_remote(self, _):
|
||||
"""for a remote user, should be their full username"""
|
||||
self.assertEqual(
|
||||
bookwyrm_tags.get_user_identifier(self.remote_user), "rat@example.com"
|
||||
utilities.get_user_identifier(self.remote_user), "rat@example.com"
|
||||
)
|
||||
|
||||
def test_get_notification_count(self, _):
|
||||
"""just countin'"""
|
||||
self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 0)
|
||||
|
||||
models.Notification.objects.create(user=self.user, notification_type="FAVORITE")
|
||||
models.Notification.objects.create(user=self.user, notification_type="MENTION")
|
||||
|
||||
models.Notification.objects.create(
|
||||
user=self.remote_user, notification_type="FOLLOW"
|
||||
)
|
||||
|
||||
self.assertEqual(bookwyrm_tags.get_notification_count(self.user), 2)
|
||||
|
||||
def test_get_replies(self, _):
|
||||
"""direct replies to a status"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
@ -95,7 +81,7 @@ class TemplateTags(TestCase):
|
||||
deleted_date=timezone.now(),
|
||||
)
|
||||
|
||||
replies = bookwyrm_tags.get_replies(parent)
|
||||
replies = status_display.get_replies(parent)
|
||||
self.assertEqual(len(replies), 2)
|
||||
self.assertTrue(first_child in replies)
|
||||
self.assertTrue(second_child in replies)
|
||||
@ -111,7 +97,7 @@ class TemplateTags(TestCase):
|
||||
reply_parent=parent, user=self.user, content="hi"
|
||||
)
|
||||
|
||||
result = bookwyrm_tags.get_parent(child)
|
||||
result = status_display.get_parent(child)
|
||||
self.assertEqual(result, parent)
|
||||
self.assertIsInstance(result, models.Review)
|
||||
|
||||
@ -119,44 +105,26 @@ class TemplateTags(TestCase):
|
||||
"""did a user like a status"""
|
||||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||
|
||||
self.assertFalse(bookwyrm_tags.get_user_liked(self.user, status))
|
||||
self.assertFalse(interaction.get_user_liked(self.user, status))
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.Favorite.objects.create(user=self.user, status=status)
|
||||
self.assertTrue(bookwyrm_tags.get_user_liked(self.user, status))
|
||||
self.assertTrue(interaction.get_user_liked(self.user, status))
|
||||
|
||||
def test_get_user_boosted(self, _):
|
||||
"""did a user boost a status"""
|
||||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||
|
||||
self.assertFalse(bookwyrm_tags.get_user_boosted(self.user, status))
|
||||
self.assertFalse(interaction.get_user_boosted(self.user, status))
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.Boost.objects.create(user=self.user, boosted_status=status)
|
||||
self.assertTrue(bookwyrm_tags.get_user_boosted(self.user, status))
|
||||
|
||||
def test_follow_request_exists(self, _):
|
||||
"""does a user want to follow"""
|
||||
self.assertFalse(
|
||||
bookwyrm_tags.follow_request_exists(self.user, self.remote_user)
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.UserFollowRequest.objects.create(
|
||||
user_subject=self.user, user_object=self.remote_user
|
||||
)
|
||||
|
||||
self.assertFalse(
|
||||
bookwyrm_tags.follow_request_exists(self.user, self.remote_user)
|
||||
)
|
||||
self.assertTrue(
|
||||
bookwyrm_tags.follow_request_exists(self.remote_user, self.user)
|
||||
)
|
||||
self.assertTrue(interaction.get_user_boosted(self.user, status))
|
||||
|
||||
def test_get_boosted(self, _):
|
||||
"""load a boosted status"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
status = models.Review.objects.create(user=self.remote_user, book=self.book)
|
||||
boost = models.Boost.objects.create(user=self.user, boosted_status=status)
|
||||
boosted = bookwyrm_tags.get_boosted(boost)
|
||||
boosted = status_display.get_boosted(boost)
|
||||
self.assertIsInstance(boosted, models.Review)
|
||||
self.assertEqual(boosted, status)
|
||||
|
||||
@ -178,48 +146,23 @@ class TemplateTags(TestCase):
|
||||
|
||||
def test_get_uuid(self, _):
|
||||
"""uuid functionality"""
|
||||
uuid = bookwyrm_tags.get_uuid("hi")
|
||||
uuid = utilities.get_uuid("hi")
|
||||
self.assertTrue(re.match(r"hi[A-Za-z0-9\-]", uuid))
|
||||
|
||||
def test_get_markdown(self, _):
|
||||
"""mardown format data"""
|
||||
result = bookwyrm_tags.get_markdown("_hi_")
|
||||
result = markdown.get_markdown("_hi_")
|
||||
self.assertEqual(result, "<p><em>hi</em></p>")
|
||||
|
||||
result = bookwyrm_tags.get_markdown("<marquee>_hi_</marquee>")
|
||||
result = markdown.get_markdown("<marquee>_hi_</marquee>")
|
||||
self.assertEqual(result, "<p><em>hi</em></p>")
|
||||
|
||||
def test_get_mentions(self, _):
|
||||
"""list of people mentioned"""
|
||||
status = models.Status.objects.create(content="hi", user=self.remote_user)
|
||||
result = bookwyrm_tags.get_mentions(status, self.user)
|
||||
result = status_display.get_mentions(status, self.user)
|
||||
self.assertEqual(result, "@rat@example.com ")
|
||||
|
||||
def test_get_status_preview_name(self, _):
|
||||
"""status context string"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
status = models.Status.objects.create(content="hi", user=self.user)
|
||||
result = bookwyrm_tags.get_status_preview_name(status)
|
||||
self.assertEqual(result, "status")
|
||||
|
||||
status = models.Review.objects.create(
|
||||
content="hi", user=self.user, book=self.book
|
||||
)
|
||||
result = bookwyrm_tags.get_status_preview_name(status)
|
||||
self.assertEqual(result, "review of <em>Test Book</em>")
|
||||
|
||||
status = models.Comment.objects.create(
|
||||
content="hi", user=self.user, book=self.book
|
||||
)
|
||||
result = bookwyrm_tags.get_status_preview_name(status)
|
||||
self.assertEqual(result, "comment on <em>Test Book</em>")
|
||||
|
||||
status = models.Quotation.objects.create(
|
||||
content="hi", user=self.user, book=self.book
|
||||
)
|
||||
result = bookwyrm_tags.get_status_preview_name(status)
|
||||
self.assertEqual(result, "quotation from <em>Test Book</em>")
|
||||
|
||||
def test_related_status(self, _):
|
||||
"""gets the subclass model for a notification status"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
|
@ -122,6 +122,14 @@ class ListViews(TestCase):
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
@ -130,6 +138,81 @@ class ListViews(TestCase):
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_sorted(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
for (i, book) in enumerate([self.book, self.book_two, self.book_three]):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=book,
|
||||
approved=True,
|
||||
order=i + 1,
|
||||
)
|
||||
|
||||
request = self.factory.get("/?sort_by=order")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get("/?sort_by=title")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get("/?sort_by=rating")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get("/?sort_by=sdkfh")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_empty(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_logged_out(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = False
|
||||
@ -138,12 +221,32 @@ class ListViews(TestCase):
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_json_view(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
is_api.return_value = True
|
||||
result = view(request, self.list.id)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
def test_list_page_json_view_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.List.as_view()
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
request = self.factory.get("/?page=1")
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.views.list.is_api_request") as is_api:
|
||||
@ -204,466 +307,34 @@ class ListViews(TestCase):
|
||||
result = view(request, self.list.id)
|
||||
self.assertEqual(result.status_code, 302)
|
||||
|
||||
def test_curate_approve(self):
|
||||
"""approve a pending item"""
|
||||
view = views.Curate.as_view()
|
||||
def test_user_lists_page(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.UserLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
pending = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=False,
|
||||
order=1,
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
)
|
||||
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": pending.id,
|
||||
"approved": "true",
|
||||
},
|
||||
)
|
||||
request = self.factory.get("")
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
view(request, self.list.id)
|
||||
result = view(request, self.local_user.localname)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
self.assertEqual(mock.call_count, 2)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
pending.refresh_from_db()
|
||||
self.assertEqual(self.list.books.count(), 1)
|
||||
self.assertEqual(self.list.listitem_set.first(), pending)
|
||||
self.assertTrue(pending.approved)
|
||||
|
||||
def test_curate_reject(self):
|
||||
"""approve a pending item"""
|
||||
view = views.Curate.as_view()
|
||||
def test_user_lists_page_logged_out(self):
|
||||
"""there are so many views, this just makes sure it LOADS"""
|
||||
view = views.UserLists.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
pending = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=False,
|
||||
order=1,
|
||||
models.List.objects.create(name="Public list", user=self.local_user)
|
||||
models.List.objects.create(
|
||||
name="Private list", privacy="direct", user=self.local_user
|
||||
)
|
||||
request = self.factory.get("")
|
||||
request.user = self.anonymous_user
|
||||
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": pending.id,
|
||||
"approved": "false",
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
view(request, self.list.id)
|
||||
|
||||
self.assertFalse(self.list.books.exists())
|
||||
self.assertFalse(models.ListItem.objects.exists())
|
||||
|
||||
def test_add_book(self):
|
||||
"""put a book on a list"""
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
views.list.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
item = self.list.listitem_set.get()
|
||||
self.assertEqual(item.book, self.book)
|
||||
self.assertEqual(item.user, self.local_user)
|
||||
self.assertTrue(item.approved)
|
||||
|
||||
def test_add_two_books(self):
|
||||
"""
|
||||
Putting two books on the list. The first should have an order value of
|
||||
1 and the second should have an order value of 2.
|
||||
"""
|
||||
request_one = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_one.user = self.local_user
|
||||
|
||||
request_two = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_two.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_two.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[1].book, self.book_two)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
|
||||
def test_add_three_books_and_remove_second(self):
|
||||
"""
|
||||
Put three books on a list and then remove the one in the middle. The
|
||||
ordering of the list should adjust to not have a gap.
|
||||
"""
|
||||
request_one = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_one.user = self.local_user
|
||||
|
||||
request_two = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_two.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_two.user = self.local_user
|
||||
|
||||
request_three = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_three.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_three.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
views.list.add_book(request_three)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[1].book, self.book_two)
|
||||
self.assertEqual(items[2].book, self.book_three)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
self.assertEqual(items[2].order, 3)
|
||||
|
||||
remove_request = self.factory.post("", {"item": items[1].id})
|
||||
remove_request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.remove_book(remove_request, self.list.id)
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[1].book, self.book_three)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
|
||||
def test_adding_book_with_a_pending_book(self):
|
||||
"""
|
||||
When a list contains any pending books, the pending books should have
|
||||
be at the end of the list by order. If a book is added while a book is
|
||||
pending, its order should precede the pending books.
|
||||
"""
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_three.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.rat,
|
||||
book=self.book_two,
|
||||
approved=False,
|
||||
order=2,
|
||||
)
|
||||
views.list.add_book(request)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertTrue(items[0].approved)
|
||||
|
||||
self.assertEqual(items[1].book, self.book_three)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
self.assertTrue(items[1].approved)
|
||||
|
||||
self.assertEqual(items[2].book, self.book_two)
|
||||
self.assertEqual(items[2].order, 3)
|
||||
self.assertFalse(items[2].approved)
|
||||
|
||||
def test_approving_one_pending_book_from_multiple(self):
|
||||
"""
|
||||
When a list contains any pending books, the pending books should have
|
||||
be at the end of the list by order. If a pending book is approved, then
|
||||
its order should be at the end of the approved books and before the
|
||||
remaining pending books.
|
||||
"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book_two,
|
||||
approved=True,
|
||||
order=2,
|
||||
)
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.rat,
|
||||
book=self.book_three,
|
||||
approved=False,
|
||||
order=3,
|
||||
)
|
||||
to_be_approved = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.rat,
|
||||
book=self.book_four,
|
||||
approved=False,
|
||||
order=4,
|
||||
)
|
||||
|
||||
view = views.Curate.as_view()
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": to_be_approved.id,
|
||||
"approved": "true",
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
view(request, self.list.id)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertTrue(items[0].approved)
|
||||
|
||||
self.assertEqual(items[1].book, self.book_two)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
self.assertTrue(items[1].approved)
|
||||
|
||||
self.assertEqual(items[2].book, self.book_four)
|
||||
self.assertEqual(items[2].order, 3)
|
||||
self.assertTrue(items[2].approved)
|
||||
|
||||
self.assertEqual(items[3].book, self.book_three)
|
||||
self.assertEqual(items[3].order, 4)
|
||||
self.assertFalse(items[3].approved)
|
||||
|
||||
def test_add_three_books_and_move_last_to_first(self):
|
||||
"""
|
||||
Put three books on the list and move the last book to the first
|
||||
position.
|
||||
"""
|
||||
request_one = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_one.user = self.local_user
|
||||
|
||||
request_two = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_two.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_two.user = self.local_user
|
||||
|
||||
request_three = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_three.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_three.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
views.list.add_book(request_three)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[1].book, self.book_two)
|
||||
self.assertEqual(items[2].book, self.book_three)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
self.assertEqual(items[2].order, 3)
|
||||
|
||||
set_position_request = self.factory.post("", {"position": 1})
|
||||
set_position_request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.set_book_position(set_position_request, items[2].id)
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book_three)
|
||||
self.assertEqual(items[1].book, self.book)
|
||||
self.assertEqual(items[2].book, self.book_two)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
self.assertEqual(items[2].order, 3)
|
||||
|
||||
def test_add_book_outsider(self):
|
||||
"""put a book on a list"""
|
||||
self.list.curation = "open"
|
||||
self.list.save(broadcast=False)
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request.user = self.rat
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
views.list.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.rat.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
item = self.list.listitem_set.get()
|
||||
self.assertEqual(item.book, self.book)
|
||||
self.assertEqual(item.user, self.rat)
|
||||
self.assertTrue(item.approved)
|
||||
|
||||
def test_add_book_pending(self):
|
||||
"""put a book on a list awaiting approval"""
|
||||
self.list.curation = "curated"
|
||||
self.list.save(broadcast=False)
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request.user = self.rat
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
views.list.add_book(request)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.rat.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
item = self.list.listitem_set.get()
|
||||
self.assertEqual(activity["object"]["id"], item.remote_id)
|
||||
|
||||
self.assertEqual(item.book, self.book)
|
||||
self.assertEqual(item.user, self.rat)
|
||||
self.assertFalse(item.approved)
|
||||
|
||||
def test_add_book_self_curated(self):
|
||||
"""put a book on a list automatically approved"""
|
||||
self.list.curation = "curated"
|
||||
self.list.save(broadcast=False)
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
views.list.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
item = self.list.listitem_set.get()
|
||||
self.assertEqual(item.book, self.book)
|
||||
self.assertEqual(item.user, self.local_user)
|
||||
self.assertTrue(item.approved)
|
||||
|
||||
def test_remove_book(self):
|
||||
"""take an item off a list"""
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
item = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
order=1,
|
||||
)
|
||||
self.assertTrue(self.list.listitem_set.exists())
|
||||
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": item.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.remove_book(request, self.list.id)
|
||||
self.assertFalse(self.list.listitem_set.exists())
|
||||
|
||||
def test_remove_book_unauthorized(self):
|
||||
"""take an item off a list"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
item = models.ListItem.objects.create(
|
||||
book_list=self.list, user=self.local_user, book=self.book, order=1
|
||||
)
|
||||
self.assertTrue(self.list.listitem_set.exists())
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": item.id,
|
||||
},
|
||||
)
|
||||
request.user = self.rat
|
||||
|
||||
views.list.remove_book(request, self.list.id)
|
||||
self.assertTrue(self.list.listitem_set.exists())
|
||||
result = view(request, self.local_user.username)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
result.render()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
529
bookwyrm/tests/views/test_list_actions.py
Normal file
529
bookwyrm/tests/views/test_list_actions.py
Normal file
@ -0,0 +1,529 @@
|
||||
""" test for app action functionality """
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import models, views
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
class ListActionViews(TestCase):
|
||||
"""tag views"""
|
||||
|
||||
def setUp(self):
|
||||
"""we need basic test data and mocks"""
|
||||
self.factory = RequestFactory()
|
||||
self.local_user = models.User.objects.create_user(
|
||||
"mouse@local.com",
|
||||
"mouse@mouse.com",
|
||||
"mouseword",
|
||||
local=True,
|
||||
localname="mouse",
|
||||
remote_id="https://example.com/users/mouse",
|
||||
)
|
||||
self.rat = models.User.objects.create_user(
|
||||
"rat@local.com",
|
||||
"rat@rat.com",
|
||||
"ratword",
|
||||
local=True,
|
||||
localname="rat",
|
||||
remote_id="https://example.com/users/rat",
|
||||
)
|
||||
work = models.Work.objects.create(title="Work")
|
||||
self.book = models.Edition.objects.create(
|
||||
title="Example Edition",
|
||||
remote_id="https://example.com/book/1",
|
||||
parent_work=work,
|
||||
)
|
||||
work_two = models.Work.objects.create(title="Labori")
|
||||
self.book_two = models.Edition.objects.create(
|
||||
title="Example Edition 2",
|
||||
remote_id="https://example.com/book/2",
|
||||
parent_work=work_two,
|
||||
)
|
||||
work_three = models.Work.objects.create(title="Trabajar")
|
||||
self.book_three = models.Edition.objects.create(
|
||||
title="Example Edition 3",
|
||||
remote_id="https://example.com/book/3",
|
||||
parent_work=work_three,
|
||||
)
|
||||
work_four = models.Work.objects.create(title="Travailler")
|
||||
self.book_four = models.Edition.objects.create(
|
||||
title="Example Edition 4",
|
||||
remote_id="https://example.com/book/4",
|
||||
parent_work=work_four,
|
||||
)
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
self.list = models.List.objects.create(
|
||||
name="Test List", user=self.local_user
|
||||
)
|
||||
self.anonymous_user = AnonymousUser
|
||||
self.anonymous_user.is_authenticated = False
|
||||
models.SiteSettings.objects.create()
|
||||
|
||||
def test_curate_approve(self):
|
||||
"""approve a pending item"""
|
||||
view = views.Curate.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
pending = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=False,
|
||||
order=1,
|
||||
)
|
||||
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": pending.id,
|
||||
"approved": "true",
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
view(request, self.list.id)
|
||||
|
||||
self.assertEqual(mock.call_count, 2)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
pending.refresh_from_db()
|
||||
self.assertEqual(self.list.books.count(), 1)
|
||||
self.assertEqual(self.list.listitem_set.first(), pending)
|
||||
self.assertTrue(pending.approved)
|
||||
|
||||
def test_curate_reject(self):
|
||||
"""approve a pending item"""
|
||||
view = views.Curate.as_view()
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
pending = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=False,
|
||||
order=1,
|
||||
)
|
||||
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": pending.id,
|
||||
"approved": "false",
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
view(request, self.list.id)
|
||||
|
||||
self.assertFalse(self.list.books.exists())
|
||||
self.assertFalse(models.ListItem.objects.exists())
|
||||
|
||||
def test_add_book(self):
|
||||
"""put a book on a list"""
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
views.list.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
item = self.list.listitem_set.get()
|
||||
self.assertEqual(item.book, self.book)
|
||||
self.assertEqual(item.user, self.local_user)
|
||||
self.assertTrue(item.approved)
|
||||
|
||||
def test_add_two_books(self):
|
||||
"""
|
||||
Putting two books on the list. The first should have an order value of
|
||||
1 and the second should have an order value of 2.
|
||||
"""
|
||||
request_one = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_one.user = self.local_user
|
||||
|
||||
request_two = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_two.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_two.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[1].book, self.book_two)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
|
||||
def test_add_three_books_and_remove_second(self):
|
||||
"""
|
||||
Put three books on a list and then remove the one in the middle. The
|
||||
ordering of the list should adjust to not have a gap.
|
||||
"""
|
||||
request_one = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_one.user = self.local_user
|
||||
|
||||
request_two = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_two.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_two.user = self.local_user
|
||||
|
||||
request_three = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_three.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_three.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
views.list.add_book(request_three)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[1].book, self.book_two)
|
||||
self.assertEqual(items[2].book, self.book_three)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
self.assertEqual(items[2].order, 3)
|
||||
|
||||
remove_request = self.factory.post("", {"item": items[1].id})
|
||||
remove_request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.remove_book(remove_request, self.list.id)
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[1].book, self.book_three)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
|
||||
def test_adding_book_with_a_pending_book(self):
|
||||
"""
|
||||
When a list contains any pending books, the pending books should have
|
||||
be at the end of the list by order. If a book is added while a book is
|
||||
pending, its order should precede the pending books.
|
||||
"""
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_three.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.rat,
|
||||
book=self.book_two,
|
||||
approved=False,
|
||||
order=2,
|
||||
)
|
||||
views.list.add_book(request)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertTrue(items[0].approved)
|
||||
|
||||
self.assertEqual(items[1].book, self.book_three)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
self.assertTrue(items[1].approved)
|
||||
|
||||
self.assertEqual(items[2].book, self.book_two)
|
||||
self.assertEqual(items[2].order, 3)
|
||||
self.assertFalse(items[2].approved)
|
||||
|
||||
def test_approving_one_pending_book_from_multiple(self):
|
||||
"""
|
||||
When a list contains any pending books, the pending books should have
|
||||
be at the end of the list by order. If a pending book is approved, then
|
||||
its order should be at the end of the approved books and before the
|
||||
remaining pending books.
|
||||
"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
approved=True,
|
||||
order=1,
|
||||
)
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book_two,
|
||||
approved=True,
|
||||
order=2,
|
||||
)
|
||||
models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.rat,
|
||||
book=self.book_three,
|
||||
approved=False,
|
||||
order=3,
|
||||
)
|
||||
to_be_approved = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.rat,
|
||||
book=self.book_four,
|
||||
approved=False,
|
||||
order=4,
|
||||
)
|
||||
|
||||
view = views.Curate.as_view()
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": to_be_approved.id,
|
||||
"approved": "true",
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
view(request, self.list.id)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertTrue(items[0].approved)
|
||||
|
||||
self.assertEqual(items[1].book, self.book_two)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
self.assertTrue(items[1].approved)
|
||||
|
||||
self.assertEqual(items[2].book, self.book_four)
|
||||
self.assertEqual(items[2].order, 3)
|
||||
self.assertTrue(items[2].approved)
|
||||
|
||||
self.assertEqual(items[3].book, self.book_three)
|
||||
self.assertEqual(items[3].order, 4)
|
||||
self.assertFalse(items[3].approved)
|
||||
|
||||
def test_add_three_books_and_move_last_to_first(self):
|
||||
"""
|
||||
Put three books on the list and move the last book to the first
|
||||
position.
|
||||
"""
|
||||
request_one = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_one.user = self.local_user
|
||||
|
||||
request_two = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_two.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_two.user = self.local_user
|
||||
|
||||
request_three = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book_three.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request_three.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.add_book(request_one)
|
||||
views.list.add_book(request_two)
|
||||
views.list.add_book(request_three)
|
||||
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book)
|
||||
self.assertEqual(items[1].book, self.book_two)
|
||||
self.assertEqual(items[2].book, self.book_three)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
self.assertEqual(items[2].order, 3)
|
||||
|
||||
set_position_request = self.factory.post("", {"position": 1})
|
||||
set_position_request.user = self.local_user
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.set_book_position(set_position_request, items[2].id)
|
||||
items = self.list.listitem_set.order_by("order").all()
|
||||
self.assertEqual(items[0].book, self.book_three)
|
||||
self.assertEqual(items[1].book, self.book)
|
||||
self.assertEqual(items[2].book, self.book_two)
|
||||
self.assertEqual(items[0].order, 1)
|
||||
self.assertEqual(items[1].order, 2)
|
||||
self.assertEqual(items[2].order, 3)
|
||||
|
||||
def test_add_book_outsider(self):
|
||||
"""put a book on a list"""
|
||||
self.list.curation = "open"
|
||||
self.list.save(broadcast=False)
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request.user = self.rat
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
views.list.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.rat.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
item = self.list.listitem_set.get()
|
||||
self.assertEqual(item.book, self.book)
|
||||
self.assertEqual(item.user, self.rat)
|
||||
self.assertTrue(item.approved)
|
||||
|
||||
def test_add_book_pending(self):
|
||||
"""put a book on a list awaiting approval"""
|
||||
self.list.curation = "curated"
|
||||
self.list.save(broadcast=False)
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request.user = self.rat
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
views.list.add_book(request)
|
||||
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.rat.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
item = self.list.listitem_set.get()
|
||||
self.assertEqual(activity["object"]["id"], item.remote_id)
|
||||
|
||||
self.assertEqual(item.book, self.book)
|
||||
self.assertEqual(item.user, self.rat)
|
||||
self.assertFalse(item.approved)
|
||||
|
||||
def test_add_book_self_curated(self):
|
||||
"""put a book on a list automatically approved"""
|
||||
self.list.curation = "curated"
|
||||
self.list.save(broadcast=False)
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"book": self.book.id,
|
||||
"list": self.list.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock:
|
||||
views.list.add_book(request)
|
||||
self.assertEqual(mock.call_count, 1)
|
||||
activity = json.loads(mock.call_args[0][1])
|
||||
self.assertEqual(activity["type"], "Add")
|
||||
self.assertEqual(activity["actor"], self.local_user.remote_id)
|
||||
self.assertEqual(activity["target"], self.list.remote_id)
|
||||
|
||||
item = self.list.listitem_set.get()
|
||||
self.assertEqual(item.book, self.book)
|
||||
self.assertEqual(item.user, self.local_user)
|
||||
self.assertTrue(item.approved)
|
||||
|
||||
def test_remove_book(self):
|
||||
"""take an item off a list"""
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
item = models.ListItem.objects.create(
|
||||
book_list=self.list,
|
||||
user=self.local_user,
|
||||
book=self.book,
|
||||
order=1,
|
||||
)
|
||||
self.assertTrue(self.list.listitem_set.exists())
|
||||
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": item.id,
|
||||
},
|
||||
)
|
||||
request.user = self.local_user
|
||||
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
views.list.remove_book(request, self.list.id)
|
||||
self.assertFalse(self.list.listitem_set.exists())
|
||||
|
||||
def test_remove_book_unauthorized(self):
|
||||
"""take an item off a list"""
|
||||
with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"):
|
||||
item = models.ListItem.objects.create(
|
||||
book_list=self.list, user=self.local_user, book=self.book, order=1
|
||||
)
|
||||
self.assertTrue(self.list.listitem_set.exists())
|
||||
request = self.factory.post(
|
||||
"",
|
||||
{
|
||||
"item": item.id,
|
||||
},
|
||||
)
|
||||
request.user = self.rat
|
||||
|
||||
views.list.remove_book(request, self.list.id)
|
||||
self.assertTrue(self.list.listitem_set.exists())
|
@ -29,7 +29,7 @@ class Author(View):
|
||||
"author": author,
|
||||
"books": [b.default_edition for b in books],
|
||||
}
|
||||
return TemplateResponse(request, "author.html", data)
|
||||
return TemplateResponse(request, "author/author.html", data)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@ -43,7 +43,7 @@ class EditAuthor(View):
|
||||
"""info about a book"""
|
||||
author = get_object_or_404(models.Author, id=author_id)
|
||||
data = {"author": author, "form": forms.AuthorForm(instance=author)}
|
||||
return TemplateResponse(request, "edit_author.html", data)
|
||||
return TemplateResponse(request, "author/edit_author.html", data)
|
||||
|
||||
def post(self, request, author_id):
|
||||
"""edit a author cool"""
|
||||
@ -52,7 +52,7 @@ class EditAuthor(View):
|
||||
form = forms.AuthorForm(request.POST, request.FILES, instance=author)
|
||||
if not form.is_valid():
|
||||
data = {"author": author, "form": form}
|
||||
return TemplateResponse(request, "edit_author.html", data)
|
||||
return TemplateResponse(request, "author/edit_author.html", data)
|
||||
author = form.save()
|
||||
|
||||
return redirect("/author/%s" % author.id)
|
||||
|
@ -10,7 +10,12 @@ from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.importers import Importer, LibrarythingImporter, GoodreadsImporter
|
||||
from bookwyrm.importers import (
|
||||
Importer,
|
||||
LibrarythingImporter,
|
||||
GoodreadsImporter,
|
||||
StorygraphImporter,
|
||||
)
|
||||
from bookwyrm.tasks import app
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@ -42,6 +47,8 @@ class Import(View):
|
||||
importer = None
|
||||
if source == "LibraryThing":
|
||||
importer = LibrarythingImporter()
|
||||
elif source == "Storygraph":
|
||||
importer = StorygraphImporter()
|
||||
else:
|
||||
# Default : GoodReads
|
||||
importer = GoodreadsImporter()
|
||||
|
@ -5,7 +5,7 @@ from urllib.parse import urlencode
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import Avg, Count, Q, Max
|
||||
from django.db.models import Avg, Count, DecimalField, Q, Max
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.http import HttpResponseNotFound, HttpResponseBadRequest, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
@ -108,31 +108,23 @@ class List(View):
|
||||
if direction not in ("ascending", "descending"):
|
||||
direction = "ascending"
|
||||
|
||||
internal_sort_by = {
|
||||
directional_sort_by = {
|
||||
"order": "order",
|
||||
"title": "book__title",
|
||||
"rating": "average_rating",
|
||||
}
|
||||
directional_sort_by = internal_sort_by[sort_by]
|
||||
}[sort_by]
|
||||
if direction == "descending":
|
||||
directional_sort_by = "-" + directional_sort_by
|
||||
|
||||
if sort_by == "order":
|
||||
items = book_list.listitem_set.filter(approved=True).order_by(
|
||||
directional_sort_by
|
||||
)
|
||||
elif sort_by == "title":
|
||||
items = book_list.listitem_set.filter(approved=True).order_by(
|
||||
directional_sort_by
|
||||
)
|
||||
elif sort_by == "rating":
|
||||
items = (
|
||||
book_list.listitem_set.annotate(
|
||||
average_rating=Avg(Coalesce("book__review__rating", 0))
|
||||
items = book_list.listitem_set
|
||||
if sort_by == "rating":
|
||||
items = items.annotate(
|
||||
average_rating=Avg(
|
||||
Coalesce("book__review__rating", 0.0),
|
||||
output_field=DecimalField(),
|
||||
)
|
||||
.filter(approved=True)
|
||||
.order_by(directional_sort_by)
|
||||
)
|
||||
items = items.filter(approved=True).order_by(directional_sort_by)
|
||||
|
||||
paginated = Paginator(items, PAGE_LENGTH)
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
from collections import namedtuple
|
||||
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Count, OuterRef, Subquery, F, Q
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.http import HttpResponseBadRequest, HttpResponseNotFound
|
||||
@ -37,30 +38,41 @@ class Shelf(View):
|
||||
return HttpResponseNotFound()
|
||||
if not shelf.visible_to_user(request.user):
|
||||
return HttpResponseNotFound()
|
||||
books = shelf.books
|
||||
# this is a constructed "all books" view, with a fake "shelf" obj
|
||||
else:
|
||||
FakeShelf = namedtuple(
|
||||
"Shelf", ("identifier", "name", "user", "books", "privacy")
|
||||
)
|
||||
books = models.Edition.objects.filter(
|
||||
# privacy is ensured because the shelves are already filtered above
|
||||
shelfbook__shelf__in=shelves.all()
|
||||
).distinct()
|
||||
shelf = FakeShelf("all", _("All books"), user, books, "public")
|
||||
|
||||
is_self = request.user == user
|
||||
|
||||
if is_api_request(request):
|
||||
return ActivitypubResponse(shelf.to_activity(**request.GET))
|
||||
|
||||
reviews = privacy_filter(
|
||||
request.user,
|
||||
models.Review.objects.filter(
|
||||
user=user,
|
||||
rating__isnull=False,
|
||||
book__id=OuterRef("id"),
|
||||
),
|
||||
).order_by("-published_date")
|
||||
|
||||
books = books.annotate(rating=Subquery(reviews.values("rating")[:1]))
|
||||
|
||||
paginated = Paginator(
|
||||
shelf.books.order_by("-updated_date"),
|
||||
books.order_by("-updated_date"),
|
||||
PAGE_LENGTH,
|
||||
)
|
||||
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
data = {
|
||||
"user": user,
|
||||
"is_self": is_self,
|
||||
"is_self": request.user == user,
|
||||
"shelves": shelves.all(),
|
||||
"shelf": shelf,
|
||||
"books": page,
|
||||
|
Reference in New Issue
Block a user