mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2025-02-08 23:32:21 +00:00
Merge branch 'main' into export-fixes
This commit is contained in:
commit
46a158d701
33 changed files with 454 additions and 184 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.7.1
|
0.7.2
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
""" Get your admin code to allow install """
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
from bookwyrm import models
|
|
||||||
from bookwyrm.settings import VERSION
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""command-line options"""
|
|
||||||
|
|
||||||
help = "What version is this?"
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
"""specify which function to run"""
|
|
||||||
parser.add_argument(
|
|
||||||
"--current",
|
|
||||||
action="store_true",
|
|
||||||
help="Version stored in database",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--target",
|
|
||||||
action="store_true",
|
|
||||||
help="Version stored in settings",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--update",
|
|
||||||
action="store_true",
|
|
||||||
help="Update database version",
|
|
||||||
)
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
"""execute init"""
|
|
||||||
site = models.SiteSettings.objects.get()
|
|
||||||
current = site.version or "0.0.1"
|
|
||||||
target = VERSION
|
|
||||||
if options.get("current"):
|
|
||||||
print(current)
|
|
||||||
return
|
|
||||||
|
|
||||||
if options.get("target"):
|
|
||||||
print(target)
|
|
||||||
return
|
|
||||||
|
|
||||||
if options.get("update"):
|
|
||||||
site.version = target
|
|
||||||
site.save()
|
|
||||||
return
|
|
||||||
|
|
||||||
if current != target:
|
|
||||||
print(f"{current}/{target}")
|
|
||||||
else:
|
|
||||||
print(current)
|
|
23
bookwyrm/migrations/0192_make_page_positions_text.py
Normal file
23
bookwyrm/migrations/0192_make_page_positions_text.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.23 on 2024-01-04 23:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0191_merge_20240102_0326"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="quotation",
|
||||||
|
name="endposition",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="quotation",
|
||||||
|
name="position",
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.2.23 on 2024-01-02 19:36
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0191_merge_20240102_0326"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="sitesettings",
|
||||||
|
old_name="version",
|
||||||
|
new_name="available_version",
|
||||||
|
),
|
||||||
|
]
|
13
bookwyrm/migrations/0193_merge_20240203_1539.py
Normal file
13
bookwyrm/migrations/0193_merge_20240203_1539.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Generated by Django 3.2.23 on 2024-02-03 15:39
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0192_make_page_positions_text"),
|
||||||
|
("bookwyrm", "0192_sitesettings_user_exports_enabled"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
13
bookwyrm/migrations/0194_merge_20240203_1619.py
Normal file
13
bookwyrm/migrations/0194_merge_20240203_1619.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Generated by Django 3.2.23 on 2024-02-03 16:19
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0192_rename_version_sitesettings_available_version"),
|
||||||
|
("bookwyrm", "0193_merge_20240203_1539"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
|
@ -152,8 +152,9 @@ class ActivitypubMixin:
|
||||||
# find anyone who's tagged in a status, for example
|
# find anyone who's tagged in a status, for example
|
||||||
mentions = self.recipients if hasattr(self, "recipients") else []
|
mentions = self.recipients if hasattr(self, "recipients") else []
|
||||||
|
|
||||||
# we always send activities to explicitly mentioned users' inboxes
|
# we always send activities to explicitly mentioned users (using shared inboxes
|
||||||
recipients = [u.inbox for u in mentions or [] if not u.local]
|
# where available to avoid duplicate submissions to a given instance)
|
||||||
|
recipients = {u.shared_inbox or u.inbox for u in mentions if not u.local}
|
||||||
|
|
||||||
# unless it's a dm, all the followers should receive the activity
|
# unless it's a dm, all the followers should receive the activity
|
||||||
if privacy != "direct":
|
if privacy != "direct":
|
||||||
|
@ -173,18 +174,18 @@ class ActivitypubMixin:
|
||||||
if user:
|
if user:
|
||||||
queryset = queryset.filter(following=user)
|
queryset = queryset.filter(following=user)
|
||||||
|
|
||||||
# ideally, we will send to shared inboxes for efficiency
|
# as above, we prefer shared inboxes if available
|
||||||
shared_inboxes = (
|
recipients.update(
|
||||||
queryset.filter(shared_inbox__isnull=False)
|
queryset.filter(shared_inbox__isnull=False).values_list(
|
||||||
.values_list("shared_inbox", flat=True)
|
"shared_inbox", flat=True
|
||||||
.distinct()
|
|
||||||
)
|
)
|
||||||
# but not everyone has a shared inbox
|
)
|
||||||
inboxes = queryset.filter(shared_inbox__isnull=True).values_list(
|
recipients.update(
|
||||||
|
queryset.filter(shared_inbox__isnull=True).values_list(
|
||||||
"inbox", flat=True
|
"inbox", flat=True
|
||||||
)
|
)
|
||||||
recipients += list(shared_inboxes) + list(inboxes)
|
)
|
||||||
return list(set(recipients))
|
return list(recipients)
|
||||||
|
|
||||||
def to_activity_dataclass(self):
|
def to_activity_dataclass(self):
|
||||||
"""convert from a model to an activity"""
|
"""convert from a model to an activity"""
|
||||||
|
|
|
@ -10,8 +10,11 @@ from django.dispatch import receiver
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from model_utils import FieldTracker
|
from model_utils import FieldTracker
|
||||||
|
|
||||||
|
from bookwyrm.connectors.abstract_connector import get_data
|
||||||
from bookwyrm.preview_images import generate_site_preview_image_task
|
from bookwyrm.preview_images import generate_site_preview_image_task
|
||||||
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES, STATIC_FULL_URL
|
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES, STATIC_FULL_URL
|
||||||
|
from bookwyrm.settings import RELEASE_API
|
||||||
|
from bookwyrm.tasks import app, MISC
|
||||||
from .base_model import BookWyrmModel, new_access_code
|
from .base_model import BookWyrmModel, new_access_code
|
||||||
from .user import User
|
from .user import User
|
||||||
from .fields import get_absolute_url
|
from .fields import get_absolute_url
|
||||||
|
@ -45,7 +48,7 @@ class SiteSettings(SiteModel):
|
||||||
default_theme = models.ForeignKey(
|
default_theme = models.ForeignKey(
|
||||||
"Theme", null=True, blank=True, on_delete=models.SET_NULL
|
"Theme", null=True, blank=True, on_delete=models.SET_NULL
|
||||||
)
|
)
|
||||||
version = models.CharField(null=True, blank=True, max_length=10)
|
available_version = models.CharField(null=True, blank=True, max_length=10)
|
||||||
|
|
||||||
# admin setup options
|
# admin setup options
|
||||||
install_mode = models.BooleanField(default=False)
|
install_mode = models.BooleanField(default=False)
|
||||||
|
@ -245,3 +248,14 @@ def preview_image(instance, *args, **kwargs):
|
||||||
|
|
||||||
if len(changed_fields) > 0:
|
if len(changed_fields) > 0:
|
||||||
generate_site_preview_image_task.delay()
|
generate_site_preview_image_task.delay()
|
||||||
|
|
||||||
|
|
||||||
|
@app.task(queue=MISC)
|
||||||
|
def check_for_updates_task():
|
||||||
|
"""See if git remote knows about a new version"""
|
||||||
|
site = SiteSettings.objects.get()
|
||||||
|
release = get_data(RELEASE_API, timeout=3)
|
||||||
|
available_version = release.get("tag_name", None)
|
||||||
|
if available_version:
|
||||||
|
site.available_version = available_version
|
||||||
|
site.save(update_fields=["available_version"])
|
||||||
|
|
|
@ -12,6 +12,8 @@ from django.db.models import Q
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.utils.translation import ngettext_lazy
|
||||||
from model_utils import FieldTracker
|
from model_utils import FieldTracker
|
||||||
from model_utils.managers import InheritanceManager
|
from model_utils.managers import InheritanceManager
|
||||||
|
|
||||||
|
@ -107,14 +109,14 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
||||||
@property
|
@property
|
||||||
def recipients(self):
|
def recipients(self):
|
||||||
"""tagged users who definitely need to get this status in broadcast"""
|
"""tagged users who definitely need to get this status in broadcast"""
|
||||||
mentions = [u for u in self.mention_users.all() if not u.local]
|
mentions = {u for u in self.mention_users.all() if not u.local}
|
||||||
if (
|
if (
|
||||||
hasattr(self, "reply_parent")
|
hasattr(self, "reply_parent")
|
||||||
and self.reply_parent
|
and self.reply_parent
|
||||||
and not self.reply_parent.user.local
|
and not self.reply_parent.user.local
|
||||||
):
|
):
|
||||||
mentions.append(self.reply_parent.user)
|
mentions.add(self.reply_parent.user)
|
||||||
return list(set(mentions))
|
return list(mentions)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ignore_activity(
|
def ignore_activity(
|
||||||
|
@ -178,6 +180,24 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
||||||
"""you can't boost dms"""
|
"""you can't boost dms"""
|
||||||
return self.privacy in ["unlisted", "public"]
|
return self.privacy in ["unlisted", "public"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def page_title(self):
|
||||||
|
"""title of the page when only this status is shown"""
|
||||||
|
return _("%(display_name)s's status") % {"display_name": self.user.display_name}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def page_description(self):
|
||||||
|
"""description of the page in meta tags when only this status is shown"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def page_image(self):
|
||||||
|
"""image to use as preview in meta tags when only this status is shown"""
|
||||||
|
if self.mention_books.exists():
|
||||||
|
book = self.mention_books.first()
|
||||||
|
return book.preview_image or book.cover
|
||||||
|
return self.user.preview_image
|
||||||
|
|
||||||
def to_replies(self, **kwargs):
|
def to_replies(self, **kwargs):
|
||||||
"""helper function for loading AP serialized replies to a status"""
|
"""helper function for loading AP serialized replies to a status"""
|
||||||
return self.to_ordered_collection(
|
return self.to_ordered_collection(
|
||||||
|
@ -301,6 +321,10 @@ class BookStatus(Status):
|
||||||
|
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def page_image(self):
|
||||||
|
return self.book.preview_image or self.book.cover or super().page_image
|
||||||
|
|
||||||
|
|
||||||
class Comment(BookStatus):
|
class Comment(BookStatus):
|
||||||
"""like a review but without a rating and transient"""
|
"""like a review but without a rating and transient"""
|
||||||
|
@ -332,17 +356,26 @@ class Comment(BookStatus):
|
||||||
|
|
||||||
activity_serializer = activitypub.Comment
|
activity_serializer = activitypub.Comment
|
||||||
|
|
||||||
|
@property
|
||||||
|
def page_title(self):
|
||||||
|
return _("%(display_name)s's comment on %(book_title)s") % {
|
||||||
|
"display_name": self.user.display_name,
|
||||||
|
"book_title": self.book.title,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Quotation(BookStatus):
|
class Quotation(BookStatus):
|
||||||
"""like a review but without a rating and transient"""
|
"""like a review but without a rating and transient"""
|
||||||
|
|
||||||
quote = fields.HtmlField()
|
quote = fields.HtmlField()
|
||||||
raw_quote = models.TextField(blank=True, null=True)
|
raw_quote = models.TextField(blank=True, null=True)
|
||||||
position = models.IntegerField(
|
position = models.TextField(
|
||||||
validators=[MinValueValidator(0)], null=True, blank=True
|
null=True,
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
endposition = models.IntegerField(
|
endposition = models.TextField(
|
||||||
validators=[MinValueValidator(0)], null=True, blank=True
|
null=True,
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
position_mode = models.CharField(
|
position_mode = models.CharField(
|
||||||
max_length=3,
|
max_length=3,
|
||||||
|
@ -374,6 +407,13 @@ class Quotation(BookStatus):
|
||||||
|
|
||||||
activity_serializer = activitypub.Quotation
|
activity_serializer = activitypub.Quotation
|
||||||
|
|
||||||
|
@property
|
||||||
|
def page_title(self):
|
||||||
|
return _("%(display_name)s's quote from %(book_title)s") % {
|
||||||
|
"display_name": self.user.display_name,
|
||||||
|
"book_title": self.book.title,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Review(BookStatus):
|
class Review(BookStatus):
|
||||||
"""a book review"""
|
"""a book review"""
|
||||||
|
@ -403,6 +443,13 @@ class Review(BookStatus):
|
||||||
"""indicate the book in question for mastodon (or w/e) users"""
|
"""indicate the book in question for mastodon (or w/e) users"""
|
||||||
return self.content
|
return self.content
|
||||||
|
|
||||||
|
@property
|
||||||
|
def page_title(self):
|
||||||
|
return _("%(display_name)s's review of %(book_title)s") % {
|
||||||
|
"display_name": self.user.display_name,
|
||||||
|
"book_title": self.book.title,
|
||||||
|
}
|
||||||
|
|
||||||
activity_serializer = activitypub.Review
|
activity_serializer = activitypub.Review
|
||||||
pure_type = "Article"
|
pure_type = "Article"
|
||||||
|
|
||||||
|
@ -426,6 +473,18 @@ class ReviewRating(Review):
|
||||||
template = get_template("snippets/generated_status/rating.html")
|
template = get_template("snippets/generated_status/rating.html")
|
||||||
return template.render({"book": self.book, "rating": self.rating}).strip()
|
return template.render({"book": self.book, "rating": self.rating}).strip()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def page_description(self):
|
||||||
|
return ngettext_lazy(
|
||||||
|
"%(display_name)s rated %(book_title)s: %(display_rating).1f star",
|
||||||
|
"%(display_name)s rated %(book_title)s: %(display_rating).1f stars",
|
||||||
|
"display_rating",
|
||||||
|
) % {
|
||||||
|
"display_name": self.user.display_name,
|
||||||
|
"book_title": self.book.title,
|
||||||
|
"display_rating": self.rating,
|
||||||
|
}
|
||||||
|
|
||||||
activity_serializer = activitypub.Rating
|
activity_serializer = activitypub.Rating
|
||||||
pure_type = "Note"
|
pure_type = "Note"
|
||||||
|
|
||||||
|
|
|
@ -350,8 +350,7 @@ USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
agent = requests.utils.default_user_agent()
|
USER_AGENT = f"BookWyrm (BookWyrm/{VERSION}; +https://{DOMAIN}/)"
|
||||||
USER_AGENT = f"{agent} (BookWyrm/{VERSION}; +https://{DOMAIN}/)"
|
|
||||||
|
|
||||||
# Imagekit generated thumbnails
|
# Imagekit generated thumbnails
|
||||||
ENABLE_THUMBNAIL_GENERATION = env.bool("ENABLE_THUMBNAIL_GENERATION", False)
|
ENABLE_THUMBNAIL_GENERATION = env.bool("ENABLE_THUMBNAIL_GENERATION", False)
|
||||||
|
|
|
@ -111,6 +111,10 @@ const tries = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
f: {
|
f: {
|
||||||
|
b: {
|
||||||
|
2: "FB2",
|
||||||
|
3: "FB3",
|
||||||
|
},
|
||||||
l: {
|
l: {
|
||||||
a: {
|
a: {
|
||||||
c: "FLAC",
|
c: "FLAC",
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
{% block title %}{{ book|book_title }}{% endblock %}
|
{% block title %}{{ book|book_title }}{% endblock %}
|
||||||
|
|
||||||
{% block opengraph %}
|
{% block opengraph %}
|
||||||
{% include 'snippets/opengraph.html' with title=book.title description=book|book_description image=book.preview_image %}
|
{% firstof book.preview_image book.cover as book_image %}
|
||||||
|
{% include 'snippets/opengraph.html' with title=book.title description=book|book_description image=book_image %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="title">{% trans "Confirm your email address" %}</h1>
|
<h1 class="title">{% trans "Confirm your email address" %}</h1>
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns is-multiline">
|
||||||
<div class="column">
|
<div class="column is-full is-half-desktop">
|
||||||
<div class="block content">
|
<div class="block content">
|
||||||
<section class="block">
|
<section class="block">
|
||||||
<p>{% trans "A confirmation code has been sent to the email address you used to register your account." %}</p>
|
<p>{% trans "A confirmation code has been sent to the email address you used to register your account." %}</p>
|
||||||
|
|
|
@ -2,13 +2,11 @@
|
||||||
{% load feed_page_tags %}
|
{% load feed_page_tags %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block opengraph %}
|
{% block opengraph %}
|
||||||
{% firstof status.book status.mention_books.first as book %}
|
{% include 'snippets/opengraph.html' with image=page_image %}
|
||||||
{% if book %}
|
|
||||||
{% include 'snippets/opengraph.html' with image=preview %}
|
|
||||||
{% else %}
|
|
||||||
{% include 'snippets/opengraph.html' %}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h1 class="title">{% trans "Create an Account" %}</h1>
|
<h1 class="title">{% trans "Create an Account" %}</h1>
|
||||||
<div class="columns">
|
<div class="columns is-multiline">
|
||||||
<div class="column">
|
<div class="column is-full is-half-desktop">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
{% if valid %}
|
{% if valid %}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="title">{% trans "Log in" %}</h1>
|
<h1 class="title">{% trans "Log in" %}</h1>
|
||||||
<div class="columns is-multiline">
|
<div class="columns is-multiline">
|
||||||
<div class="column is-half">
|
<div class="column {% if site.allow_registration %}is-half{% else %}is-full is-half-desktop{% endif %}">
|
||||||
{% if login_form.non_field_errors %}
|
{% if login_form.non_field_errors %}
|
||||||
<p class="notification is-danger">{{ login_form.non_field_errors }}</p>
|
<p class="notification is-danger">{{ login_form.non_field_errors }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -20,13 +20,15 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_localname_confirm">{% trans "Username:" %}</label>
|
<label class="label" for="id_localname_confirm">{% trans "Username:" %}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input type="text" name="localname" maxlength="255" class="input" required="" id="id_localname_confirm" value="{{ login_form.localname.value|default:'' }}">
|
<input type="text" name="localname" maxlength="255" class="input" required=""
|
||||||
|
id="id_localname_confirm" value="{{ login_form.localname.value|default:'' }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_password_confirm">{% trans "Password:" %}</label>
|
<label class="label" for="id_password_confirm">{% trans "Password:" %}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password_confirm" aria-describedby="desc_password">
|
<input type="password" name="password" maxlength="128" class="input" required=""
|
||||||
|
id="id_password_confirm" aria-describedby="desc_password">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=login_form.password.errors id="desc_password" %}
|
{% include 'snippets/form_errors.html' with errors_list=login_form.password.errors id="desc_password" %}
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
{% block title %}{% trans "Reset Password" %}{% endblock %}
|
{% block title %}{% trans "Reset Password" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="columns">
|
<div class="columns is-multiline">
|
||||||
<div class="column">
|
<div class="column is-full is-half-desktop">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h1 class="title">{% trans "Reset Password" %}</h1>
|
<h1 class="title">{% trans "Reset Password" %}</h1>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="title">{% trans "Reactivate Account" %}</h1>
|
<h1 class="title">{% trans "Reactivate Account" %}</h1>
|
||||||
<div class="columns is-multiline">
|
<div class="columns is-multiline">
|
||||||
<div class="column is-half">
|
<div class="column {% if site.allow_registration %}is-half{% else %}is-full is-half-desktop{% endif %}">
|
||||||
{% if login_form.non_field_errors %}
|
{% if login_form.non_field_errors %}
|
||||||
<p class="notification is-danger">{{ login_form.non_field_errors }}</p>
|
<p class="notification is-danger">{{ login_form.non_field_errors }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -16,13 +16,15 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_localname_confirm">{% trans "Username:" %}</label>
|
<label class="label" for="id_localname_confirm">{% trans "Username:" %}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input type="text" name="localname" maxlength="255" class="input" required="" id="id_localname_confirm" value="{{ login_form.localname.value|default:'' }}">
|
<input type="text" name="localname" maxlength="255" class="input" required=""
|
||||||
|
id="id_localname_confirm" value="{{ login_form.localname.value|default:'' }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_password_confirm">{% trans "Password:" %}</label>
|
<label class="label" for="id_password_confirm">{% trans "Password:" %}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password_confirm" aria-describedby="desc_password">
|
<input type="password" name="password" maxlength="128" class="input" required=""
|
||||||
|
id="id_password_confirm" aria-describedby="desc_password">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=login_form.password.errors id="desc_password" %}
|
{% include 'snippets/form_errors.html' with errors_list=login_form.password.errors id="desc_password" %}
|
||||||
|
|
|
@ -45,6 +45,10 @@
|
||||||
{% include 'settings/dashboard/warnings/update_version.html' with warning_level="warning" fullwidth=True %}
|
{% include 'settings/dashboard/warnings/update_version.html' with warning_level="warning" fullwidth=True %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if schedule_form %}
|
||||||
|
{% include 'settings/dashboard/warnings/check_for_updates.html' with warning_level="success" fullwidth=True %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if missing_privacy or missing_conduct %}
|
{% if missing_privacy or missing_conduct %}
|
||||||
<div class="column is-12 columns m-0 p-0">
|
<div class="column is-12 columns m-0 p-0">
|
||||||
{% if missing_privacy %}
|
{% if missing_privacy %}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
{% extends 'settings/dashboard/warnings/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block warning_link %}#{% endblock %}
|
||||||
|
|
||||||
|
{% block warning_text %}
|
||||||
|
|
||||||
|
<form name="check-version" method="POST" action="{% url 'settings-dashboard' %}" class="is-flex is-align-items-center">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<p class="pr-2">
|
||||||
|
{% blocktrans trimmed with current=current_version available=available_version %}
|
||||||
|
Would you like to automatically check for new BookWyrm releases? (recommended)
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{{ schedule_form.every.as_hidden }}
|
||||||
|
{{ schedule_form.period.as_hidden }}
|
||||||
|
|
||||||
|
<button class="button is-small" type="submit">{% trans "Schedule checks" %}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -85,6 +85,10 @@
|
||||||
{% url 'settings-celery' as url %}
|
{% url 'settings-celery' as url %}
|
||||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Celery status" %}</a>
|
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Celery status" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
{% url 'settings-schedules' as url %}
|
||||||
|
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Scheduled tasks" %}</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
{% url 'settings-email-config' as url %}
|
{% url 'settings-email-config' as url %}
|
||||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Email Configuration" %}</a>
|
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Email Configuration" %}</a>
|
||||||
|
|
127
bookwyrm/templates/settings/schedules.html
Normal file
127
bookwyrm/templates/settings/schedules.html
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
{% extends 'settings/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load humanize %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans "Scheduled tasks" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{% trans "Scheduled tasks" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block panel %}
|
||||||
|
|
||||||
|
<div class="block content">
|
||||||
|
<h3>{% trans "Tasks" %}</h3>
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table is-striped is-fullwidth">
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{% trans "Name" %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Celery task" %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Date changed" %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Last run at" %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Schedule" %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Schedule ID" %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Enabled" %}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for task in tasks %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ task.name }}
|
||||||
|
</td>
|
||||||
|
<td class="overflow-wrap-anywhere">
|
||||||
|
{{ task.task }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ task.date_changed }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ task.last_run_at }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% firstof task.interval task.crontab "None" %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ task.interval.id }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="tag">
|
||||||
|
{% if task.enabled %}
|
||||||
|
<span class="icon icon-check" aria-hidden="true"></span>
|
||||||
|
{% endif %}
|
||||||
|
{{ task.enabled|yesno }}
|
||||||
|
</span>
|
||||||
|
{% if task.name != "celery.backend_cleanup" %}
|
||||||
|
<form name="unschedule-{{ task.id }}" method="POST" action="{% url 'settings-schedules' task.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="button is-danger is-small">{% trans "Un-schedule" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
{% trans "No scheduled tasks" %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block content">
|
||||||
|
<h3>{% trans "Schedules" %}</h3>
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table is-striped is-fullwidth">
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{% trans "ID" %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Schedule" %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Tasks" %}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for schedule in schedules %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ schedule.id }}
|
||||||
|
</td>
|
||||||
|
<td class="overflow-wrap-anywhere">
|
||||||
|
{{ schedule }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ schedule.periodictask_set.count }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
{% trans "No schedules found" %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -56,8 +56,7 @@ uuid: a unique identifier used to make html "id" attributes unique and clarify j
|
||||||
<input
|
<input
|
||||||
aria-label="{% if draft.position_mode == 'PG' %}Page{% else %}Percent{% endif %}"
|
aria-label="{% if draft.position_mode == 'PG' %}Page{% else %}Percent{% endif %}"
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="text"
|
||||||
min="0"
|
|
||||||
name="position"
|
name="position"
|
||||||
size="3"
|
size="3"
|
||||||
value="{% firstof draft.position '' %}"
|
value="{% firstof draft.position '' %}"
|
||||||
|
@ -72,8 +71,7 @@ uuid: a unique identifier used to make html "id" attributes unique and clarify j
|
||||||
<input
|
<input
|
||||||
aria-label="{% if draft.position_mode == 'PG' %}Page{% else %}Percent{% endif %}"
|
aria-label="{% if draft.position_mode == 'PG' %}Page{% else %}Percent{% endif %}"
|
||||||
class="input"
|
class="input"
|
||||||
type="number"
|
type="text"
|
||||||
min="0"
|
|
||||||
name="endposition"
|
name="endposition"
|
||||||
size="3"
|
size="3"
|
||||||
value="{% firstof draft.endposition '' %}"
|
value="{% firstof draft.endposition '' %}"
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% if preview_images_enabled is True %}
|
{% firstof image site.preview_image as page_image %}
|
||||||
|
{% if page_image %}
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
{% if image %}
|
<meta name="twitter:image" content="{{ media_full_url }}{{ page_image }}">
|
||||||
<meta name="twitter:image" content="{{ media_full_url }}{{ image }}">
|
<meta name="og:image" content="{{ media_full_url }}{{ page_image }}">
|
||||||
<meta name="og:image" content="{{ media_full_url }}{{ image }}">
|
{% elif site.logo %}
|
||||||
{% else %}
|
<meta name="twitter:card" content="summary">
|
||||||
<meta name="twitter:image" content="{{ media_full_url }}{{ site.preview_image }}">
|
<meta name="twitter:image" content="{{ media_full_url }}{{ site.logo }}">
|
||||||
<meta name="og:image" content="{{ media_full_url }}{{ site.preview_image }}">
|
<meta name="twitter:image:alt" content="{{ site.name }} Logo">
|
||||||
{% endif %}
|
<meta name="og:image" content="{{ media_full_url }}{{ site.logo }}">
|
||||||
{% else %}
|
{% else %}
|
||||||
<meta name="twitter:card" content="summary">
|
<meta name="twitter:card" content="summary">
|
||||||
<meta name="twitter:image" content="{% if site.logo %}{{ media_full_url }}{{ site.logo }}{% else %}{% static "images/logo.png" %}{% endif %}">
|
<meta name="twitter:image" content="{% static "images/logo.png" %}">
|
||||||
<meta name="og:image" content="{% if site.logo %}{{ media_full_url }}{{ site.logo }}{% else %}{% static "images/logo.png" %}{% endif %}">
|
<meta name="twitter:image:alt" content="BookWyrm Logo">
|
||||||
|
<meta name="og:image" content="{% static "images/logo.png" %}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<meta name="twitter:image:alt" content="BookWyrm Logo">
|
|
||||||
|
|
||||||
<meta name="twitter:title" content="{% if title %}{{ title }} - {% endif %}{{ site.name }}">
|
<meta name="twitter:title" content="{% if title %}{{ title }} - {% endif %}{{ site.name }}">
|
||||||
<meta name="og:title" content="{% if title %}{{ title }} - {% endif %}{{ site.name }}">
|
<meta name="og:title" content="{% if title %}{{ title }} - {% endif %}{{ site.name }}">
|
||||||
|
|
||||||
<meta name="twitter:description" content="{% if description %}{{ description }}{% else %}{{ site.instance_tagline }}{% endif %}">
|
{% firstof description site.instance_tagline as description %}
|
||||||
<meta name="og:description" content="{% if description %}{{ description }}{% else %}{{ site.instance_tagline }}{% endif %}">
|
<meta name="twitter:description" content="{{ description }}">
|
||||||
|
<meta name="og:description" content="{{ description }}">
|
||||||
|
|
|
@ -227,14 +227,18 @@ class ActivitypubMixins(TestCase):
|
||||||
shared_inbox="http://example.com/inbox",
|
shared_inbox="http://example.com/inbox",
|
||||||
outbox="https://example.com/users/nutria/outbox",
|
outbox="https://example.com/users/nutria/outbox",
|
||||||
)
|
)
|
||||||
MockSelf = namedtuple("Self", ("privacy", "user"))
|
MockSelf = namedtuple("Self", ("privacy", "user", "recipients"))
|
||||||
mock_self = MockSelf("public", self.local_user)
|
|
||||||
self.local_user.followers.add(self.remote_user)
|
self.local_user.followers.add(self.remote_user)
|
||||||
self.local_user.followers.add(another_remote_user)
|
self.local_user.followers.add(another_remote_user)
|
||||||
|
|
||||||
|
mock_self = MockSelf("public", self.local_user, [])
|
||||||
recipients = ActivitypubMixin.get_recipients(mock_self)
|
recipients = ActivitypubMixin.get_recipients(mock_self)
|
||||||
self.assertEqual(len(recipients), 1)
|
self.assertCountEqual(recipients, ["http://example.com/inbox"])
|
||||||
self.assertEqual(recipients[0], "http://example.com/inbox")
|
|
||||||
|
# should also work with recipient that is a follower
|
||||||
|
mock_self.recipients.append(another_remote_user)
|
||||||
|
recipients = ActivitypubMixin.get_recipients(mock_self)
|
||||||
|
self.assertCountEqual(recipients, ["http://example.com/inbox"])
|
||||||
|
|
||||||
def test_get_recipients_software(self, *_):
|
def test_get_recipients_software(self, *_):
|
||||||
"""should differentiate between bookwyrm and other remote users"""
|
"""should differentiate between bookwyrm and other remote users"""
|
||||||
|
|
|
@ -272,8 +272,8 @@ class BookViews(TestCase):
|
||||||
book=self.book,
|
book=self.book,
|
||||||
content="hi",
|
content="hi",
|
||||||
quote="wow",
|
quote="wow",
|
||||||
position=12,
|
position="12",
|
||||||
endposition=13,
|
endposition="13",
|
||||||
)
|
)
|
||||||
|
|
||||||
request = self.factory.get("")
|
request = self.factory.get("")
|
||||||
|
@ -286,7 +286,9 @@ class BookViews(TestCase):
|
||||||
validate_html(result.render())
|
validate_html(result.render())
|
||||||
print(result.render())
|
print(result.render())
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
self.assertEqual(result.context_data["statuses"].object_list[0].endposition, 13)
|
self.assertEqual(
|
||||||
|
result.context_data["statuses"].object_list[0].endposition, "13"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _setup_cover_url():
|
def _setup_cover_url():
|
||||||
|
|
|
@ -369,6 +369,11 @@ urlpatterns = [
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/celery/ping/?$", views.celery_ping, name="settings-celery-ping"
|
r"^settings/celery/ping/?$", views.celery_ping, name="settings-celery-ping"
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
r"^settings/schedules/(?P<task_id>\d+)?$",
|
||||||
|
views.ScheduledTasks.as_view(),
|
||||||
|
name="settings-schedules",
|
||||||
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^settings/email-config/?$",
|
r"^settings/email-config/?$",
|
||||||
views.EmailConfig.as_view(),
|
views.EmailConfig.as_view(),
|
||||||
|
|
|
@ -5,6 +5,7 @@ from .admin.announcements import EditAnnouncement, delete_announcement
|
||||||
from .admin.automod import AutoMod, automod_delete, run_automod
|
from .admin.automod import AutoMod, automod_delete, run_automod
|
||||||
from .admin.automod import schedule_automod_task, unschedule_automod_task
|
from .admin.automod import schedule_automod_task, unschedule_automod_task
|
||||||
from .admin.celery_status import CeleryStatus, celery_ping
|
from .admin.celery_status import CeleryStatus, celery_ping
|
||||||
|
from .admin.schedule import ScheduledTasks
|
||||||
from .admin.dashboard import Dashboard
|
from .admin.dashboard import Dashboard
|
||||||
from .admin.federation import Federation, FederatedServer
|
from .admin.federation import Federation, FederatedServer
|
||||||
from .admin.federation import AddFederatedServer, ImportServerBlocklist
|
from .admin.federation import AddFederatedServer, ImportServerBlocklist
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.template.response import TemplateResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
from django_celery_beat.models import PeriodicTask
|
from django_celery_beat.models import PeriodicTask, IntervalSchedule
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ def schedule_automod_task(request):
|
||||||
return TemplateResponse(request, "settings/automod/rules.html", data)
|
return TemplateResponse(request, "settings/automod/rules.html", data)
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
schedule = form.save(request)
|
schedule, _ = IntervalSchedule.objects.get_or_create(**form.cleaned_data)
|
||||||
PeriodicTask.objects.get_or_create(
|
PeriodicTask.objects.get_or_create(
|
||||||
interval=schedule,
|
interval=schedule,
|
||||||
name="automod-task",
|
name="automod-task",
|
||||||
|
|
|
@ -6,16 +6,18 @@ from dateutil.parser import parse
|
||||||
from packaging import version
|
from packaging import version
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.shortcuts import redirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
from django_celery_beat.models import PeriodicTask, IntervalSchedule
|
||||||
|
|
||||||
from csp.decorators import csp_update
|
from csp.decorators import csp_update
|
||||||
|
|
||||||
from bookwyrm import models, settings
|
from bookwyrm import forms, models, settings
|
||||||
from bookwyrm.connectors.abstract_connector import get_data
|
|
||||||
from bookwyrm.utils import regex
|
from bookwyrm.utils import regex
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,21 +61,36 @@ class Dashboard(View):
|
||||||
== site._meta.get_field("privacy_policy").get_default()
|
== site._meta.get_field("privacy_policy").get_default()
|
||||||
)
|
)
|
||||||
|
|
||||||
# check version
|
if site.available_version and version.parse(
|
||||||
|
site.available_version
|
||||||
try:
|
) > version.parse(settings.VERSION):
|
||||||
release = get_data(settings.RELEASE_API, timeout=3)
|
|
||||||
available_version = release.get("tag_name", None)
|
|
||||||
if available_version and version.parse(available_version) > version.parse(
|
|
||||||
settings.VERSION
|
|
||||||
):
|
|
||||||
data["current_version"] = settings.VERSION
|
data["current_version"] = settings.VERSION
|
||||||
data["available_version"] = available_version
|
data["available_version"] = site.available_version
|
||||||
except: # pylint: disable= bare-except
|
|
||||||
pass
|
if not PeriodicTask.objects.filter(name="check-for-updates").exists():
|
||||||
|
data["schedule_form"] = forms.IntervalScheduleForm(
|
||||||
|
{"every": 1, "period": "days"}
|
||||||
|
)
|
||||||
|
|
||||||
return TemplateResponse(request, "settings/dashboard/dashboard.html", data)
|
return TemplateResponse(request, "settings/dashboard/dashboard.html", data)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
"""Create a schedule task to check for updates"""
|
||||||
|
schedule_form = forms.IntervalScheduleForm(request.POST)
|
||||||
|
if not schedule_form.is_valid():
|
||||||
|
raise schedule_form.ValidationError(schedule_form.errors)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
schedule, _ = IntervalSchedule.objects.get_or_create(
|
||||||
|
**schedule_form.cleaned_data
|
||||||
|
)
|
||||||
|
PeriodicTask.objects.get_or_create(
|
||||||
|
interval=schedule,
|
||||||
|
name="check-for-updates",
|
||||||
|
task="bookwyrm.models.site.check_for_updates_task",
|
||||||
|
)
|
||||||
|
return redirect("settings-dashboard")
|
||||||
|
|
||||||
|
|
||||||
def get_charts_and_stats(request):
|
def get_charts_and_stats(request):
|
||||||
"""Defines the dashboard charts"""
|
"""Defines the dashboard charts"""
|
||||||
|
|
31
bookwyrm/views/admin/schedule.py
Normal file
31
bookwyrm/views/admin/schedule.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
""" Scheduled celery tasks """
|
||||||
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
from django_celery_beat.models import PeriodicTask, IntervalSchedule
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name="dispatch")
|
||||||
|
@method_decorator(
|
||||||
|
permission_required("bookwyrm.edit_instance_settings", raise_exception=True),
|
||||||
|
name="dispatch",
|
||||||
|
)
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
class ScheduledTasks(View):
|
||||||
|
"""Manage automated flagging"""
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
"""view schedules"""
|
||||||
|
data = {}
|
||||||
|
data["tasks"] = PeriodicTask.objects.all()
|
||||||
|
data["schedules"] = IntervalSchedule.objects.all()
|
||||||
|
return TemplateResponse(request, "settings/schedules.html", data)
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def post(self, request, task_id):
|
||||||
|
"""un-schedule a task"""
|
||||||
|
task = PeriodicTask.objects.get(id=task_id)
|
||||||
|
task.delete()
|
||||||
|
return redirect("settings-schedules")
|
|
@ -200,19 +200,15 @@ class Status(View):
|
||||||
params=[status.id, visible_thread, visible_thread],
|
params=[status.id, visible_thread, visible_thread],
|
||||||
)
|
)
|
||||||
|
|
||||||
preview = None
|
|
||||||
if hasattr(status, "book"):
|
|
||||||
preview = status.book.preview_image
|
|
||||||
elif status.mention_books.exists():
|
|
||||||
preview = status.mention_books.first().preview_image
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
**feed_page_data(request.user),
|
**feed_page_data(request.user),
|
||||||
**{
|
**{
|
||||||
"status": status,
|
"status": status,
|
||||||
"children": children,
|
"children": children,
|
||||||
"ancestors": ancestors,
|
"ancestors": ancestors,
|
||||||
"preview": preview,
|
"title": status.page_title,
|
||||||
|
"description": status.page_description,
|
||||||
|
"page_image": status.page_image,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, "feed/status.html", data)
|
return TemplateResponse(request, "feed/status.html", data)
|
||||||
|
|
37
update.sh
37
update.sh
|
@ -1,37 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# determine inital and target versions
|
|
||||||
initial_version="`./bw-dev runweb python manage.py instance_version --current`"
|
|
||||||
target_version="`./bw-dev runweb python manage.py instance_version --target`"
|
|
||||||
|
|
||||||
initial_version="`echo $initial_version | tail -n 1 | xargs`"
|
|
||||||
target_version="`echo $target_version | tail -n 1 | xargs`"
|
|
||||||
if [[ "$initial_version" = "$target_version" ]]; then
|
|
||||||
echo "Already up to date; version $initial_version"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "---------------------------------------"
|
|
||||||
echo "Updating from version: $initial_version"
|
|
||||||
echo ".......... to version: $target_version"
|
|
||||||
echo "---------------------------------------"
|
|
||||||
|
|
||||||
function version_gt() { test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"; }
|
|
||||||
|
|
||||||
# execute scripts between initial and target
|
|
||||||
for version in `ls -A updates/ | sort -V `; do
|
|
||||||
if version_gt $initial_version $version; then
|
|
||||||
# too early
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if version_gt $version $target_version; then
|
|
||||||
# too late
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
echo "Running tasks for version $version"
|
|
||||||
./updates/$version
|
|
||||||
done
|
|
||||||
|
|
||||||
./bw-dev runweb python manage.py instance_version --update
|
|
||||||
echo "✨ ----------- Done! --------------- ✨"
|
|
Loading…
Reference in a new issue