mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-05-18 00:12:59 +00:00
d640e4ac96
- new setting to enable user exports defaults to False - add setting to enable and disable user exports - do not allow user exports when using s3 storage - do not serve non-image files from /images/ (requires update to nginx settings) - increase default file upload limit to 100MB to enable user exports to be imported (can be changed in .env)
248 lines
8.9 KiB
Python
248 lines
8.9 KiB
Python
""" the particulars for this instance of BookWyrm """
|
|
import datetime
|
|
from urllib.parse import urljoin
|
|
import uuid
|
|
|
|
import django.contrib.auth.models as auth_models
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.db import models, IntegrityError
|
|
from django.dispatch import receiver
|
|
from django.utils import timezone
|
|
from model_utils import FieldTracker
|
|
|
|
from bookwyrm.preview_images import generate_site_preview_image_task
|
|
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES, STATIC_FULL_URL
|
|
from .base_model import BookWyrmModel, new_access_code
|
|
from .user import User
|
|
from .fields import get_absolute_url
|
|
|
|
|
|
class SiteModel(models.Model):
|
|
"""we just need edit perms"""
|
|
|
|
class Meta:
|
|
"""this is just here to provide default fields for other models"""
|
|
|
|
abstract = True
|
|
|
|
# pylint: disable=no-self-use
|
|
def raise_not_editable(self, viewer):
|
|
"""Check if the user has the right permissions"""
|
|
if viewer.has_perm("bookwyrm.edit_instance_settings"):
|
|
return
|
|
raise PermissionDenied()
|
|
|
|
|
|
class SiteSettings(SiteModel):
|
|
"""customized settings for this instance"""
|
|
|
|
name = models.CharField(default="BookWyrm", max_length=100)
|
|
instance_tagline = models.CharField(
|
|
max_length=150, default="Social Reading and Reviewing"
|
|
)
|
|
instance_description = models.TextField(default="This instance has no description.")
|
|
instance_short_description = models.CharField(max_length=255, blank=True, null=True)
|
|
default_theme = models.ForeignKey(
|
|
"Theme", null=True, blank=True, on_delete=models.SET_NULL
|
|
)
|
|
version = models.CharField(null=True, blank=True, max_length=10)
|
|
|
|
# admin setup options
|
|
install_mode = models.BooleanField(default=False)
|
|
admin_code = models.CharField(max_length=50, default=uuid.uuid4)
|
|
|
|
# about page
|
|
registration_closed_text = models.TextField(
|
|
default="We aren't taking new users at this time. You can find an open "
|
|
'instance at <a href="https://joinbookwyrm.com/instances">'
|
|
"joinbookwyrm.com/instances</a>."
|
|
)
|
|
invite_request_text = models.TextField(
|
|
default="If your request is approved, you will receive an email with a "
|
|
"registration link."
|
|
)
|
|
code_of_conduct = models.TextField(default="Add a code of conduct here.")
|
|
privacy_policy = models.TextField(default="Add a privacy policy here.")
|
|
impressum = models.TextField(default="Add a impressum here.")
|
|
show_impressum = models.BooleanField(default=False)
|
|
|
|
# registration
|
|
allow_registration = models.BooleanField(default=False)
|
|
allow_invite_requests = models.BooleanField(default=True)
|
|
invite_request_question = models.BooleanField(default=False)
|
|
require_confirm_email = models.BooleanField(default=True)
|
|
default_user_auth_group = models.ForeignKey(
|
|
auth_models.Group, null=True, blank=True, on_delete=models.RESTRICT
|
|
)
|
|
|
|
invite_question_text = models.CharField(
|
|
max_length=255, blank=True, default="What is your favourite book?"
|
|
)
|
|
# images
|
|
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)
|
|
support_title = models.CharField(max_length=100, null=True, blank=True)
|
|
admin_email = models.EmailField(max_length=255, null=True, blank=True)
|
|
footer_item = models.TextField(null=True, blank=True)
|
|
|
|
# controls
|
|
imports_enabled = models.BooleanField(default=True)
|
|
import_size_limit = models.IntegerField(default=0)
|
|
import_limit_reset = models.IntegerField(default=0)
|
|
user_exports_enabled = models.BooleanField(default=False)
|
|
user_import_time_limit = models.IntegerField(default=48)
|
|
|
|
field_tracker = FieldTracker(fields=["name", "instance_tagline", "logo"])
|
|
|
|
@classmethod
|
|
def get(cls):
|
|
"""gets the site settings db entry or defaults"""
|
|
try:
|
|
return cls.objects.get(id=1)
|
|
except cls.DoesNotExist:
|
|
default_settings = SiteSettings(id=1)
|
|
default_settings.save()
|
|
return default_settings
|
|
|
|
@property
|
|
def logo_url(self):
|
|
"""helper to build the logo url"""
|
|
return self.get_url("logo", "images/logo.png")
|
|
|
|
@property
|
|
def logo_small_url(self):
|
|
"""helper to build the logo url"""
|
|
return self.get_url("logo_small", "images/logo-small.png")
|
|
|
|
@property
|
|
def favicon_url(self):
|
|
"""helper to build the logo url"""
|
|
return self.get_url("favicon", "images/favicon.png")
|
|
|
|
def get_url(self, field, default_path):
|
|
"""get a media url or a default static path"""
|
|
uploaded = getattr(self, field, None)
|
|
if uploaded:
|
|
return get_absolute_url(uploaded)
|
|
return urljoin(STATIC_FULL_URL, default_path)
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""if require_confirm_email is disabled, make sure no users are pending,
|
|
if enabled, make sure invite_question_text is not empty"""
|
|
if not self.require_confirm_email:
|
|
User.objects.filter(is_active=False, deactivation_reason="pending").update(
|
|
is_active=True, deactivation_reason=None
|
|
)
|
|
if not self.invite_question_text:
|
|
self.invite_question_text = "What is your favourite book?"
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
class Theme(SiteModel):
|
|
"""Theme files"""
|
|
|
|
created_date = models.DateTimeField(auto_now_add=True)
|
|
name = models.CharField(max_length=50, unique=True)
|
|
path = models.CharField(max_length=50, unique=True)
|
|
loads = models.BooleanField(null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
# pylint: disable=invalid-str-returned
|
|
return self.name
|
|
|
|
|
|
class SiteInvite(models.Model):
|
|
"""gives someone access to create an account on the instance"""
|
|
|
|
created_date = models.DateTimeField(auto_now_add=True)
|
|
code = models.CharField(max_length=32, default=new_access_code)
|
|
expiry = models.DateTimeField(blank=True, null=True)
|
|
use_limit = models.IntegerField(blank=True, null=True)
|
|
times_used = models.IntegerField(default=0)
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
invitees = models.ManyToManyField(User, related_name="invitees")
|
|
|
|
# pylint: disable=no-self-use
|
|
def raise_not_editable(self, viewer):
|
|
"""Admins only"""
|
|
if viewer.has_perm("bookwyrm.create_invites"):
|
|
return
|
|
raise PermissionDenied()
|
|
|
|
def valid(self):
|
|
"""make sure it hasn't expired or been used"""
|
|
return (self.expiry is None or self.expiry > timezone.now()) and (
|
|
self.use_limit is None or self.times_used < self.use_limit
|
|
)
|
|
|
|
@property
|
|
def link(self):
|
|
"""formats the invite link"""
|
|
return f"https://{DOMAIN}/invite/{self.code}"
|
|
|
|
|
|
class InviteRequest(BookWyrmModel):
|
|
"""prospective users can request an invite"""
|
|
|
|
email = models.EmailField(max_length=255, unique=True)
|
|
invite = models.ForeignKey(
|
|
SiteInvite, on_delete=models.SET_NULL, null=True, blank=True
|
|
)
|
|
answer = models.TextField(max_length=255, unique=False, null=True, blank=True)
|
|
invite_sent = models.BooleanField(default=False)
|
|
ignored = models.BooleanField(default=False)
|
|
|
|
def raise_not_editable(self, viewer):
|
|
"""Only check perms on edit, not create"""
|
|
if not self.id or viewer.has_perm("bookwyrm.create_invites"):
|
|
return
|
|
raise PermissionDenied()
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""don't create a request for a registered email"""
|
|
if not self.id and User.objects.filter(email=self.email).exists():
|
|
raise IntegrityError()
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
def get_password_reset_expiry():
|
|
"""give people a limited time to use the link"""
|
|
now = timezone.now()
|
|
return now + datetime.timedelta(days=1)
|
|
|
|
|
|
class PasswordReset(models.Model):
|
|
"""gives someone access to create an account on the instance"""
|
|
|
|
code = models.CharField(max_length=32, default=new_access_code)
|
|
expiry = models.DateTimeField(default=get_password_reset_expiry)
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
|
|
|
def valid(self):
|
|
"""make sure it hasn't expired or been used"""
|
|
return self.expiry > timezone.now()
|
|
|
|
@property
|
|
def link(self):
|
|
"""formats the invite link"""
|
|
return f"https://{DOMAIN}/password-reset/{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()
|