Added Import Limit

This commit is contained in:
Giebisch 2022-12-06 23:11:03 +01:00
parent 8ec984c3ff
commit df54df8309
16 changed files with 138 additions and 9 deletions

View file

@ -1,7 +1,8 @@
""" handle reading a csv from an external service, defaults are from Goodreads """ """ handle reading a csv from an external service, defaults are from Goodreads """
import csv import csv
from datetime import timedelta
from django.utils import timezone from django.utils import timezone
from bookwyrm.models import ImportJob, ImportItem from bookwyrm.models import ImportJob, ImportItem, SiteSettings
class Importer: class Importer:
@ -49,7 +50,24 @@ class Importer:
source=self.service, source=self.service,
) )
site_settings = SiteSettings.objects.get()
import_size_limit = site_settings.import_size_limit
import_limit_reset = site_settings.import_limit_reset
enforce_limit = import_size_limit and import_limit_reset
if enforce_limit:
time_range = timezone.now() - timedelta(days=import_limit_reset)
import_jobs = ImportJob.objects.filter(
user=user, created_date__gte=time_range
)
imported_books = sum([job.successful_item_count for job in import_jobs])
allowed_imports = import_size_limit - imported_books
if allowed_imports <= 0:
job.complete_job()
return job
for index, entry in rows: for index, entry in rows:
if enforce_limit and index >= allowed_imports:
break
self.create_item(job, index, entry) self.create_item(job, index, entry)
return job return job

View file

@ -0,0 +1,23 @@
# Generated by Django 3.2.16 on 2022-12-05 13:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0166_sitesettings_imports_enabled"),
]
operations = [
migrations.AddField(
model_name="sitesettings",
name="import_size_limit",
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name="sitesettings",
name="import_limit_reset",
field=models.IntegerField(default=0),
),
]

View file

@ -88,6 +88,8 @@ class SiteSettings(SiteModel):
# controls # controls
imports_enabled = models.BooleanField(default=True) imports_enabled = models.BooleanField(default=True)
import_size_limit = models.IntegerField(default=0)
import_limit_reset = models.IntegerField(default=0)
field_tracker = FieldTracker(fields=["name", "instance_tagline", "logo"]) field_tracker = FieldTracker(fields=["name", "instance_tagline", "logo"])

View file

@ -40,6 +40,10 @@
width: 500px !important; width: 500px !important;
} }
.is-h-em {
height: 1em !important;
}
.is-h-xs { .is-h-xs {
height: 80px !important; height: 80px !important;
} }

View file

@ -15,6 +15,11 @@
{% endif %} {% endif %}
{% if site.imports_enabled %} {% if site.imports_enabled %}
<div class="notification">
<p>Currently you are allowed to import {{ import_size_limit }} books every {{ import_limit_reset }} days.</p>
<p>You have {{ allowed_imports }} left.</p>
</div>
{% if recent_avg_hours or recent_avg_minutes %} {% if recent_avg_hours or recent_avg_minutes %}
<div class="notification"> <div class="notification">
<p> <p>
@ -90,7 +95,12 @@
</div> </div>
</div> </div>
</div> </div>
{% if allowed_imports > 0 %}
<button class="button is-primary" type="submit">{% trans "Import" %}</button> <button class="button is-primary" type="submit">{% trans "Import" %}</button>
{% else %}
<button class="button is-primary is-disabled" type="submit">{% trans "Import" %}</button>
<p>{% trans "You've reached the import limit." %}</p>
{% endif%}
</form> </form>
{% else %} {% else %}
<div class="box notification has-text-centered is-warning m-6 content"> <div class="box notification has-text-centered is-warning m-6 content">

View file

@ -57,8 +57,39 @@
</div> </div>
</form> </form>
{% endif %} {% endif %}
<details class="details-panel box">
<summary>
<span role="heading" aria-level="2" class="title is-6">
{% trans "Limit the amount of imports" %}
</span>
<span class="details-close icon icon-x" aria-hidden="true"></span>
</summary>
<form
name="imports-set-limit"
id="imports-set-limit"
method="POST"
action="{% url 'settings-imports-set-limit' %}"
>
<div class="notification">
{% trans "Some users might try to import a large number of books, which you want to limit." %}
{% trans "Set the value to 0 to not enforce any limit." %}
</div>
<div class="align.to-t">
<label for="limit">Set import limit to</label>
<input name="limit" class="input is-w-xs is-h-em" type="text" placeholder="0" value={{ import_size_limit }}>
<label for="reset">books every</label>
<input name="reset" class="input is-w-xs is-h-em" type="text" placeholder="0" value={{ import_limit_reset }}>
<label>days.</label>
{% csrf_token %}
<div class="control">
<button type="submit" class="button is-warning">
{% trans "Set limit" %}
</button>
</div>
</div>
</form>
</details>
</div> </div>
<div class="block"> <div class="block">
<div class="tabs"> <div class="tabs">
<ul> <ul>

View file

