Adds import view

This commit is contained in:
Mouse Reeve 2021-01-12 11:28:03 -08:00
parent b61544b5f5
commit 8693895bc6
10 changed files with 136 additions and 88 deletions

View file

@ -3,7 +3,7 @@
{% block content %}
<div class="block">
<h1 class="title">Import Books from GoodReads</h1>
<form name="import" action="/import-data/" method="post" enctype="multipart/form-data">
<form name="import" action="/import" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="field">
{{ import_form.as_p }}
@ -30,7 +30,7 @@
{% endif %}
<ul>
{% for job in jobs %}
<li><a href="/import-status/{{ job.id }}">{{ job.created_date | naturaltime }}</a></li>
<li><a href="/import/{{ job.id }}">{{ job.created_date | naturaltime }}</a></li>
{% endfor %}
</ul>
</div>

View file

@ -30,9 +30,8 @@
<div class="block">
<h2 class="title is-4">Failed to load</h2>
{% if not job.retry %}
<form name="retry" action="/retry-import/" method="post">
<form name="retry" action="/import/{{ job.id }}" method="post">
{% csrf_token %}
<input type="hidden" name="import_job" value="{{ job.id }}">
<ul>
<fieldset>
{% for item in failed_items %}

View file

@ -63,7 +63,7 @@
boosted your <a href="{{ related_status.local_path }}">{{ related_status | status_preview_name|safe }}</a>
{% endif %}
{% else %}
your <a href="/import-status/{{ notification.related_import.id }}">import</a> completed.
your <a href="/import/{{ notification.related_import.id }}">import</a> completed.
{% endif %}
</p>
</div>

View file

