mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2025-02-02 04:12:20 +00:00
Added Import Limit
This commit is contained in:
parent
8ec984c3ff
commit
df54df8309
16 changed files with 138 additions and 9 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
23
bookwyrm/migrations/0167_sitesettings_import_size_limit.py
Normal file
23
bookwyrm/migrations/0167_sitesettings_import_size_limit.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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"])
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
<button class="button is-primary" type="submit">{% trans "Import" %}</button>
|
{% if allowed_imports > 0 %}
|
||||||
|
<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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
),
|
),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in a new issue