@ -28,7 +28,7 @@ class CalibreImport(TestCase):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True "mouse", "mouse@mouse.mouse", "password", local=True
) )
models.SiteSettings.objects.create()
work = models.Work.objects.create(title="Test Work") work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create( self.book = models.Edition.objects.create(
title="Example Edition", title="Example Edition",

View file

@ -35,7 +35,7 @@ class GoodreadsImport(TestCase):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True "mouse", "mouse@mouse.mouse", "password", local=True
) )
models.SiteSettings.objects.create()
work = models.Work.objects.create(title="Test Work") work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create( self.book = models.Edition.objects.create(
title="Example Edition", title="Example Edition",

View file

@ -39,7 +39,7 @@ class GenericImporter(TestCase):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True "mouse", "mouse@mouse.mouse", "password", local=True
) )
models.SiteSettings.objects.create()
work = models.Work.objects.create(title="Test Work") work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create( self.book = models.Edition.objects.create(
title="Example Edition", title="Example Edition",

View file

@ -37,6 +37,7 @@ class LibrarythingImport(TestCase):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mmai", "mmai@mmai.mmai", "password", local=True "mmai", "mmai@mmai.mmai", "password", local=True
) )
models.SiteSettings.objects.create()
work = models.Work.objects.create(title="Test Work") work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create( self.book = models.Edition.objects.create(
title="Example Edition", title="Example Edition",

View file

@ -35,7 +35,7 @@ class OpenLibraryImport(TestCase):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True "mouse", "mouse@mouse.mouse", "password", local=True
) )
models.SiteSettings.objects.create()
work = models.Work.objects.create(title="Test Work") work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create( self.book = models.Edition.objects.create(
title="Example Edition", title="Example Edition",

View file

@ -35,7 +35,7 @@ class StorygraphImport(TestCase):
self.local_user = models.User.objects.create_user( self.local_user = models.User.objects.create_user(
"mouse", "mouse@mouse.mouse", "password", local=True "mouse", "mouse@mouse.mouse", "password", local=True
) )
models.SiteSettings.objects.create()
work = models.Work.objects.create(title="Test Work") work = models.Work.objects.create(title="Test Work")
self.book = models.Edition.objects.create( self.book = models.Edition.objects.create(
title="Example Edition", title="Example Edition",

View file

@ -311,6 +311,11 @@ urlpatterns = [
views.enable_imports, views.enable_imports,
name="settings-imports-enable", name="settings-imports-enable",
), ),
re_path(
r"^settings/imports/set-limit/?$",
views.set_import_size_limit,
name="settings-imports-set-limit",
),
re_path( re_path(
r"^settings/celery/?$", views.CeleryStatus.as_view(), name="settings-celery" r"^settings/celery/?$", views.CeleryStatus.as_view(), name="settings-celery"
), ),

View file

@ -10,7 +10,12 @@ from .admin.federation import Federation, FederatedServer
from .admin.federation import AddFederatedServer, ImportServerBlocklist from .admin.federation import AddFederatedServer, ImportServerBlocklist
from .admin.federation import block_server, unblock_server, refresh_server from .admin.federation import block_server, unblock_server, refresh_server
from .admin.email_blocklist import EmailBlocklist from .admin.email_blocklist import EmailBlocklist
from .admin.imports import ImportList, disable_imports, enable_imports from .admin.imports import (
ImportList,
disable_imports,
enable_imports,
set_import_size_limit,
)
from .admin.ip_blocklist import IPBlocklist from .admin.ip_blocklist import IPBlocklist
from .admin.invite import ManageInvites, Invite, InviteRequest from .admin.invite import ManageInvites, Invite, InviteRequest
from .admin.invite import ManageInviteRequests, ignore_invite_request from .admin.invite import ManageInviteRequests, ignore_invite_request

View file

@ -38,6 +38,8 @@ class ImportList(View):
paginated = Paginator(imports, PAGE_LENGTH) paginated = Paginator(imports, PAGE_LENGTH)
page = paginated.get_page(request.GET.get("page")) page = paginated.get_page(request.GET.get("page"))
site_settings = models.SiteSettings.objects.get()
data = { data = {
"imports": page, "imports": page,
"page_range": paginated.get_elided_page_range( "page_range": paginated.get_elided_page_range(
@ -45,6 +47,8 @@ class ImportList(View):
), ),
"status": status, "status": status,
"sort": sort, "sort": sort,
"import_size_limit": site_settings.import_size_limit,
"import_limit_reset": site_settings.import_limit_reset,
} }
return TemplateResponse(request, "settings/imports/imports.html", data) return TemplateResponse(request, "settings/imports/imports.html", data)
@ -76,3 +80,17 @@ def enable_imports(request):
site.imports_enabled = True site.imports_enabled = True
site.save(update_fields=["imports_enabled"]) site.save(update_fields=["imports_enabled"])
return redirect("settings-imports") return redirect("settings-imports")
@require_POST
@permission_required("bookwyrm.edit_instance_settings", raise_exception=True)
# pylint: disable=unused-argument
def set_import_size_limit(request):
"""Limit the amount of books users can import at once"""
site = models.SiteSettings.objects.get()
import_size_limit = int(request.POST.get("limit"))
import_limit_reset = int(request.POST.get("reset"))
site.import_size_limit = import_size_limit
site.import_limit_reset = import_limit_reset
site.save(update_fields=["import_size_limit", "import_limit_reset"])
return redirect("settings-imports")

View file

@ -51,6 +51,18 @@ class Import(View):
elif seconds: elif seconds:
data["recent_avg_minutes"] = seconds / 60 data["recent_avg_minutes"] = seconds / 60
site_settings = models.SiteSettings.objects.get()
time_range = timezone.now() - datetime.timedelta(
days=site_settings.import_limit_reset
)
import_jobs = models.ImportJob.objects.filter(
user=request.user, created_date__gte=time_range
)
imported_books = sum([job.successful_item_count for job in import_jobs])
data["import_size_limit"] = site_settings.import_size_limit
data["import_limit_reset"] = site_settings.import_limit_reset
data["allowed_imports"] = site_settings.import_size_limit - imported_books
return TemplateResponse(request, "import/import.html", data) return TemplateResponse(request, "import/import.html", data)
def post(self, request): def post(self, request):