@ -19,7 +19,7 @@ class DirectMessageViews(TestCase):
def test_direct_messages_page(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.DirectMessages.as_view()
view = views.DirectMessage.as_view()
request = self.factory.get('')
request.user = self.local_user
result = view(request)

View file

@ -0,0 +1,43 @@
''' test for app action functionality '''
from unittest.mock import patch
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from bookwyrm import models
from bookwyrm import views
class ImportViews(TestCase):
''' goodreads import 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.mouse', 'password',
local=True, localname='mouse')
def test_import_page(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.Import.as_view()
request = self.factory.get('')
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'import.html')
self.assertEqual(result.status_code, 200)
def test_import_status(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.ImportStatus.as_view()
import_job = models.ImportJob.objects.create(user=self.local_user)
request = self.factory.get('')
request.user = self.local_user
with patch('bookwyrm.tasks.app.AsyncResult') as async_result:
async_result.return_value = []
result = view(request, import_job.id)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'import_status.html')
self.assertEqual(result.status_code, 200)

View file

@ -65,11 +65,9 @@ urlpatterns = [
re_path(r'^direct-messages/?$', views.DirectMessage.as_view()),
# imports
re_path(r'^import/?$', views.Import.as_view()),
re_path(r'^import/?$', actions.import_data),
re_path(r'^retry-import/?$', actions.retry_import),
re_path(r'^import-status/(\d+)/?$', vviews.import_status),
re_path(r'^import/(\d+)/?$', views.ImportStatus.as_view()),
re_path(r'^user-edit/?$', vviews.edit_profile_page),

View file

@ -1,5 +1,5 @@
''' views for actions you can take in the application '''
from io import BytesIO, TextIOWrapper
from io import BytesIO
from uuid import uuid4
from PIL import Image
@ -15,7 +15,7 @@ from django.template.response import TemplateResponse
from django.utils import timezone
from django.views.decorators.http import require_POST
from bookwyrm import forms, models, outgoing, goodreads_import
from bookwyrm import forms, models, outgoing
from bookwyrm.connectors import connector_manager
from bookwyrm.broadcast import broadcast
from bookwyrm.vviews import get_user_from_username, get_edition
@ -605,47 +605,6 @@ def delete_follow_request(request):
return redirect('/user/%s' % request.user.localname)
@login_required
@require_POST
def import_data(request):
''' ingest a goodreads csv '''
form = forms.ImportForm(request.POST, request.FILES)
if form.is_valid():
include_reviews = request.POST.get('include_reviews') == 'on'
privacy = request.POST.get('privacy')
try:
job = goodreads_import.create_job(
request.user,
TextIOWrapper(
request.FILES['csv_file'],
encoding=request.encoding),
include_reviews,
privacy,
)
except (UnicodeDecodeError, ValueError):
return HttpResponseBadRequest('Not a valid csv file')
goodreads_import.start_import(job)
return redirect('/import-status/%d' % job.id)
return HttpResponseBadRequest()
@login_required
@require_POST
def retry_import(request):
''' ingest a goodreads csv '''
job = get_object_or_404(models.ImportJob, id=request.POST.get('import_job'))
items = []
for item in request.POST.getlist('import_item'):
items.append(get_object_or_404(models.ImportItem, id=item))
job = goodreads_import.create_retry_job(
request.user,
job,
items,
)
goodreads_import.start_import(job)
return redirect('/import-status/%d' % job.id)
def update_readthrough(request, book=None, create=True):
''' updates but does not save dates on a readthrough '''
try:

View file

@ -5,4 +5,4 @@ from .invite import ManageInvites, Invite
from .landing import About, Home, Feed, Discover
from .notifications import Notifications
from .direct_message import DirectMessage
from .import_datra import Import
from .import_data import Import, ImportStatus

View file

@ -0,0 +1,83 @@
''' import books from another app '''
from io import TextIOWrapper
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseBadRequest
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views import View
from bookwyrm import forms, goodreads_import, models
from bookwyrm.tasks import app
# pylint: disable= no-self-use
@method_decorator(login_required, name='dispatch')
class Import(View):
''' import view '''
def get(self, request):
''' load import page '''
return TemplateResponse(request, 'import.html', {
'title': 'Import Books',
'import_form': forms.ImportForm(),
'jobs': models.ImportJob.
objects.filter(user=request.user).order_by('-created_date'),
})
def post(self, request):
''' ingest a goodreads csv '''
form = forms.ImportForm(request.POST, request.FILES)
if form.is_valid():
include_reviews = request.POST.get('include_reviews') == 'on'
privacy = request.POST.get('privacy')
try:
job = goodreads_import.create_job(
request.user,
TextIOWrapper(
request.FILES['csv_file'],
encoding=request.encoding),
include_reviews,
privacy,
)
except (UnicodeDecodeError, ValueError):
return HttpResponseBadRequest('Not a valid csv file')
goodreads_import.start_import(job)
return redirect('/import-status/%d' % job.id)
return HttpResponseBadRequest()
@method_decorator(login_required, name='dispatch')
class ImportStatus(View):
''' status of an existing import '''
def get(self, request, job_id):
''' status of an import job '''
job = models.ImportJob.objects.get(id=job_id)
if job.user != request.user:
raise PermissionDenied
task = app.AsyncResult(job.task_id)
items = job.items.order_by('index').all()
failed_items = [i for i in items if i.fail_reason]
items = [i for i in items if not i.fail_reason]
return TemplateResponse(request, 'import_status.html', {
'title': 'Import Status',
'job': job,
'items': items,
'failed_items': failed_items,
'task': task
})
def post(self, request, job_id):
''' retry lines from an import '''
job = get_object_or_404(models.ImportJob, id=job_id)
items = []
for item in request.POST.getlist('import_item'):
items.append(get_object_or_404(models.ImportItem, id=item))
job = goodreads_import.create_retry_job(
request.user,
job,
items,
)
goodreads_import.start_import(job)
return redirect('/import-status/%d' % job.id)

View file

@ -7,7 +7,6 @@ from django.core.paginator import Paginator
from django.db.models import Avg, Q
from django.db.models.functions import Greatest
from django.http import HttpResponseNotFound, JsonResponse
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.views.decorators.csrf import csrf_exempt
@ -18,7 +17,6 @@ from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.connectors import connector_manager
from bookwyrm.settings import PAGE_LENGTH
from bookwyrm.tasks import app
from bookwyrm.utils import regex
@ -159,38 +157,6 @@ def search(request):
return TemplateResponse(request, 'search_results.html', data)
@login_required
@require_GET
def import_page(request):
''' import history from goodreads '''
return TemplateResponse(request, 'import.html', {
'title': 'Import Books',
'import_form': forms.ImportForm(),
'jobs': models.ImportJob.
objects.filter(user=request.user).order_by('-created_date'),
})
@login_required
@require_GET
def import_status(request, job_id):
''' status of an import job '''
job = models.ImportJob.objects.get(id=job_id)
if job.user != request.user:
raise PermissionDenied
task = app.AsyncResult(job.task_id)
items = job.items.order_by('index').all()
failed_items = [i for i in items if i.fail_reason]
items = [i for i in items if not i.fail_reason]
return TemplateResponse(request, 'import_status.html', {
'title': 'Import Status',
'job': job,
'items': items,
'failed_items': failed_items,
'task': task
})
@csrf_exempt
@require_GET
def user_page(request, username):