Merge remote-tracking branch 'upstream/main' into images-django-imagekit
This commit is contained in:
commit
e251b687dc
171 changed files with 3674 additions and 2175 deletions
|
@ -2,11 +2,14 @@
|
|||
import re
|
||||
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from model_utils import FieldTracker
|
||||
from model_utils.managers import InheritanceManager
|
||||
from imagekit.models import ImageSpecField
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.settings import DOMAIN, DEFAULT_LANGUAGE, ENABLE_THUMBNAIL_GENERATION
|
||||
from bookwyrm.preview_images import generate_edition_preview_image_task
|
||||
from bookwyrm.settings import DOMAIN, DEFAULT_LANGUAGE, ENABLE_PREVIEW_IMAGES, ENABLE_THUMBNAIL_GENERATION
|
||||
|
||||
from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin
|
||||
from .base_model import BookWyrmModel
|
||||
|
@ -83,10 +86,14 @@ class Book(BookDataModel):
|
|||
cover = fields.ImageField(
|
||||
upload_to="covers/", blank=True, null=True, alt_field="alt_text"
|
||||
)
|
||||
preview_image = models.ImageField(
|
||||
upload_to="previews/covers/", blank=True, null=True
|
||||
)
|
||||
first_published_date = fields.DateTimeField(blank=True, null=True)
|
||||
published_date = fields.DateTimeField(blank=True, null=True)
|
||||
|
||||
objects = InheritanceManager()
|
||||
field_tracker = FieldTracker(fields=["authors", "title", "subtitle", "cover"])
|
||||
|
||||
if ENABLE_THUMBNAIL_GENERATION:
|
||||
cover_bw_book_xsmall_webp = ImageSpecField(
|
||||
|
@ -328,3 +335,17 @@ def isbn_13_to_10(isbn_13):
|
|||
if checkdigit == 10:
|
||||
checkdigit = "X"
|
||||
return converted + str(checkdigit)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@receiver(models.signals.post_save, sender=Edition)
|
||||
def preview_image(instance, *args, **kwargs):
|
||||
"""create preview image on book create"""
|
||||
if not ENABLE_PREVIEW_IMAGES:
|
||||
return
|
||||
changed_fields = {}
|
||||
if instance.field_tracker:
|
||||
changed_fields = instance.field_tracker.changed()
|
||||
|
||||
if len(changed_fields) > 0:
|
||||
generate_edition_preview_image_task.delay(instance.id)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
""" activitypub-aware django model fields """
|
||||
from dataclasses import MISSING
|
||||
import imghdr
|
||||
import re
|
||||
from uuid import uuid4
|
||||
|
||||
|
@ -9,7 +10,7 @@ from django.contrib.postgres.fields import ArrayField as DjangoArrayField
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import models
|
||||
from django.forms import ClearableFileInput, ImageField
|
||||
from django.forms import ClearableFileInput, ImageField as DjangoImageField
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from bookwyrm import activitypub
|
||||
|
@ -201,6 +202,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
|
|||
*args, max_length=255, choices=PrivacyLevels.choices, default="public"
|
||||
)
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def set_field_from_activity(self, instance, data):
|
||||
to = data.to
|
||||
cc = data.cc
|
||||
|
@ -219,6 +221,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
|
|||
if hasattr(instance, "mention_users"):
|
||||
mentions = [u.remote_id for u in instance.mention_users.all()]
|
||||
# this is a link to the followers list
|
||||
# pylint: disable=protected-access
|
||||
followers = instance.user.__class__._meta.get_field(
|
||||
"followers"
|
||||
).field_to_activity(instance.user.followers)
|
||||
|
@ -334,10 +337,14 @@ class TagField(ManyToManyField):
|
|||
|
||||
|
||||
class ClearableFileInputWithWarning(ClearableFileInput):
|
||||
"""max file size warning"""
|
||||
|
||||
template_name = "widgets/clearable_file_input_with_warning.html"
|
||||
|
||||
|
||||
class CustomImageField(ImageField):
|
||||
class CustomImageField(DjangoImageField):
|
||||
"""overwrites image field for form"""
|
||||
|
||||
widget = ClearableFileInputWithWarning
|
||||
|
||||
|
||||
|
@ -400,11 +407,12 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
|
|||
if not response:
|
||||
return None
|
||||
|
||||
image_name = str(uuid4()) + "." + url.split(".")[-1]
|
||||
image_content = ContentFile(response.content)
|
||||
image_name = str(uuid4()) + "." + imghdr.what(None, image_content.read())
|
||||
return [image_name, image_content]
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
"""special case for forms"""
|
||||
return super().formfield(
|
||||
**{
|
||||
"form_class": CustomImageField,
|
||||
|
|
|
@ -75,7 +75,12 @@ class ImportItem(models.Model):
|
|||
|
||||
def resolve(self):
|
||||
"""try various ways to lookup a book"""
|
||||
self.book = self.get_book_from_isbn() or self.get_book_from_title_author()
|
||||
if self.isbn:
|
||||
self.book = self.get_book_from_isbn()
|
||||
else:
|
||||
# don't fall back on title/author search is isbn is present.
|
||||
# you're too likely to mismatch
|
||||
self.get_book_from_title_author()
|
||||
|
||||
def get_book_from_isbn(self):
|
||||
"""search by isbn"""
|
||||
|
|
|
@ -93,7 +93,8 @@ class ListItem(CollectionItemMixin, BookWyrmModel):
|
|||
)
|
||||
|
||||
class Meta:
|
||||
# A book may only be placed into a list once, and each order in the list may be used only
|
||||
# once
|
||||
"""A book may only be placed into a list once,
|
||||
and each order in the list may be used only once"""
|
||||
|
||||
unique_together = (("book", "book_list"), ("order", "book_list"))
|
||||
ordering = ("-created_date",)
|
||||
|
|
|
@ -99,7 +99,7 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
|
|||
status = "follow_request"
|
||||
activity_serializer = activitypub.Follow
|
||||
|
||||
def save(self, *args, broadcast=True, **kwargs):
|
||||
def save(self, *args, broadcast=True, **kwargs): # pylint: disable=arguments-differ
|
||||
"""make sure the follow or block relationship doesn't already exist"""
|
||||
# if there's a request for a follow that already exists, accept it
|
||||
# without changing the local database state
|
||||
|
|
|
@ -4,9 +4,12 @@ import datetime
|
|||
|
||||
from Crypto import Random
|
||||
from django.db import models, IntegrityError
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
from model_utils import FieldTracker
|
||||
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from bookwyrm.preview_images import generate_site_preview_image_task
|
||||
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES
|
||||
from .base_model import BookWyrmModel
|
||||
from .user import User
|
||||
|
||||
|
@ -35,6 +38,9 @@ class SiteSettings(models.Model):
|
|||
logo = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||
logo_small = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||
favicon = models.ImageField(upload_to="logos/", null=True, blank=True)
|
||||
preview_image = models.ImageField(
|
||||
upload_to="previews/logos/", null=True, blank=True
|
||||
)
|
||||
|
||||
# footer
|
||||
support_link = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
@ -42,6 +48,8 @@ class SiteSettings(models.Model):
|
|||
admin_email = models.EmailField(max_length=255, null=True, blank=True)
|
||||
footer_item = models.TextField(null=True, blank=True)
|
||||
|
||||
field_tracker = FieldTracker(fields=["name", "instance_tagline", "logo"])
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
"""gets the site settings db entry or defaults"""
|
||||
|
@ -119,3 +127,15 @@ class PasswordReset(models.Model):
|
|||
def link(self):
|
||||
"""formats the invite link"""
|
||||
return "https://{}/password-reset/{}".format(DOMAIN, self.code)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@receiver(models.signals.post_save, sender=SiteSettings)
|
||||
def preview_image(instance, *args, **kwargs):
|
||||
"""Update image preview for the default site image"""
|
||||
if not ENABLE_PREVIEW_IMAGES:
|
||||
return
|
||||
changed_fields = instance.field_tracker.changed()
|
||||
|
||||
if len(changed_fields) > 0:
|
||||
generate_site_preview_image_task.delay()
|
||||
|
|
|
@ -5,11 +5,15 @@ import re
|
|||
from django.apps import apps
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.template.loader import get_template
|
||||
from django.utils import timezone
|
||||
from model_utils import FieldTracker
|
||||
from model_utils.managers import InheritanceManager
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.preview_images import generate_edition_preview_image_task
|
||||
from bookwyrm.settings import ENABLE_PREVIEW_IMAGES
|
||||
from .activitypub_mixin import ActivitypubMixin, ActivityMixin
|
||||
from .activitypub_mixin import OrderedCollectionPageMixin
|
||||
from .base_model import BookWyrmModel
|
||||
|
@ -304,6 +308,8 @@ class Review(Status):
|
|||
max_digits=3,
|
||||
)
|
||||
|
||||
field_tracker = FieldTracker(fields=["rating"])
|
||||
|
||||
@property
|
||||
def pure_name(self):
|
||||
"""clarify review names for mastodon serialization"""
|
||||
|
@ -398,3 +404,17 @@ class Boost(ActivityMixin, Status):
|
|||
# This constraint can't work as it would cross tables.
|
||||
# class Meta:
|
||||
# unique_together = ('user', 'boosted_status')
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@receiver(models.signals.post_save)
|
||||
def preview_image(instance, sender, *args, **kwargs):
|
||||
"""Updates book previews if the rating has changed"""
|
||||
if not ENABLE_PREVIEW_IMAGES or sender not in (Review, ReviewRating):
|
||||
return
|
||||
|
||||
changed_fields = instance.field_tracker.changed()
|
||||
|
||||
if len(changed_fields) > 0:
|
||||
edition = instance.book
|
||||
generate_edition_preview_image_task.delay(edition.id)
|
||||
|
|
|
@ -6,15 +6,18 @@ from django.apps import apps
|
|||
from django.contrib.auth.models import AbstractUser, Group
|
||||
from django.contrib.postgres.fields import CICharField
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.dispatch import receiver
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from model_utils import FieldTracker
|
||||
import pytz
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.connectors import get_data, ConnectorException
|
||||
from bookwyrm.models.shelf import Shelf
|
||||
from bookwyrm.models.status import Status, Review
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from bookwyrm.preview_images import generate_user_preview_image_task
|
||||
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES
|
||||
from bookwyrm.signatures import create_key_pair
|
||||
from bookwyrm.tasks import app
|
||||
from bookwyrm.utils import regex
|
||||
|
@ -70,6 +73,9 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
activitypub_field="icon",
|
||||
alt_field="alt_text",
|
||||
)
|
||||
preview_image = models.ImageField(
|
||||
upload_to="previews/avatars/", blank=True, null=True
|
||||
)
|
||||
followers = fields.ManyToManyField(
|
||||
"self",
|
||||
link_only=True,
|
||||
|
@ -117,6 +123,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
|
||||
name_field = "username"
|
||||
property_fields = [("following_link", "following")]
|
||||
field_tracker = FieldTracker(fields=["name", "avatar"])
|
||||
|
||||
@property
|
||||
def following_link(self):
|
||||
|
@ -232,7 +239,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
def save(self, *args, **kwargs):
|
||||
"""populate fields for new local users"""
|
||||
created = not bool(self.id)
|
||||
if not self.local and not re.match(regex.full_username, self.username):
|
||||
if not self.local and not re.match(regex.FULL_USERNAME, self.username):
|
||||
# generate a username that uses the domain (webfinger format)
|
||||
actor_parts = urlparse(self.remote_id)
|
||||
self.username = "%s@%s" % (self.username, actor_parts.netloc)
|
||||
|
@ -356,7 +363,7 @@ class AnnualGoal(BookWyrmModel):
|
|||
|
||||
def get_remote_id(self):
|
||||
"""put the year in the path"""
|
||||
return "%s/goal/%d" % (self.user.remote_id, self.year)
|
||||
return "{:s}/goal/{:d}".format(self.user.remote_id, self.year)
|
||||
|
||||
@property
|
||||
def books(self):
|
||||
|
@ -443,3 +450,15 @@ def get_remote_reviews(outbox):
|
|||
if not activity["type"] == "Review":
|
||||
continue
|
||||
activitypub.Review(**activity).to_model()
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@receiver(models.signals.post_save, sender=User)
|
||||
def preview_image(instance, *args, **kwargs):
|
||||
"""create preview images when user is updated"""
|
||||
if not ENABLE_PREVIEW_IMAGES:
|
||||
return
|
||||
changed_fields = instance.field_tracker.changed()
|
||||
|
||||
if len(changed_fields) > 0:
|
||||
generate_user_preview_image_task.delay(instance.id)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue