forked from mirrors/bookwyrm
Refactors import status view
This commit is contained in:
parent
309d289a65
commit
1e8269b6c9
3 changed files with 75 additions and 124 deletions
|
@ -33,7 +33,7 @@ class ImportJob(models.Model):
|
||||||
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
created_date = models.DateTimeField(default=timezone.now)
|
created_date = models.DateTimeField(default=timezone.now)
|
||||||
task_id = models.CharField(max_length=100, null=True)
|
task_id = models.CharField(max_length=100, null=True) # TODO: deprecated
|
||||||
include_reviews = models.BooleanField(default=True)
|
include_reviews = models.BooleanField(default=True)
|
||||||
mappings = models.JSONField()
|
mappings = models.JSONField()
|
||||||
complete = models.BooleanField(default=False)
|
complete = models.BooleanField(default=False)
|
||||||
|
|
|
@ -6,116 +6,42 @@
|
||||||
{% block title %}{% trans "Import Status" %}{% endblock %}
|
{% block title %}{% trans "Import Status" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}{% spaceless %}
|
{% block content %}{% spaceless %}
|
||||||
<div class="block">
|
<header class="block">
|
||||||
<h1 class="title">{% trans "Import Status" %}</h1>
|
<h1 class="title">{% trans "Import Status" %}</h1>
|
||||||
<a href="{% url 'import' %}" class="has-text-weight-normal help subtitle is-link">{% trans "Back to imports" %}</a>
|
<a href="{% url 'import' %}" class="has-text-weight-normal help subtitle is-link">{% trans "Back to imports" %}</a>
|
||||||
|
|
||||||
{% if task.failed %}
|
<div class="block">
|
||||||
<div class="notification is-danger">{% trans "TASK FAILED" %}</div>
|
<dl>
|
||||||
|
<dt class="is-pulled-left mr-5">{% trans "Import started:" %}</dt>
|
||||||
|
<dd>{{ job.created_date | naturaltime }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if not complete %}
|
||||||
|
<div class="box is-processing">
|
||||||
|
<div class="block">
|
||||||
|
<span class="icon icon-spinner is-pulled-left" aria-hidden="true"></span>
|
||||||
|
<span>{% trans "In progress" %}</span>
|
||||||
|
<span class="is-pulled-right">
|
||||||
|
<a href="" class="button is-small">{% trans "Refresh" %}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="is-flex">
|
||||||
|
<progress class="progress is-success is-medium mr-2" value="{{ percent }}" max="100">{{ percent }}%</progress>
|
||||||
|
<span>{{ percent }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</header>
|
||||||
<dl>
|
|
||||||
<dt class="is-pulled-left mr-5">{% trans "Import started:" %}</dt>
|
|
||||||
<dd>{{ job.created_date | naturaltime }}</dd>
|
|
||||||
|
|
||||||
{% if job.complete %}
|
|
||||||
<dt class="is-pulled-left mr-5">{% trans "Import completed:" %}</dt>
|
|
||||||
<dd>{{ task.date_done | naturaltime }}</dd>
|
|
||||||
{% endif %}
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% if not job.complete %}
|
<h2 class="title is-4">
|
||||||
<p>
|
{% trans "Your Import" %}
|
||||||
{% trans "Import still in progress." %}
|
</h2>
|
||||||
<br/>
|
|
||||||
{% trans "(Hit reload to update!)" %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if failed_items %}
|
|
||||||
<div class="block">
|
|
||||||
<h2 class="title is-4">{% trans "Failed to load" %}</h2>
|
|
||||||
{% if not job.retry %}
|
|
||||||
<form name="retry" action="/import/{{ job.id }}" method="post" class="box">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
{% with failed_count=failed_items|length %}
|
|
||||||
{% if failed_count > 10 %}
|
|
||||||
<p class="block">
|
|
||||||
<a href="#select-all-failed-imports">
|
|
||||||
{% blocktrans %}Jump to the bottom of the list to select the {{ failed_count }} items which failed to import.{% endblocktrans %}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
<fieldset id="failed_imports">
|
|
||||||
<ul>
|
|
||||||
{% for item in failed_items %}
|
|
||||||
<li class="mb-2 is-flex is-align-items-start">
|
|
||||||
<input class="checkbox mt-1" type="checkbox" name="import_item" value="{{ item.id }}" id="import_item_{{ item.id }}">
|
|
||||||
<label class="ml-1" for="import-item-{{ item.id }}">
|
|
||||||
{% blocktrans with index=item.index title=item.data.Title author=item.data.Author %}Line {{ index }}: <strong>{{ title }}</strong> by {{ author }}{% endblocktrans %}
|
|
||||||
<br/>
|
|
||||||
{{ item.fail_reason }}.
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset class="mt-3">
|
|
||||||
<a name="select-all-failed-imports"></a>
|
|
||||||
|
|
||||||
<label class="label is-inline">
|
|
||||||
<input
|
|
||||||
id="toggle-all-checkboxes-failed-imports"
|
|
||||||
class="checkbox"
|
|
||||||
type="checkbox"
|
|
||||||
data-action="toggle-all"
|
|
||||||
data-target="failed_imports"
|
|
||||||
/>
|
|
||||||
{% trans "Select all" %}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<button class="button is-block mt-3" type="submit">{% trans "Retry items" %}</button>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
<ul>
|
|
||||||
{% for item in failed_items %}
|
|
||||||
<li class="pb-1">
|
|
||||||
<p>
|
|
||||||
Line {{ item.index }}:
|
|
||||||
<strong>{{ item.data.Title }}</strong> by
|
|
||||||
{{ item.data.Author }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ item.fail_reason }}.
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="block">
|
|
||||||
{% if job.complete %}
|
|
||||||
<h2 class="title is-4">{% trans "Successfully imported" %}</h2>
|
|
||||||
{% else %}
|
|
||||||
<h2 class="title is-4">{% trans "Import Progress" %}</h2>
|
|
||||||
{% endif %}
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Book" %}
|
{% trans "Row" %}
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Title" %}
|
{% trans "Title" %}
|
||||||
|
@ -124,16 +50,16 @@
|
||||||
{% trans "Author" %}
|
{% trans "Author" %}
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
|
{% trans "Book" %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Status" %}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if item.book %}
|
{{ item.index }}
|
||||||
<a href="{{ item.book.local_path }}">
|
|
||||||
{% include 'snippets/book_cover.html' with book=item.book cover_class='is-h-s' size='small' %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ item.data.Title }}
|
{{ item.data.Title }}
|
||||||
|
@ -143,15 +69,34 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if item.book %}
|
{% if item.book %}
|
||||||
<span class="icon icon-check">
|
<a href="{{ item.book.local_path }}">
|
||||||
<span class="is-sr-only">{% trans "Imported" %}</span>
|
{% include 'snippets/book_cover.html' with book=item.book cover_class='is-h-s' size='small' %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if item.book %}
|
||||||
|
<span class="icon icon-check has-text-success" aria-hidden="true"></span>
|
||||||
|
<span class="is-sr-only-mobile">{% trans "Imported" %}</span>
|
||||||
|
|
||||||
|
{% elif item.fail_reason %}
|
||||||
|
<span class="icon icon-x has-text-danger" aria-hidden="true"></span>
|
||||||
|
<span class="is-sr-only-mobile">
|
||||||
|
{{ item.fail_reason }}
|
||||||
</span>
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="icon icon-dots-three" aria-hidden="true"></span>
|
||||||
|
<span class="is-sr-only-mobile">{% trans "Pending" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% include 'snippets/pagination.html' with page=items %}
|
||||||
|
</div>
|
||||||
{% endspaceless %}{% endblock %}
|
{% endspaceless %}{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
""" import books from another app """
|
""" import books from another app """
|
||||||
from io import TextIOWrapper
|
from io import TextIOWrapper
|
||||||
|
import math
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.core.paginator import Paginator
|
||||||
from django.http import HttpResponseBadRequest
|
from django.http import HttpResponseBadRequest
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
|
@ -17,7 +19,7 @@ from bookwyrm.importers import (
|
||||||
GoodreadsImporter,
|
GoodreadsImporter,
|
||||||
StorygraphImporter,
|
StorygraphImporter,
|
||||||
)
|
)
|
||||||
from bookwyrm.tasks import app
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
|
|
||||||
# pylint: disable= no-self-use
|
# pylint: disable= no-self-use
|
||||||
@method_decorator(login_required, name="dispatch")
|
@method_decorator(login_required, name="dispatch")
|
||||||
|
@ -82,21 +84,25 @@ class ImportStatus(View):
|
||||||
if job.user != request.user:
|
if job.user != request.user:
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
try:
|
items = job.items.order_by("index")
|
||||||
task = app.AsyncResult(job.task_id)
|
pending_items = items.filter(fail_reason__isnull=True, book__isnull=True)
|
||||||
# triggers attribute error if the task won't load
|
item_count = items.count() or 1
|
||||||
task.status # pylint: disable=pointless-statement
|
|
||||||
except (ValueError, AttributeError):
|
|
||||||
task = None
|
|
||||||
|
|
||||||
items = job.items.order_by("index").all()
|
paginated = Paginator(items, PAGE_LENGTH)
|
||||||
failed_items = [i for i in items if i.fail_reason]
|
page = paginated.get_page(request.GET.get("page"))
|
||||||
items = [i for i in items if not i.fail_reason]
|
data = {
|
||||||
return TemplateResponse(
|
"job": job,
|
||||||
request,
|
"items": page,
|
||||||
"import/import_status.html",
|
"page_range": paginated.get_elided_page_range(
|
||||||
{"job": job, "items": items, "failed_items": failed_items, "task": task},
|
page.number, on_each_side=2, on_ends=1
|
||||||
)
|
),
|
||||||
|
"complete": not pending_items.exists(),
|
||||||
|
"percent": math.floor( # pylint: disable=c-extension-no-member
|
||||||
|
(item_count - pending_items.count()) / item_count * 100
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return TemplateResponse(request, "import/import_status.html", data)
|
||||||
|
|
||||||
def post(self, request, job_id):
|
def post(self, request, job_id):
|
||||||
"""retry lines from an import"""
|
"""retry lines from an import"""
|
||||||
|
|
Loading…
Reference in a new issue