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 %} {% block content %}
<div class="block"> <div class="block">
<h1 class="title">Import Books from GoodReads</h1> <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 %} {% csrf_token %}
<div class="field"> <div class="field">
{{ import_form.as_p }} {{ import_form.as_p }}
@ -30,7 +30,7 @@
{% endif %} {% endif %}
<ul> <ul>
{% for job in jobs %} {% 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 %} {% endfor %}
</ul> </ul>
</div> </div>

View file

@ -30,9 +30,8 @@
<div class="block"> <div class="block">
<h2 class="title is-4">Failed to load</h2> <h2 class="title is-4">Failed to load</h2>
{% if not job.retry %} {% if not job.retry %}
<form name="retry" action="/retry-import/" method="post"> <form name="retry" action="/import/{{ job.id }}" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="import_job" value="{{ job.id }}">
<ul> <ul>
<fieldset> <fieldset>
{% for item in failed_items %} {% 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> boosted your <a href="{{ related_status.local_path }}">{{ related_status | status_preview_name|safe }}</a>
{% endif %} {% endif %}
{% else %} {% 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 %} {% endif %}
</p> </p>
</div> </div>

View file

@ -19,7 +19,7 @@ class DirectMessageViews(TestCase):
def test_direct_messages_page(self): def test_direct_messages_page(self):
''' there are so many views, this just makes sure it LOADS ''' ''' 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 = self.factory.get('')
request.user = self.local_user request.user = self.local_user
result = view(request) 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()), re_path(r'^direct-messages/?$', views.DirectMessage.as_view()),
# imports
re_path(r'^import/?$', views.Import.as_view()), re_path(r'^import/?$', views.Import.as_view()),
re_path(r'^import/?$', actions.import_data), re_path(r'^import/(\d+)/?$', views.ImportStatus.as_view()),
re_path(r'^retry-import/?$', actions.retry_import),
re_path(r'^import-status/(\d+)/?$', vviews.import_status),
re_path(r'^user-edit/?$', vviews.edit_profile_page), re_path(r'^user-edit/?$', vviews.edit_profile_page),

View file

@ -1,5 +1,5 @@
''' views for actions you can take in the application ''' ''' views for actions you can take in the application '''
from io import BytesIO, TextIOWrapper from io import BytesIO
from uuid import uuid4 from uuid import uuid4
from PIL import Image from PIL import Image
@ -15,7 +15,7 @@ from django.template.response import TemplateResponse
from django.utils import timezone from django.utils import timezone
from django.views.decorators.http import require_POST 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.connectors import connector_manager
from bookwyrm.broadcast import broadcast from bookwyrm.broadcast import broadcast
from bookwyrm.vviews import get_user_from_username, get_edition 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) 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): def update_readthrough(request, book=None, create=True):
''' updates but does not save dates on a readthrough ''' ''' updates but does not save dates on a readthrough '''
try: try:

View file

@ -5,4 +5,4 @@ from .invite import ManageInvites, Invite
from .landing import About, Home, Feed, Discover from .landing import About, Home, Feed, Discover
from .notifications import Notifications from .notifications import Notifications
from .direct_message import DirectMessage 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 import Avg, Q
from django.db.models.functions import Greatest from django.db.models.functions import Greatest
from django.http import HttpResponseNotFound, JsonResponse from django.http import HttpResponseNotFound, JsonResponse
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@ -18,7 +17,6 @@ from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.connectors import connector_manager from bookwyrm.connectors import connector_manager
from bookwyrm.settings import PAGE_LENGTH from bookwyrm.settings import PAGE_LENGTH
from bookwyrm.tasks import app
from bookwyrm.utils import regex from bookwyrm.utils import regex
@ -159,38 +157,6 @@ def search(request):
return TemplateResponse(request, 'search_results.html', data) 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 @csrf_exempt
@require_GET @require_GET
def user_page(request, username): def user_page(request, username):