From df54df8309ffd77007afe2819a8a00a3b4d98d8b Mon Sep 17 00:00:00 2001 From: Giebisch Date: Tue, 6 Dec 2022 23:11:03 +0100 Subject: [PATCH] Added Import Limit --- bookwyrm/importers/importer.py | 20 ++++++++++- .../0167_sitesettings_import_size_limit.py | 23 +++++++++++++ bookwyrm/models/site.py | 2 ++ .../static/css/bookwyrm/utilities/_size.scss | 4 +++ bookwyrm/templates/import/import.html | 12 ++++++- .../templates/settings/imports/imports.html | 33 ++++++++++++++++++- .../tests/importers/test_calibre_import.py | 2 +- .../tests/importers/test_goodreads_import.py | 2 +- bookwyrm/tests/importers/test_importer.py | 2 +- .../importers/test_librarything_import.py | 1 + .../importers/test_openlibrary_import.py | 2 +- .../tests/importers/test_storygraph_import.py | 2 +- bookwyrm/urls.py | 5 +++ bookwyrm/views/__init__.py | 7 +++- bookwyrm/views/admin/imports.py | 18 ++++++++++ bookwyrm/views/imports/import_data.py | 12 +++++++ 16 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 bookwyrm/migrations/0167_sitesettings_import_size_limit.py diff --git a/bookwyrm/importers/importer.py b/bookwyrm/importers/importer.py index a2641ff11..95b34ec67 100644 --- a/bookwyrm/importers/importer.py +++ b/bookwyrm/importers/importer.py @@ -1,7 +1,8 @@ """ handle reading a csv from an external service, defaults are from Goodreads """ import csv +from datetime import timedelta from django.utils import timezone -from bookwyrm.models import ImportJob, ImportItem +from bookwyrm.models import ImportJob, ImportItem, SiteSettings class Importer: @@ -49,7 +50,24 @@ class Importer: 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: + if enforce_limit and index >= allowed_imports: + break self.create_item(job, index, entry) return job diff --git a/bookwyrm/migrations/0167_sitesettings_import_size_limit.py b/bookwyrm/migrations/0167_sitesettings_import_size_limit.py new file mode 100644 index 000000000..fdbfaf51d --- /dev/null +++ b/bookwyrm/migrations/0167_sitesettings_import_size_limit.py @@ -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), + ), + ] diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index 9e97ede9a..78f35e39b 100644 --- a/bookwyrm/models/site.py +++ b/bookwyrm/models/site.py @@ -88,6 +88,8 @@ class SiteSettings(SiteModel): # controls 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"]) diff --git a/bookwyrm/static/css/bookwyrm/utilities/_size.scss b/bookwyrm/static/css/bookwyrm/utilities/_size.scss index cbc74d7ab..258aa9a73 100644 --- a/bookwyrm/static/css/bookwyrm/utilities/_size.scss +++ b/bookwyrm/static/css/bookwyrm/utilities/_size.scss @@ -40,6 +40,10 @@ width: 500px !important; } +.is-h-em { + height: 1em !important; +} + .is-h-xs { height: 80px !important; } diff --git a/bookwyrm/templates/import/import.html b/bookwyrm/templates/import/import.html index 325caa92b..deb39f7da 100644 --- a/bookwyrm/templates/import/import.html +++ b/bookwyrm/templates/import/import.html @@ -15,6 +15,11 @@ {% endif %} {% if site.imports_enabled %} +
+

Currently you are allowed to import {{ import_size_limit }} books every {{ import_limit_reset }} days.

+

You have {{ allowed_imports }} left.

+
+ {% if recent_avg_hours or recent_avg_minutes %}

@@ -90,7 +95,12 @@

- + {% if allowed_imports > 0 %} + + {% else %} + +

{% trans "You've reached the import limit." %}

+ {% endif%} {% else %}
diff --git a/bookwyrm/templates/settings/imports/imports.html b/bookwyrm/templates/settings/imports/imports.html index 135af34ed..f498a619f 100644 --- a/bookwyrm/templates/settings/imports/imports.html +++ b/bookwyrm/templates/settings/imports/imports.html @@ -57,8 +57,39 @@
{% endif %} +
+ + + {% trans "Limit the amount of imports" %} + + + +
+
+ {% 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." %} +
+
+ + + + + + {% csrf_token %} +
+ +
+
+
+
-
    diff --git a/bookwyrm/tests/importers/test_calibre_import.py b/bookwyrm/tests/importers/test_calibre_import.py index 57c781b22..37b206458 100644 --- a/bookwyrm/tests/importers/test_calibre_import.py +++ b/bookwyrm/tests/importers/test_calibre_import.py @@ -28,7 +28,7 @@ class CalibreImport(TestCase): self.local_user = models.User.objects.create_user( "mouse", "mouse@mouse.mouse", "password", local=True ) - + models.SiteSettings.objects.create() work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Example Edition", diff --git a/bookwyrm/tests/importers/test_goodreads_import.py b/bookwyrm/tests/importers/test_goodreads_import.py index 815166691..88f8eb3f4 100644 --- a/bookwyrm/tests/importers/test_goodreads_import.py +++ b/bookwyrm/tests/importers/test_goodreads_import.py @@ -35,7 +35,7 @@ class GoodreadsImport(TestCase): self.local_user = models.User.objects.create_user( "mouse", "mouse@mouse.mouse", "password", local=True ) - + models.SiteSettings.objects.create() work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Example Edition", diff --git a/bookwyrm/tests/importers/test_importer.py b/bookwyrm/tests/importers/test_importer.py index ef7f2448b..a6ccc60c7 100644 --- a/bookwyrm/tests/importers/test_importer.py +++ b/bookwyrm/tests/importers/test_importer.py @@ -39,7 +39,7 @@ class GenericImporter(TestCase): self.local_user = models.User.objects.create_user( "mouse", "mouse@mouse.mouse", "password", local=True ) - + models.SiteSettings.objects.create() work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Example Edition", diff --git a/bookwyrm/tests/importers/test_librarything_import.py b/bookwyrm/tests/importers/test_librarything_import.py index 3fe752b40..71a1c9796 100644 --- a/bookwyrm/tests/importers/test_librarything_import.py +++ b/bookwyrm/tests/importers/test_librarything_import.py @@ -37,6 +37,7 @@ class LibrarythingImport(TestCase): self.local_user = models.User.objects.create_user( "mmai", "mmai@mmai.mmai", "password", local=True ) + models.SiteSettings.objects.create() work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Example Edition", diff --git a/bookwyrm/tests/importers/test_openlibrary_import.py b/bookwyrm/tests/importers/test_openlibrary_import.py index b91de1d1b..82b5ec3ea 100644 --- a/bookwyrm/tests/importers/test_openlibrary_import.py +++ b/bookwyrm/tests/importers/test_openlibrary_import.py @@ -35,7 +35,7 @@ class OpenLibraryImport(TestCase): self.local_user = models.User.objects.create_user( "mouse", "mouse@mouse.mouse", "password", local=True ) - + models.SiteSettings.objects.create() work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Example Edition", diff --git a/bookwyrm/tests/importers/test_storygraph_import.py b/bookwyrm/tests/importers/test_storygraph_import.py index 78edc0870..9980c48e8 100644 --- a/bookwyrm/tests/importers/test_storygraph_import.py +++ b/bookwyrm/tests/importers/test_storygraph_import.py @@ -35,7 +35,7 @@ class StorygraphImport(TestCase): self.local_user = models.User.objects.create_user( "mouse", "mouse@mouse.mouse", "password", local=True ) - + models.SiteSettings.objects.create() work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Example Edition", diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index daf05e10e..8e1771a9a 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -311,6 +311,11 @@ urlpatterns = [ views.enable_imports, name="settings-imports-enable", ), + re_path( + r"^settings/imports/set-limit/?$", + views.set_import_size_limit, + name="settings-imports-set-limit", + ), re_path( r"^settings/celery/?$", views.CeleryStatus.as_view(), name="settings-celery" ), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 21e33450c..97ad30a6f 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -10,7 +10,12 @@ from .admin.federation import Federation, FederatedServer from .admin.federation import AddFederatedServer, ImportServerBlocklist from .admin.federation import block_server, unblock_server, refresh_server 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.invite import ManageInvites, Invite, InviteRequest from .admin.invite import ManageInviteRequests, ignore_invite_request diff --git a/bookwyrm/views/admin/imports.py b/bookwyrm/views/admin/imports.py index fe04a0f2b..066bc42e4 100644 --- a/bookwyrm/views/admin/imports.py +++ b/bookwyrm/views/admin/imports.py @@ -38,6 +38,8 @@ class ImportList(View): paginated = Paginator(imports, PAGE_LENGTH) page = paginated.get_page(request.GET.get("page")) + + site_settings = models.SiteSettings.objects.get() data = { "imports": page, "page_range": paginated.get_elided_page_range( @@ -45,6 +47,8 @@ class ImportList(View): ), "status": status, "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) @@ -76,3 +80,17 @@ def enable_imports(request): site.imports_enabled = True site.save(update_fields=["imports_enabled"]) 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") diff --git a/bookwyrm/views/imports/import_data.py b/bookwyrm/views/imports/import_data.py index f0bb2e698..dba995a7e 100644 --- a/bookwyrm/views/imports/import_data.py +++ b/bookwyrm/views/imports/import_data.py @@ -51,6 +51,18 @@ class Import(View): elif seconds: 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) def post(self, request):