Merge branch 'main' into run-not-exec
This commit is contained in:
commit
ec63ca3817
|
@ -28,10 +28,14 @@ MAX_STREAM_LENGTH=200
|
||||||
REDIS_ACTIVITY_HOST=redis_activity
|
REDIS_ACTIVITY_HOST=redis_activity
|
||||||
REDIS_ACTIVITY_PORT=6379
|
REDIS_ACTIVITY_PORT=6379
|
||||||
REDIS_ACTIVITY_PASSWORD=redispassword345
|
REDIS_ACTIVITY_PASSWORD=redispassword345
|
||||||
|
# Optional, use a different redis database (defaults to 0)
|
||||||
|
# REDIS_ACTIVITY_DB_INDEX=0
|
||||||
|
|
||||||
# Redis as celery broker
|
# Redis as celery broker
|
||||||
REDIS_BROKER_PORT=6379
|
REDIS_BROKER_PORT=6379
|
||||||
REDIS_BROKER_PASSWORD=redispassword123
|
REDIS_BROKER_PASSWORD=redispassword123
|
||||||
|
# Optional, use a different redis database (defaults to 0)
|
||||||
|
# REDIS_BROKER_DB_INDEX=0
|
||||||
|
|
||||||
# Monitoring for celery
|
# Monitoring for celery
|
||||||
FLOWER_PORT=8888
|
FLOWER_PORT=8888
|
||||||
|
|
|
@ -20,22 +20,6 @@ class ActivityEncoder(JSONEncoder):
|
||||||
return o.__dict__
|
return o.__dict__
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Link:
|
|
||||||
"""for tagging a book in a status"""
|
|
||||||
|
|
||||||
href: str
|
|
||||||
name: str
|
|
||||||
type: str = "Link"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Mention(Link):
|
|
||||||
"""a subtype of Link for mentioning an actor"""
|
|
||||||
|
|
||||||
type: str = "Mention"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
class Signature:
|
class Signature:
|
||||||
|
@ -198,8 +182,9 @@ class ActivityObject:
|
||||||
)
|
)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self, **kwargs):
|
||||||
"""convert to dictionary with context attr"""
|
"""convert to dictionary with context attr"""
|
||||||
|
omit = kwargs.get("omit", ())
|
||||||
data = self.__dict__.copy()
|
data = self.__dict__.copy()
|
||||||
# recursively serialize
|
# recursively serialize
|
||||||
for (k, v) in data.items():
|
for (k, v) in data.items():
|
||||||
|
@ -208,8 +193,9 @@ class ActivityObject:
|
||||||
data[k] = v.serialize()
|
data[k] = v.serialize()
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
data = {k: v for (k, v) in data.items() if v is not None}
|
data = {k: v for (k, v) in data.items() if v is not None and k not in omit}
|
||||||
data["@context"] = "https://www.w3.org/ns/activitystreams"
|
if "@context" not in omit:
|
||||||
|
data["@context"] = "https://www.w3.org/ns/activitystreams"
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@ -222,35 +208,32 @@ def set_related_field(
|
||||||
model = apps.get_model(f"bookwyrm.{model_name}", require_ready=True)
|
model = apps.get_model(f"bookwyrm.{model_name}", require_ready=True)
|
||||||
origin_model = apps.get_model(f"bookwyrm.{origin_model_name}", require_ready=True)
|
origin_model = apps.get_model(f"bookwyrm.{origin_model_name}", require_ready=True)
|
||||||
|
|
||||||
with transaction.atomic():
|
if isinstance(data, str):
|
||||||
if isinstance(data, str):
|
existing = model.find_existing_by_remote_id(data)
|
||||||
existing = model.find_existing_by_remote_id(data)
|
if existing:
|
||||||
if existing:
|
data = existing.to_activity()
|
||||||
data = existing.to_activity()
|
else:
|
||||||
else:
|
data = get_data(data)
|
||||||
data = get_data(data)
|
activity = model.activity_serializer(**data)
|
||||||
activity = model.activity_serializer(**data)
|
|
||||||
|
|
||||||
# this must exist because it's the object that triggered this function
|
# this must exist because it's the object that triggered this function
|
||||||
instance = origin_model.find_existing_by_remote_id(related_remote_id)
|
instance = origin_model.find_existing_by_remote_id(related_remote_id)
|
||||||
if not instance:
|
if not instance:
|
||||||
raise ValueError(f"Invalid related remote id: {related_remote_id}")
|
raise ValueError(f"Invalid related remote id: {related_remote_id}")
|
||||||
|
|
||||||
# set the origin's remote id on the activity so it will be there when
|
# set the origin's remote id on the activity so it will be there when
|
||||||
# the model instance is created
|
# the model instance is created
|
||||||
# edition.parentWork = instance, for example
|
# edition.parentWork = instance, for example
|
||||||
model_field = getattr(model, related_field_name)
|
model_field = getattr(model, related_field_name)
|
||||||
if hasattr(model_field, "activitypub_field"):
|
if hasattr(model_field, "activitypub_field"):
|
||||||
setattr(
|
setattr(activity, getattr(model_field, "activitypub_field"), instance.remote_id)
|
||||||
activity, getattr(model_field, "activitypub_field"), instance.remote_id
|
item = activity.to_model()
|
||||||
)
|
|
||||||
item = activity.to_model()
|
|
||||||
|
|
||||||
# if the related field isn't serialized (attachments on Status), then
|
# if the related field isn't serialized (attachments on Status), then
|
||||||
# we have to set it post-creation
|
# we have to set it post-creation
|
||||||
if not hasattr(model_field, "activitypub_field"):
|
if not hasattr(model_field, "activitypub_field"):
|
||||||
setattr(item, related_field_name, instance)
|
setattr(item, related_field_name, instance)
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
|
|
||||||
def get_model_from_type(activity_type):
|
def get_model_from_type(activity_type):
|
||||||
|
@ -304,3 +287,27 @@ def resolve_remote_id(
|
||||||
|
|
||||||
# if we're refreshing, "result" will be set and we'll update it
|
# if we're refreshing, "result" will be set and we'll update it
|
||||||
return item.to_model(model=model, instance=result, save=save)
|
return item.to_model(model=model, instance=result, save=save)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(init=False)
|
||||||
|
class Link(ActivityObject):
|
||||||
|
"""for tagging a book in a status"""
|
||||||
|
|
||||||
|
href: str
|
||||||
|
name: str = None
|
||||||
|
mediaType: str = None
|
||||||
|
id: str = None
|
||||||
|
attributedTo: str = None
|
||||||
|
type: str = "Link"
|
||||||
|
|
||||||
|
def serialize(self, **kwargs):
|
||||||
|
"""remove fields"""
|
||||||
|
omit = ("id", "type", "@context")
|
||||||
|
return super().serialize(omit=omit)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(init=False)
|
||||||
|
class Mention(Link):
|
||||||
|
"""a subtype of Link for mentioning an actor"""
|
||||||
|
|
||||||
|
type: str = "Mention"
|
||||||
|
|
|
@ -17,6 +17,8 @@ class BookData(ActivityObject):
|
||||||
goodreadsKey: str = None
|
goodreadsKey: str = None
|
||||||
bnfId: str = None
|
bnfId: str = None
|
||||||
lastEditedBy: str = None
|
lastEditedBy: str = None
|
||||||
|
links: List[str] = field(default_factory=lambda: [])
|
||||||
|
fileLinks: List[str] = field(default_factory=lambda: [])
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
|
|
@ -15,6 +15,11 @@ class PublicKey(ActivityObject):
|
||||||
publicKeyPem: str
|
publicKeyPem: str
|
||||||
type: str = "PublicKey"
|
type: str = "PublicKey"
|
||||||
|
|
||||||
|
def serialize(self, **kwargs):
|
||||||
|
"""remove fields"""
|
||||||
|
omit = ("type", "@context")
|
||||||
|
return super().serialize(omit=omit)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
|
|
|
@ -216,6 +216,18 @@ class CoverForm(CustomForm):
|
||||||
help_texts = {f: None for f in fields}
|
help_texts = {f: None for f in fields}
|
||||||
|
|
||||||
|
|
||||||
|
class LinkDomainForm(CustomForm):
|
||||||
|
class Meta:
|
||||||
|
model = models.LinkDomain
|
||||||
|
fields = ["name"]
|
||||||
|
|
||||||
|
|
||||||
|
class FileLinkForm(CustomForm):
|
||||||
|
class Meta:
|
||||||
|
model = models.FileLink
|
||||||
|
fields = ["url", "filetype", "availability", "book", "added_by"]
|
||||||
|
|
||||||
|
|
||||||
class EditionForm(CustomForm):
|
class EditionForm(CustomForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Edition
|
model = models.Edition
|
||||||
|
@ -230,6 +242,8 @@ class EditionForm(CustomForm):
|
||||||
"shelves",
|
"shelves",
|
||||||
"connector",
|
"connector",
|
||||||
"search_vector",
|
"search_vector",
|
||||||
|
"links",
|
||||||
|
"file_links",
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
"title": forms.TextInput(attrs={"aria-describedby": "desc_title"}),
|
"title": forms.TextInput(attrs={"aria-describedby": "desc_title"}),
|
||||||
|
@ -439,7 +453,7 @@ class GroupForm(CustomForm):
|
||||||
class ReportForm(CustomForm):
|
class ReportForm(CustomForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Report
|
model = models.Report
|
||||||
fields = ["user", "reporter", "statuses", "note"]
|
fields = ["user", "reporter", "statuses", "links", "note"]
|
||||||
|
|
||||||
|
|
||||||
class EmailBlocklistForm(CustomForm):
|
class EmailBlocklistForm(CustomForm):
|
||||||
|
@ -478,3 +492,19 @@ class SortListForm(forms.Form):
|
||||||
("descending", _("Descending")),
|
("descending", _("Descending")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReadThroughForm(CustomForm):
|
||||||
|
def clean(self):
|
||||||
|
"""make sure the email isn't in use by a registered user"""
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
start_date = cleaned_data.get("start_date")
|
||||||
|
finish_date = cleaned_data.get("finish_date")
|
||||||
|
if start_date and finish_date and start_date > finish_date:
|
||||||
|
self.add_error(
|
||||||
|
"finish_date", _("Reading finish date cannot be before start date.")
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.ReadThrough
|
||||||
|
fields = ["user", "book", "start_date", "finish_date"]
|
||||||
|
|
|
@ -5,7 +5,9 @@ import redis
|
||||||
from bookwyrm import settings
|
from bookwyrm import settings
|
||||||
|
|
||||||
r = redis.Redis(
|
r = redis.Redis(
|
||||||
host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0
|
host=settings.REDIS_ACTIVITY_HOST,
|
||||||
|
port=settings.REDIS_ACTIVITY_PORT,
|
||||||
|
db=settings.REDIS_ACTIVITY_DB_INDEX,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.core.management.base import BaseCommand
|
||||||
from django.contrib.auth.models import Group, Permission
|
from django.contrib.auth.models import Group, Permission
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from bookwyrm.models import Connector, FederatedServer, SiteSettings, User
|
from bookwyrm import models
|
||||||
|
|
||||||
|
|
||||||
def init_groups():
|
def init_groups():
|
||||||
|
@ -55,7 +55,7 @@ def init_permissions():
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
content_type = ContentType.objects.get_for_model(User)
|
content_type = models.ContentType.objects.get_for_model(User)
|
||||||
for permission in permissions:
|
for permission in permissions:
|
||||||
permission_obj = Permission.objects.create(
|
permission_obj = Permission.objects.create(
|
||||||
codename=permission["codename"],
|
codename=permission["codename"],
|
||||||
|
@ -72,7 +72,7 @@ def init_permissions():
|
||||||
|
|
||||||
def init_connectors():
|
def init_connectors():
|
||||||
"""access book data sources"""
|
"""access book data sources"""
|
||||||
Connector.objects.create(
|
models.Connector.objects.create(
|
||||||
identifier="bookwyrm.social",
|
identifier="bookwyrm.social",
|
||||||
name="BookWyrm dot Social",
|
name="BookWyrm dot Social",
|
||||||
connector_file="bookwyrm_connector",
|
connector_file="bookwyrm_connector",
|
||||||
|
@ -84,7 +84,7 @@ def init_connectors():
|
||||||
priority=2,
|
priority=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
Connector.objects.create(
|
models.Connector.objects.create(
|
||||||
identifier="inventaire.io",
|
identifier="inventaire.io",
|
||||||
name="Inventaire",
|
name="Inventaire",
|
||||||
connector_file="inventaire",
|
connector_file="inventaire",
|
||||||
|
@ -96,7 +96,7 @@ def init_connectors():
|
||||||
priority=3,
|
priority=3,
|
||||||
)
|
)
|
||||||
|
|
||||||
Connector.objects.create(
|
models.Connector.objects.create(
|
||||||
identifier="openlibrary.org",
|
identifier="openlibrary.org",
|
||||||
name="OpenLibrary",
|
name="OpenLibrary",
|
||||||
connector_file="openlibrary",
|
connector_file="openlibrary",
|
||||||
|
@ -113,7 +113,7 @@ def init_federated_servers():
|
||||||
"""big no to nazis"""
|
"""big no to nazis"""
|
||||||
built_in_blocks = ["gab.ai", "gab.com"]
|
built_in_blocks = ["gab.ai", "gab.com"]
|
||||||
for server in built_in_blocks:
|
for server in built_in_blocks:
|
||||||
FederatedServer.objects.create(
|
models.FederatedServer.objects.create(
|
||||||
server_name=server,
|
server_name=server,
|
||||||
status="blocked",
|
status="blocked",
|
||||||
)
|
)
|
||||||
|
@ -121,18 +121,61 @@ def init_federated_servers():
|
||||||
|
|
||||||
def init_settings():
|
def init_settings():
|
||||||
"""info about the instance"""
|
"""info about the instance"""
|
||||||
SiteSettings.objects.create(
|
models.SiteSettings.objects.create(
|
||||||
support_link="https://www.patreon.com/bookwyrm",
|
support_link="https://www.patreon.com/bookwyrm",
|
||||||
support_title="Patreon",
|
support_title="Patreon",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def init_link_domains(*_):
|
||||||
|
"""safe book links"""
|
||||||
|
domains = [
|
||||||
|
("standardebooks.org", "Standard EBooks"),
|
||||||
|
("www.gutenberg.org", "Project Gutenberg"),
|
||||||
|
("archive.org", "Internet Archive"),
|
||||||
|
("openlibrary.org", "Open Library"),
|
||||||
|
("theanarchistlibrary.org", "The Anarchist Library"),
|
||||||
|
]
|
||||||
|
for domain, name in domains:
|
||||||
|
models.LinkDomain.objects.create(
|
||||||
|
domain=domain,
|
||||||
|
name=name,
|
||||||
|
status="approved",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Initializes the database with starter data"
|
help = "Initializes the database with starter data"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--limit",
|
||||||
|
default=None,
|
||||||
|
help="Limit init to specific table",
|
||||||
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
init_groups()
|
limit = options.get("limit")
|
||||||
init_permissions()
|
tables = [
|
||||||
init_connectors()
|
"group",
|
||||||
init_federated_servers()
|
"permission",
|
||||||
init_settings()
|
"connector",
|
||||||
|
"federatedserver",
|
||||||
|
"settings",
|
||||||
|
"linkdomain",
|
||||||
|
]
|
||||||
|
if limit and limit not in tables:
|
||||||
|
raise Exception("Invalid table limit:", limit)
|
||||||
|
|
||||||
|
if not limit or limit == "group":
|
||||||
|
init_groups()
|
||||||
|
if not limit or limit == "permission":
|
||||||
|
init_permissions()
|
||||||
|
if not limit or limit == "connector":
|
||||||
|
init_connectors()
|
||||||
|
if not limit or limit == "federatedserver":
|
||||||
|
init_federated_servers()
|
||||||
|
if not limit or limit == "settings":
|
||||||
|
init_settings()
|
||||||
|
if not limit or limit == "linkdomain":
|
||||||
|
init_link_domains()
|
||||||
|
|
|
@ -22,13 +22,6 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
help = "Populate list streams for all users"
|
help = "Populate list streams for all users"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument(
|
|
||||||
"--stream",
|
|
||||||
default=None,
|
|
||||||
help="Specifies which time of stream to populate",
|
|
||||||
)
|
|
||||||
|
|
||||||
# pylint: disable=no-self-use,unused-argument
|
# pylint: disable=no-self-use,unused-argument
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
"""run feed builder"""
|
"""run feed builder"""
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Generated by Django 3.2.10 on 2022-01-12 23:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0125_alter_user_preferred_language"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="annualgoal",
|
||||||
|
name="privacy",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("public", "Public"),
|
||||||
|
("unlisted", "Unlisted"),
|
||||||
|
("followers", "Followers"),
|
||||||
|
("direct", "Private"),
|
||||||
|
],
|
||||||
|
default="public",
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="importjob",
|
||||||
|
name="privacy",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("public", "Public"),
|
||||||
|
("unlisted", "Unlisted"),
|
||||||
|
("followers", "Followers"),
|
||||||
|
("direct", "Private"),
|
||||||
|
],
|
||||||
|
default="public",
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="user",
|
||||||
|
name="default_post_privacy",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("public", "Public"),
|
||||||
|
("unlisted", "Unlisted"),
|
||||||
|
("followers", "Followers"),
|
||||||
|
("direct", "Private"),
|
||||||
|
],
|
||||||
|
default="public",
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Generated by Django 3.2.10 on 2022-01-10 21:20
|
||||||
|
|
||||||
|
import bookwyrm.models.activitypub_mixin
|
||||||
|
import bookwyrm.models.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0125_alter_user_preferred_language"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="LinkDomain",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_date", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_date", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"remote_id",
|
||||||
|
bookwyrm.models.fields.RemoteIdField(
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
validators=[bookwyrm.models.fields.validate_remote_id],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("domain", models.CharField(max_length=255, unique=True)),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("approved", "Approved"),
|
||||||
|
("blocked", "Blocked"),
|
||||||
|
("pending", "Pending"),
|
||||||
|
],
|
||||||
|
default="pending",
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=100)),
|
||||||
|
(
|
||||||
|
"reported_by",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Link",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_date", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_date", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"remote_id",
|
||||||
|
bookwyrm.models.fields.RemoteIdField(
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
validators=[bookwyrm.models.fields.validate_remote_id],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("url", bookwyrm.models.fields.URLField(max_length=255)),
|
||||||
|
(
|
||||||
|
"added_by",
|
||||||
|
bookwyrm.models.fields.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"domain",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="links",
|
||||||
|
to="bookwyrm.linkdomain",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
bases=(bookwyrm.models.activitypub_mixin.ActivitypubMixin, models.Model),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="FileLink",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"link_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="bookwyrm.link",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("filetype", bookwyrm.models.fields.CharField(max_length=5)),
|
||||||
|
(
|
||||||
|
"book",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="file_links",
|
||||||
|
to="bookwyrm.book",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
bases=("bookwyrm.link",),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 3.2.10 on 2022-01-10 22:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0126_filelink_link_linkdomain"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveConstraint(
|
||||||
|
model_name="report",
|
||||||
|
name="self_report",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="report",
|
||||||
|
name="links",
|
||||||
|
field=models.ManyToManyField(blank=True, to="bookwyrm.Link"),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Generated by Django 3.2.10 on 2022-01-13 01:14
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0126_auto_20220112_2315"),
|
||||||
|
("bookwyrm", "0127_auto_20220110_2211"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 3.2.10 on 2022-01-17 17:16
|
||||||
|
|
||||||
|
import bookwyrm.models.fields
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0128_merge_0126_auto_20220112_2315_0127_auto_20220110_2211"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="filelink",
|
||||||
|
name="availability",
|
||||||
|
field=bookwyrm.models.fields.CharField(
|
||||||
|
choices=[
|
||||||
|
("free", "Free"),
|
||||||
|
("purchase", "Purchasable"),
|
||||||
|
("loan", "Available for loan"),
|
||||||
|
],
|
||||||
|
default="free",
|
||||||
|
max_length=100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="filelink",
|
||||||
|
name="filetype",
|
||||||
|
field=bookwyrm.models.fields.CharField(max_length=50),
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,6 +4,7 @@ import sys
|
||||||
|
|
||||||
from .book import Book, Work, Edition, BookDataModel
|
from .book import Book, Work, Edition, BookDataModel
|
||||||
from .author import Author
|
from .author import Author
|
||||||
|
from .link import Link, FileLink, LinkDomain
|
||||||
from .connector import Connector
|
from .connector import Connector
|
||||||
|
|
||||||
from .shelf import Shelf, ShelfBook
|
from .shelf import Shelf, ShelfBook
|
||||||
|
|
|
@ -241,8 +241,11 @@ class Work(OrderedCollectionPageMixin, Book):
|
||||||
)
|
)
|
||||||
|
|
||||||
activity_serializer = activitypub.Work
|
activity_serializer = activitypub.Work
|
||||||
serialize_reverse_fields = [("editions", "editions", "-edition_rank")]
|
serialize_reverse_fields = [
|
||||||
deserialize_reverse_fields = [("editions", "editions")]
|
("editions", "editions", "-edition_rank"),
|
||||||
|
("file_links", "fileLinks", "-created_date"),
|
||||||
|
]
|
||||||
|
deserialize_reverse_fields = [("editions", "editions"), ("file_links", "fileLinks")]
|
||||||
|
|
||||||
|
|
||||||
# https://schema.org/BookFormatType
|
# https://schema.org/BookFormatType
|
||||||
|
@ -296,6 +299,8 @@ class Edition(Book):
|
||||||
|
|
||||||
activity_serializer = activitypub.Edition
|
activity_serializer = activitypub.Edition
|
||||||
name_field = "title"
|
name_field = "title"
|
||||||
|
serialize_reverse_fields = [("file_links", "fileLinks", "-created_date")]
|
||||||
|
deserialize_reverse_fields = [("file_links", "fileLinks")]
|
||||||
|
|
||||||
def get_rank(self):
|
def get_rank(self):
|
||||||
"""calculate how complete the data is on this edition"""
|
"""calculate how complete the data is on this edition"""
|
||||||
|
@ -337,6 +342,11 @@ class Edition(Book):
|
||||||
# set rank
|
# set rank
|
||||||
self.edition_rank = self.get_rank()
|
self.edition_rank = self.get_rank()
|
||||||
|
|
||||||
|
# clear author cache
|
||||||
|
if self.id:
|
||||||
|
for author_id in self.authors.values_list("id", flat=True):
|
||||||
|
cache.delete(f"author-books-{author_id}")
|
||||||
|
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -203,9 +203,12 @@ class UsernameField(ActivitypubFieldMixin, models.CharField):
|
||||||
return value.split("@")[0]
|
return value.split("@")[0]
|
||||||
|
|
||||||
|
|
||||||
PrivacyLevels = models.TextChoices(
|
PrivacyLevels = [
|
||||||
"Privacy", ["public", "unlisted", "followers", "direct"]
|
("public", _("Public")),
|
||||||
)
|
("unlisted", _("Unlisted")),
|
||||||
|
("followers", _("Followers")),
|
||||||
|
("direct", _("Private")),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class PrivacyField(ActivitypubFieldMixin, models.CharField):
|
class PrivacyField(ActivitypubFieldMixin, models.CharField):
|
||||||
|
@ -214,9 +217,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField):
|
||||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(
|
super().__init__(*args, max_length=255, choices=PrivacyLevels, default="public")
|
||||||
*args, max_length=255, choices=PrivacyLevels.choices, default="public"
|
|
||||||
)
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
def set_field_from_activity(self, instance, data, overwrite=True):
|
def set_field_from_activity(self, instance, data, overwrite=True):
|
||||||
|
@ -516,6 +517,10 @@ class CharField(ActivitypubFieldMixin, models.CharField):
|
||||||
"""activitypub-aware char field"""
|
"""activitypub-aware char field"""
|
||||||
|
|
||||||
|
|
||||||
|
class URLField(ActivitypubFieldMixin, models.URLField):
|
||||||
|
"""activitypub-aware url field"""
|
||||||
|
|
||||||
|
|
||||||
class TextField(ActivitypubFieldMixin, models.TextField):
|
class TextField(ActivitypubFieldMixin, models.TextField):
|
||||||
"""activitypub-aware text field"""
|
"""activitypub-aware text field"""
|
||||||
|
|
||||||
|
|
|
@ -40,9 +40,7 @@ class ImportJob(models.Model):
|
||||||
mappings = models.JSONField()
|
mappings = models.JSONField()
|
||||||
complete = models.BooleanField(default=False)
|
complete = models.BooleanField(default=False)
|
||||||
source = models.CharField(max_length=100)
|
source = models.CharField(max_length=100)
|
||||||
privacy = models.CharField(
|
privacy = models.CharField(max_length=255, default="public", choices=PrivacyLevels)
|
||||||
max_length=255, default="public", choices=PrivacyLevels.choices
|
|
||||||
)
|
|
||||||
retry = models.BooleanField(default=False)
|
retry = models.BooleanField(default=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
""" outlink data """
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from bookwyrm import activitypub
|
||||||
|
from .activitypub_mixin import ActivitypubMixin
|
||||||
|
from .base_model import BookWyrmModel
|
||||||
|
from . import fields
|
||||||
|
|
||||||
|
|
||||||
|
class Link(ActivitypubMixin, BookWyrmModel):
|
||||||
|
"""a link to a website"""
|
||||||
|
|
||||||
|
url = fields.URLField(max_length=255, activitypub_field="href")
|
||||||
|
added_by = fields.ForeignKey(
|
||||||
|
"User", on_delete=models.SET_NULL, null=True, activitypub_field="attributedTo"
|
||||||
|
)
|
||||||
|
domain = models.ForeignKey(
|
||||||
|
"LinkDomain",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="links",
|
||||||
|
)
|
||||||
|
|
||||||
|
activity_serializer = activitypub.Link
|
||||||
|
reverse_unfurl = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""link name via the assocaited domain"""
|
||||||
|
return self.domain.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""create a link"""
|
||||||
|
# get or create the associated domain
|
||||||
|
if not self.domain:
|
||||||
|
domain = urlparse(self.url).netloc
|
||||||
|
self.domain, _ = LinkDomain.objects.get_or_create(domain=domain)
|
||||||
|
|
||||||
|
# this is never broadcast, the owning model broadcasts an update
|
||||||
|
if "broadcast" in kwargs:
|
||||||
|
del kwargs["broadcast"]
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
AvailabilityChoices = [
|
||||||
|
("free", _("Free")),
|
||||||
|
("purchase", _("Purchasable")),
|
||||||
|
("loan", _("Available for loan")),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class FileLink(Link):
|
||||||
|
"""a link to a file"""
|
||||||
|
|
||||||
|
book = models.ForeignKey(
|
||||||
|
"Book", on_delete=models.CASCADE, related_name="file_links", null=True
|
||||||
|
)
|
||||||
|
filetype = fields.CharField(max_length=50, activitypub_field="mediaType")
|
||||||
|
availability = fields.CharField(
|
||||||
|
max_length=100, choices=AvailabilityChoices, default="free"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
StatusChoices = [
|
||||||
|
("approved", _("Approved")),
|
||||||
|
("blocked", _("Blocked")),
|
||||||
|
("pending", _("Pending")),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class LinkDomain(BookWyrmModel):
|
||||||
|
"""List of domains used in links"""
|
||||||
|
|
||||||
|
domain = models.CharField(max_length=255, unique=True)
|
||||||
|
status = models.CharField(max_length=50, choices=StatusChoices, default="pending")
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
reported_by = models.ForeignKey(
|
||||||
|
"User", blank=True, null=True, on_delete=models.SET_NULL
|
||||||
|
)
|
||||||
|
|
||||||
|
def raise_not_editable(self, viewer):
|
||||||
|
if viewer.has_perm("moderate_post"):
|
||||||
|
return
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""set a default name"""
|
||||||
|
if not self.name:
|
||||||
|
self.name = self.domain
|
||||||
|
super().save(*args, **kwargs)
|
|
@ -1,5 +1,6 @@
|
||||||
""" progress in a book """
|
""" progress in a book """
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
|
from django.core.cache import cache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ class ReadThrough(BookWyrmModel):
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""update user active time"""
|
"""update user active time"""
|
||||||
|
cache.delete(f"latest_read_through-{self.user.id}-{self.book.id}")
|
||||||
self.user.update_active_date()
|
self.user.update_active_date()
|
||||||
# an active readthrough must have an unset finish date
|
# an active readthrough must have an unset finish date
|
||||||
if self.finish_date:
|
if self.finish_date:
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
""" defines relationships between users """
|
""" defines relationships between users """
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.cache.utils import make_template_fragment_key
|
|
||||||
from django.db import models, transaction, IntegrityError
|
from django.db import models, transaction, IntegrityError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
|
@ -41,15 +40,12 @@ class UserRelationship(BookWyrmModel):
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""clear the template cache"""
|
"""clear the template cache"""
|
||||||
# invalidate the template cache
|
# invalidate the template cache
|
||||||
cache_keys = [
|
cache.delete_many(
|
||||||
make_template_fragment_key(
|
[
|
||||||
"follow_button", [self.user_subject.id, self.user_object.id]
|
f"relationship-{self.user_subject.id}-{self.user_object.id}",
|
||||||
),
|
f"relationship-{self.user_object.id}-{self.user_subject.id}",
|
||||||
make_template_fragment_key(
|
]
|
||||||
"follow_button", [self.user_object.id, self.user_subject.id]
|
)
|
||||||
),
|
|
||||||
]
|
|
||||||
cache.delete_many(cache_keys)
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
""" flagged for moderation """
|
""" flagged for moderation """
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import F, Q
|
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,14 +12,12 @@ class Report(BookWyrmModel):
|
||||||
note = models.TextField(null=True, blank=True)
|
note = models.TextField(null=True, blank=True)
|
||||||
user = models.ForeignKey("User", on_delete=models.PROTECT)
|
user = models.ForeignKey("User", on_delete=models.PROTECT)
|
||||||
statuses = models.ManyToManyField("Status", blank=True)
|
statuses = models.ManyToManyField("Status", blank=True)
|
||||||
|
links = models.ManyToManyField("Link", blank=True)
|
||||||
resolved = models.BooleanField(default=False)
|
resolved = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""don't let users report themselves"""
|
"""set order by default"""
|
||||||
|
|
||||||
constraints = [
|
|
||||||
models.CheckConstraint(check=~Q(reporter=F("user")), name="self_report")
|
|
||||||
]
|
|
||||||
ordering = ("-created_date",)
|
ordering = ("-created_date",)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
""" puttin' books on shelves """
|
""" puttin' books on shelves """
|
||||||
import re
|
import re
|
||||||
|
from django.core.cache import cache
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -94,8 +95,15 @@ class ShelfBook(CollectionItemMixin, BookWyrmModel):
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.user:
|
if not self.user:
|
||||||
self.user = self.shelf.user
|
self.user = self.shelf.user
|
||||||
|
if self.id and self.user.local:
|
||||||
|
cache.delete(f"book-on-shelf-{self.book.id}-{self.shelf.id}")
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
if self.id and self.user.local:
|
||||||
|
cache.delete(f"book-on-shelf-{self.book.id}-{self.shelf.id}")
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""an opinionated constraint!
|
"""an opinionated constraint!
|
||||||
you can't put a book on shelf twice"""
|
you can't put a book on shelf twice"""
|
||||||
|
|
|
@ -90,6 +90,14 @@ class SiteSettings(models.Model):
|
||||||
return get_absolute_url(uploaded)
|
return get_absolute_url(uploaded)
|
||||||
return urljoin(STATIC_FULL_URL, default_path)
|
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 not self.require_confirm_email:
|
||||||
|
User.objects.filter(is_active=False, deactivation_reason="pending").update(
|
||||||
|
is_active=True, deactivation_reason=None
|
||||||
|
)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class SiteInvite(models.Model):
|
class SiteInvite(models.Model):
|
||||||
"""gives someone access to create an account on the instance"""
|
"""gives someone access to create an account on the instance"""
|
||||||
|
|
|
@ -3,6 +3,7 @@ from dataclasses import MISSING
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.core.cache import cache
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -373,6 +374,12 @@ class Review(BookStatus):
|
||||||
activity_serializer = activitypub.Review
|
activity_serializer = activitypub.Review
|
||||||
pure_type = "Article"
|
pure_type = "Article"
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""clear rating caches"""
|
||||||
|
if self.book.parent_work:
|
||||||
|
cache.delete(f"book-rating-{self.book.parent_work.id}-*")
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ReviewRating(Review):
|
class ReviewRating(Review):
|
||||||
"""a subtype of review that only contains a rating"""
|
"""a subtype of review that only contains a rating"""
|
||||||
|
|
|
@ -129,7 +129,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
related_name="favorite_statuses",
|
related_name="favorite_statuses",
|
||||||
)
|
)
|
||||||
default_post_privacy = models.CharField(
|
default_post_privacy = models.CharField(
|
||||||
max_length=255, default="public", choices=fields.PrivacyLevels.choices
|
max_length=255, default="public", choices=fields.PrivacyLevels
|
||||||
)
|
)
|
||||||
remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id")
|
remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id")
|
||||||
created_date = models.DateTimeField(auto_now_add=True)
|
created_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
@ -346,6 +346,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
"""deactivate rather than delete a user"""
|
"""deactivate rather than delete a user"""
|
||||||
|
# pylint: disable=attribute-defined-outside-init
|
||||||
self.is_active = False
|
self.is_active = False
|
||||||
# skip the logic in this class's save()
|
# skip the logic in this class's save()
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
@ -406,14 +407,6 @@ class KeyPair(ActivitypubMixin, BookWyrmModel):
|
||||||
self.private_key, self.public_key = create_key_pair()
|
self.private_key, self.public_key = create_key_pair()
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
def to_activity(self, **kwargs):
|
|
||||||
"""override default AP serializer to add context object
|
|
||||||
idk if this is the best way to go about this"""
|
|
||||||
activity_object = super().to_activity(**kwargs)
|
|
||||||
del activity_object["@context"]
|
|
||||||
del activity_object["type"]
|
|
||||||
return activity_object
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_year():
|
def get_current_year():
|
||||||
"""sets default year for annual goal to this year"""
|
"""sets default year for annual goal to this year"""
|
||||||
|
@ -427,7 +420,7 @@ class AnnualGoal(BookWyrmModel):
|
||||||
goal = models.IntegerField(validators=[MinValueValidator(1)])
|
goal = models.IntegerField(validators=[MinValueValidator(1)])
|
||||||
year = models.IntegerField(default=get_current_year)
|
year = models.IntegerField(default=get_current_year)
|
||||||
privacy = models.CharField(
|
privacy = models.CharField(
|
||||||
max_length=255, default="public", choices=fields.PrivacyLevels.choices
|
max_length=255, default="public", choices=fields.PrivacyLevels
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -8,7 +8,7 @@ r = redis.Redis(
|
||||||
host=settings.REDIS_ACTIVITY_HOST,
|
host=settings.REDIS_ACTIVITY_HOST,
|
||||||
port=settings.REDIS_ACTIVITY_PORT,
|
port=settings.REDIS_ACTIVITY_PORT,
|
||||||
password=settings.REDIS_ACTIVITY_PASSWORD,
|
password=settings.REDIS_ACTIVITY_PASSWORD,
|
||||||
db=0,
|
db=settings.REDIS_ACTIVITY_DB_INDEX,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,12 @@ from django.utils.translation import gettext_lazy as _
|
||||||
env = Env()
|
env = Env()
|
||||||
env.read_env()
|
env.read_env()
|
||||||
DOMAIN = env("DOMAIN")
|
DOMAIN = env("DOMAIN")
|
||||||
VERSION = "0.1.1"
|
VERSION = "0.2.0"
|
||||||
|
|
||||||
PAGE_LENGTH = env("PAGE_LENGTH", 15)
|
PAGE_LENGTH = env("PAGE_LENGTH", 15)
|
||||||
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
|
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
|
||||||
|
|
||||||
JS_CACHE = "2d3181e1"
|
JS_CACHE = "76c5ff1f"
|
||||||
|
|
||||||
# email
|
# email
|
||||||
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
|
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
|
||||||
|
@ -25,7 +25,7 @@ EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD")
|
||||||
EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", True)
|
EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", True)
|
||||||
EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL", False)
|
EMAIL_USE_SSL = env.bool("EMAIL_USE_SSL", False)
|
||||||
EMAIL_SENDER_NAME = env("EMAIL_SENDER_NAME", "admin")
|
EMAIL_SENDER_NAME = env("EMAIL_SENDER_NAME", "admin")
|
||||||
EMAIL_SENDER_DOMAIN = env("EMAIL_SENDER_NAME", DOMAIN)
|
EMAIL_SENDER_DOMAIN = env("EMAIL_SENDER_DOMAIN", DOMAIN)
|
||||||
EMAIL_SENDER = f"{EMAIL_SENDER_NAME}@{EMAIL_SENDER_DOMAIN}"
|
EMAIL_SENDER = f"{EMAIL_SENDER_NAME}@{EMAIL_SENDER_DOMAIN}"
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
@ -106,6 +106,58 @@ TEMPLATES = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
LOG_LEVEL = env("LOG_LEVEL", "INFO").upper()
|
||||||
|
# Override aspects of the default handler to our taste
|
||||||
|
# See https://docs.djangoproject.com/en/3.2/topics/logging/#default-logging-configuration
|
||||||
|
# for a reference to the defaults we're overriding
|
||||||
|
#
|
||||||
|
# It seems that in order to override anything you have to include its
|
||||||
|
# entire dependency tree (handlers and filters) which makes this a
|
||||||
|
# bit verbose
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"filters": {
|
||||||
|
# These are copied from the default configuration, required for
|
||||||
|
# implementing mail_admins below
|
||||||
|
"require_debug_false": {
|
||||||
|
"()": "django.utils.log.RequireDebugFalse",
|
||||||
|
},
|
||||||
|
"require_debug_true": {
|
||||||
|
"()": "django.utils.log.RequireDebugTrue",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
# Overrides the default handler to make it log to console
|
||||||
|
# regardless of the DEBUG setting (default is to not log to
|
||||||
|
# console if DEBUG=False)
|
||||||
|
"console": {
|
||||||
|
"level": LOG_LEVEL,
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
},
|
||||||
|
# This is copied as-is from the default logger, and is
|
||||||
|
# required for the django section below
|
||||||
|
"mail_admins": {
|
||||||
|
"level": "ERROR",
|
||||||
|
"filters": ["require_debug_false"],
|
||||||
|
"class": "django.utils.log.AdminEmailHandler",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
# Install our new console handler for Django's logger, and
|
||||||
|
# override the log level while we're at it
|
||||||
|
"django": {
|
||||||
|
"handlers": ["console", "mail_admins"],
|
||||||
|
"level": LOG_LEVEL,
|
||||||
|
},
|
||||||
|
# Add a bookwyrm-specific logger
|
||||||
|
"bookwyrm": {
|
||||||
|
"handlers": ["console"],
|
||||||
|
"level": LOG_LEVEL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
WSGI_APPLICATION = "bookwyrm.wsgi.application"
|
WSGI_APPLICATION = "bookwyrm.wsgi.application"
|
||||||
|
|
||||||
|
@ -113,6 +165,7 @@ WSGI_APPLICATION = "bookwyrm.wsgi.application"
|
||||||
REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost")
|
REDIS_ACTIVITY_HOST = env("REDIS_ACTIVITY_HOST", "localhost")
|
||||||
REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379)
|
REDIS_ACTIVITY_PORT = env("REDIS_ACTIVITY_PORT", 6379)
|
||||||
REDIS_ACTIVITY_PASSWORD = env("REDIS_ACTIVITY_PASSWORD", None)
|
REDIS_ACTIVITY_PASSWORD = env("REDIS_ACTIVITY_PASSWORD", None)
|
||||||
|
REDIS_ACTIVITY_DB_INDEX = env("REDIS_ACTIVITY_DB_INDEX", 0)
|
||||||
|
|
||||||
MAX_STREAM_LENGTH = int(env("MAX_STREAM_LENGTH", 200))
|
MAX_STREAM_LENGTH = int(env("MAX_STREAM_LENGTH", 200))
|
||||||
|
|
||||||
|
@ -139,7 +192,7 @@ else:
|
||||||
CACHES = {
|
CACHES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "django_redis.cache.RedisCache",
|
"BACKEND": "django_redis.cache.RedisCache",
|
||||||
"LOCATION": f"redis://:{REDIS_ACTIVITY_PASSWORD}@{REDIS_ACTIVITY_HOST}:{REDIS_ACTIVITY_PORT}/0",
|
"LOCATION": f"redis://:{REDIS_ACTIVITY_PASSWORD}@{REDIS_ACTIVITY_HOST}:{REDIS_ACTIVITY_PORT}/{REDIS_ACTIVITY_DB_INDEX}",
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||||
},
|
},
|
||||||
|
|
|
@ -720,6 +720,11 @@ ol.ordered-list li::before {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overflow-wrap-anywhere {
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
min-width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
/* Threads
|
/* Threads
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
|
@ -751,6 +756,13 @@ ol.ordered-list li::before {
|
||||||
padding: 0 0.75em;
|
padding: 0 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Notifications page
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
.notification a.icon {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Breadcrumbs
|
/* Breadcrumbs
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suggest a completion as a user types
|
||||||
|
*
|
||||||
|
* Use `data-autocomplete="<completions set identifier>"`on the input field.
|
||||||
|
* specifying the trie to be used for autocomplete
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <input
|
||||||
|
* type="input"
|
||||||
|
* data-autocomplete="mimetype"
|
||||||
|
* >
|
||||||
|
* @param {Event} event
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function autocomplete(event) {
|
||||||
|
const input = event.target;
|
||||||
|
|
||||||
|
// Get suggestions
|
||||||
|
let trie = tries[input.getAttribute("data-autocomplete")];
|
||||||
|
|
||||||
|
let suggestions = getSuggestions(input.value, trie);
|
||||||
|
|
||||||
|
const boxId = input.getAttribute("list");
|
||||||
|
|
||||||
|
// Create suggestion box, if needed
|
||||||
|
let suggestionsBox = document.getElementById(boxId);
|
||||||
|
|
||||||
|
// Clear existing suggestions
|
||||||
|
suggestionsBox.innerHTML = "";
|
||||||
|
|
||||||
|
// Populate suggestions box
|
||||||
|
suggestions.forEach((suggestion) => {
|
||||||
|
const suggestionItem = document.createElement("option");
|
||||||
|
|
||||||
|
suggestionItem.textContent = suggestion;
|
||||||
|
suggestionsBox.appendChild(suggestionItem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSuggestions(input, trie) {
|
||||||
|
// Follow the trie through the provided input
|
||||||
|
input = input.toLowerCase();
|
||||||
|
|
||||||
|
input.split("").forEach((letter) => {
|
||||||
|
if (!trie) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
trie = trie[letter];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!trie) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchTrie(trie);
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchTrie(trie) {
|
||||||
|
const options = Object.values(trie);
|
||||||
|
|
||||||
|
if (typeof trie == "string") {
|
||||||
|
return [trie];
|
||||||
|
}
|
||||||
|
|
||||||
|
return options
|
||||||
|
.map((option) => {
|
||||||
|
const newTrie = option;
|
||||||
|
|
||||||
|
if (typeof newTrie == "string") {
|
||||||
|
return [newTrie];
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchTrie(newTrie);
|
||||||
|
})
|
||||||
|
.reduce((prev, next) => prev.concat(next));
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll("[data-autocomplete]").forEach((input) => {
|
||||||
|
input.addEventListener("input", autocomplete);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
const tries = {
|
||||||
|
mimetype: {
|
||||||
|
a: {
|
||||||
|
a: {
|
||||||
|
c: "AAC",
|
||||||
|
},
|
||||||
|
z: {
|
||||||
|
w: "AZW",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
d: {
|
||||||
|
a: {
|
||||||
|
i: {
|
||||||
|
s: {
|
||||||
|
y: "Daisy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
e: {
|
||||||
|
p: {
|
||||||
|
u: {
|
||||||
|
b: "ePub",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
f: {
|
||||||
|
l: {
|
||||||
|
a: {
|
||||||
|
c: "FLAC",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h: {
|
||||||
|
t: {
|
||||||
|
m: {
|
||||||
|
l: "HTML",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
m: {
|
||||||
|
4: {
|
||||||
|
a: "M4A",
|
||||||
|
b: "M4B",
|
||||||
|
},
|
||||||
|
o: {
|
||||||
|
b: {
|
||||||
|
i: "MOBI",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
p: {
|
||||||
|
3: "MP3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
o: {
|
||||||
|
g: {
|
||||||
|
g: "OGG",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
p: {
|
||||||
|
d: {
|
||||||
|
f: "PDF",
|
||||||
|
},
|
||||||
|
l: {
|
||||||
|
a: {
|
||||||
|
i: {
|
||||||
|
n: {
|
||||||
|
t: {
|
||||||
|
e: {
|
||||||
|
x: {
|
||||||
|
t: "Plaintext",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
r: {
|
||||||
|
i: {
|
||||||
|
n: {
|
||||||
|
t: {
|
||||||
|
" ": {
|
||||||
|
b: {
|
||||||
|
o: {
|
||||||
|
o: {
|
||||||
|
k: "Print book",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -35,7 +35,7 @@ let BookWyrm = new (class {
|
||||||
.forEach((node) => node.addEventListener("change", this.disableIfTooLarge.bind(this)));
|
.forEach((node) => node.addEventListener("change", this.disableIfTooLarge.bind(this)));
|
||||||
|
|
||||||
document
|
document
|
||||||
.querySelectorAll("button[data-modal-open]")
|
.querySelectorAll("[data-modal-open]")
|
||||||
.forEach((node) => node.addEventListener("click", this.handleModalButton.bind(this)));
|
.forEach((node) => node.addEventListener("click", this.handleModalButton.bind(this)));
|
||||||
|
|
||||||
document
|
document
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load bookwyrm_tags %}
|
{% load landing_page_tags %}
|
||||||
{% load cache %}
|
{% load cache %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
{% block about_content %}
|
{% block about_content %}
|
||||||
{# seven day cache #}
|
{# seven day cache #}
|
||||||
{% cache 604800 about_page %}
|
{% cache 604800 about_page %}
|
||||||
|
|
||||||
{% get_book_superlatives as superlatives %}
|
{% get_book_superlatives as superlatives %}
|
||||||
<section class="content pb-4">
|
<section class="content pb-4">
|
||||||
<h2>
|
<h2>
|
||||||
|
@ -26,7 +27,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
{% if top_rated %}
|
{% if superlatives.top_rated %}
|
||||||
{% with book=superlatives.top_rated.default_edition rating=top_rated.rating %}
|
{% with book=superlatives.top_rated.default_edition rating=top_rated.rating %}
|
||||||
<div class="column is-one-third is-flex">
|
<div class="column is-one-third is-flex">
|
||||||
<div class="media notification">
|
<div class="media notification">
|
||||||
|
@ -45,7 +46,7 @@
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if wanted %}
|
{% if superlatives.wanted %}
|
||||||
{% with book=superlatives.wanted.default_edition %}
|
{% with book=superlatives.wanted.default_edition %}
|
||||||
<div class="column is-one-third is-flex">
|
<div class="column is-one-third is-flex">
|
||||||
<div class="media notification">
|
<div class="media notification">
|
||||||
|
@ -64,7 +65,7 @@
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if controversial %}
|
{% if superlatives.controversial %}
|
||||||
{% with book=superlatives.controversial.default_edition %}
|
{% with book=superlatives.controversial.default_edition %}
|
||||||
<div class="column is-one-third is-flex">
|
<div class="column is-one-third is-flex">
|
||||||
<div class="media notification">
|
<div class="media notification">
|
||||||
|
@ -95,7 +96,7 @@
|
||||||
<h2 class="title is-3">{% trans "Meet your admins" %}</h2>
|
<h2 class="title is-3">{% trans "Meet your admins" %}</h2>
|
||||||
<p>
|
<p>
|
||||||
{% url "conduct" as coc_path %}
|
{% url "conduct" as coc_path %}
|
||||||
{% blocktrans with site_name=site.name %}
|
{% blocktrans trimmed with site_name=site.name %}
|
||||||
{{ site_name }}'s moderators and administrators keep the site up and running, enforce the <a href="coc_path">code of conduct</a>, and respond when users report spam and bad behavior.
|
{{ site_name }}'s moderators and administrators keep the site up and running, enforce the <a href="coc_path">code of conduct</a>, and respond when users report spam and bad behavior.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -23,18 +23,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block columns is-flex-direction-row-reverse" itemscope itemtype="https://schema.org/Person">
|
<div class="block columns" itemscope itemtype="https://schema.org/Person">
|
||||||
<meta itemprop="name" content="{{ author.name }}">
|
<meta itemprop="name" content="{{ author.name }}">
|
||||||
{% if author.bio %}
|
|
||||||
<div class="column">
|
|
||||||
{% include "snippets/trimmed_text.html" with full=author.bio trim_length=200 %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% firstof author.aliases author.born author.died as details %}
|
{% firstof author.aliases author.born author.died as details %}
|
||||||
{% firstof author.wikipedia_link author.openlibrary_key author.inventaire_id author.isni as links %}
|
{% firstof author.wikipedia_link author.openlibrary_key author.inventaire_id author.isni as links %}
|
||||||
{% if details or links %}
|
{% if details or links %}
|
||||||
<div class="column is-two-fifths">
|
<div class="column is-3">
|
||||||
{% if details %}
|
{% if details %}
|
||||||
<section class="block content">
|
<section class="block content">
|
||||||
<h2 class="title is-4">{% trans "Author details" %}</h2>
|
<h2 class="title is-4">{% trans "Author details" %}</h2>
|
||||||
|
@ -71,7 +66,7 @@
|
||||||
<div class="box">
|
<div class="box">
|
||||||
{% if author.wikipedia_link %}
|
{% if author.wikipedia_link %}
|
||||||
<div>
|
<div>
|
||||||
<a itemprop="sameAs" href="{{ author.wikipedia_link }}" rel="noopener" target="_blank">
|
<a itemprop="sameAs" href="{{ author.wikipedia_link }}" rel="noopener noreferrer" target="_blank">
|
||||||
{% trans "Wikipedia" %}
|
{% trans "Wikipedia" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -79,7 +74,7 @@
|
||||||
|
|
||||||
{% if author.isni %}
|
{% if author.isni %}
|
||||||
<div class="mt-1">
|
<div class="mt-1">
|
||||||
<a itemprop="sameAs" href="{{ author.isni_link }}" rel="noopener" target="_blank">
|
<a itemprop="sameAs" href="{{ author.isni_link }}" rel="noopener noreferrer" target="_blank">
|
||||||
{% trans "View ISNI record" %}
|
{% trans "View ISNI record" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,7 +83,7 @@
|
||||||
{% trans "Load data" as button_text %}
|
{% trans "Load data" as button_text %}
|
||||||
{% if author.openlibrary_key %}
|
{% if author.openlibrary_key %}
|
||||||
<div class="mt-1 is-flex">
|
<div class="mt-1 is-flex">
|
||||||
<a class="mr-3" itemprop="sameAs" href="{{ author.openlibrary_link }}" target="_blank" rel="noopener">
|
<a class="mr-3" itemprop="sameAs" href="{{ author.openlibrary_link }}" target="_blank" rel="noopener noreferrer">
|
||||||
{% trans "View on OpenLibrary" %}
|
{% trans "View on OpenLibrary" %}
|
||||||
</a>
|
</a>
|
||||||
{% if request.user.is_authenticated and perms.bookwyrm.edit_book %}
|
{% if request.user.is_authenticated and perms.bookwyrm.edit_book %}
|
||||||
|
@ -103,7 +98,7 @@
|
||||||
|
|
||||||
{% if author.inventaire_id %}
|
{% if author.inventaire_id %}
|
||||||
<div class="mt-1 is-flex">
|
<div class="mt-1 is-flex">
|
||||||
<a class="mr-3" itemprop="sameAs" href="{{ author.inventaire_link }}" target="_blank" rel="noopener">
|
<a class="mr-3" itemprop="sameAs" href="{{ author.inventaire_link }}" target="_blank" rel="noopener noreferrer">
|
||||||
{% trans "View on Inventaire" %}
|
{% trans "View on Inventaire" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -119,7 +114,7 @@
|
||||||
|
|
||||||
{% if author.librarything_key %}
|
{% if author.librarything_key %}
|
||||||
<div class="mt-1">
|
<div class="mt-1">
|
||||||
<a itemprop="sameAs" href="https://www.librarything.com/author/{{ author.librarything_key }}" target="_blank" rel="noopener">
|
<a itemprop="sameAs" href="https://www.librarything.com/author/{{ author.librarything_key }}" target="_blank" rel="noopener noreferrer">
|
||||||
{% trans "View on LibraryThing" %}
|
{% trans "View on LibraryThing" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -127,7 +122,7 @@
|
||||||
|
|
||||||
{% if author.goodreads_key %}
|
{% if author.goodreads_key %}
|
||||||
<div>
|
<div>
|
||||||
<a itemprop="sameAs" href="https://www.goodreads.com/author/show/{{ author.goodreads_key }}" target="_blank" rel="noopener">
|
<a itemprop="sameAs" href="https://www.goodreads.com/author/show/{{ author.goodreads_key }}" target="_blank" rel="noopener noreferrer">
|
||||||
{% trans "View on Goodreads" %}
|
{% trans "View on Goodreads" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -137,26 +132,30 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr aria-hidden="true">
|
<div class="column">
|
||||||
|
{% if author.bio %}
|
||||||
|
{% include "snippets/trimmed_text.html" with full=author.bio trim_length=200 %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="block">
|
<h2 class="title is-4">{% blocktrans with name=author.name %}Books by {{ name }}{% endblocktrans %}</h2>
|
||||||
<h2 class="title is-4">{% blocktrans with name=author.name %}Books by {{ name }}{% endblocktrans %}</h2>
|
<div class="columns is-multiline is-mobile">
|
||||||
<div class="columns is-multiline is-mobile">
|
{% for book in books %}
|
||||||
{% for book in books %}
|
{% with book=book.default_edition %}
|
||||||
<div class="column is-one-fifth-tablet is-half-mobile is-flex is-flex-direction-column">
|
<div class="column is-one-fifth-tablet is-half-mobile is-flex is-flex-direction-column">
|
||||||
<div class="is-flex-grow-1">
|
<div class="is-flex-grow-1">
|
||||||
{% include 'landing/small-book.html' with book=book %}
|
{% include 'landing/small-book.html' with book=book %}
|
||||||
|
</div>
|
||||||
|
{% include 'snippets/shelve_button/shelve_button.html' with book=book %}
|
||||||
</div>
|
</div>
|
||||||
{% include 'snippets/shelve_button/shelve_button.html' with book=book %}
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% include 'snippets/pagination.html' with page=books %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
{% include 'snippets/pagination.html' with page=books %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_tags %}
|
{% load book_display_tags %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
@ -122,7 +122,7 @@
|
||||||
{% trans "Load data" as button_text %}
|
{% trans "Load data" as button_text %}
|
||||||
{% if book.openlibrary_key %}
|
{% if book.openlibrary_key %}
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ book.openlibrary_link }}" target="_blank" rel="noopener">
|
<a href="{{ book.openlibrary_link }}" target="_blank" rel="noopener noreferrer">
|
||||||
{% trans "View on OpenLibrary" %}
|
{% trans "View on OpenLibrary" %}
|
||||||
</a>
|
</a>
|
||||||
{% if request.user.is_authenticated and perms.bookwyrm.edit_book %}
|
{% if request.user.is_authenticated and perms.bookwyrm.edit_book %}
|
||||||
|
@ -136,7 +136,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if book.inventaire_id %}
|
{% if book.inventaire_id %}
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ book.inventaire_link }}" target="_blank" rel="noopener">
|
<a href="{{ book.inventaire_link }}" target="_blank" rel="noopener noreferrer">
|
||||||
{% trans "View on Inventaire" %}
|
{% trans "View on Inventaire" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@
|
||||||
{% include 'snippets/toggle/open_button.html' with text=button_text controls_text="add_description" controls_uid=book.id focus="id_description" hide_active=True id="hide_description" %}
|
{% include 'snippets/toggle/open_button.html' with text=button_text controls_text="add_description" controls_uid=book.id focus="id_description" hide_active=True id="hide_description" %}
|
||||||
|
|
||||||
<div class="box is-hidden" id="add_description_{{ book.id }}">
|
<div class="box is-hidden" id="add_description_{{ book.id }}">
|
||||||
<form name="add-description" method="POST" action="/add-description/{{ book.id }}">
|
<form name="add-description" method="POST" action="{% url "add-description" book.id %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<p class="fields is-grouped">
|
<p class="fields is-grouped">
|
||||||
<label class="label" for="id_description_{{ book.id }}">{% trans "Description:" %}</label>
|
<label class="label" for="id_description_{{ book.id }}">{% trans "Description:" %}</label>
|
||||||
|
@ -237,29 +237,21 @@
|
||||||
<h2 class="title is-5">{% trans "Your reading activity" %}</h2>
|
<h2 class="title is-5">{% trans "Your reading activity" %}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
{% trans "Add read dates" as button_text %}
|
<button class="button is-small" data-modal-open="add-readthrough">
|
||||||
{% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="plus" class="is-small" controls_text="add_readthrough" focus="add_readthrough_focus_" %}
|
<span class="icon icon-plus m-mobile-0" aria-hidden="true"></span>
|
||||||
|
<span class="is-sr-only-mobile">
|
||||||
|
{% trans "Add read dates" %}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<section class="is-hidden box" id="add_readthrough">
|
{% include "readthrough/readthrough_modal.html" with id="add-readthrough" %}
|
||||||
<form name="add-readthrough" action="/create-readthrough" method="post">
|
|
||||||
{% include 'snippets/readthrough_form.html' with readthrough=None %}
|
|
||||||
<div class="field is-grouped">
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-primary" type="submit">{% trans "Create" %}</button>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
{% trans "Cancel" as button_text %}
|
|
||||||
{% include 'snippets/toggle/close_button.html' with text=button_text controls_text="add_readthrough" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
{% if not readthroughs.exists %}
|
{% if not readthroughs.exists %}
|
||||||
<p>{% trans "You don't have any reading activity for this book." %}</p>
|
<p>{% trans "You don't have any reading activity for this book." %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for readthrough in readthroughs %}
|
{% for readthrough in readthroughs %}
|
||||||
{% include 'book/readthrough.html' with readthrough=readthrough %}
|
{% include 'readthrough/readthrough_list.html' with readthrough=readthrough %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
<hr aria-hidden="true">
|
<hr aria-hidden="true">
|
||||||
|
@ -327,7 +319,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-one-fifth">
|
<div class="column is-one-fifth is-clipped">
|
||||||
{% if book.subjects %}
|
{% if book.subjects %}
|
||||||
<section class="content block">
|
<section class="content block">
|
||||||
<h2 class="title is-5">{% trans "Subjects" %}</h2>
|
<h2 class="title is-5">{% trans "Subjects" %}</h2>
|
||||||
|
@ -352,11 +344,11 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if lists.exists or request.user.list_set.exists %}
|
{% if lists.exists or request.user.list_set.exists %}
|
||||||
<section class="content block">
|
<section class="content block is-clipped">
|
||||||
<h2 class="title is-5">{% trans "Lists" %}</h2>
|
<h2 class="title is-5">{% trans "Lists" %}</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{% for list in lists %}
|
{% for list in lists %}
|
||||||
<li><a href="{{ list.local_path }}">{{ list.name }}</a></li>
|
<li><a href="{{ list.local_path }}">{{ list.name }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -366,7 +358,7 @@
|
||||||
<input type="hidden" name="book" value="{{ book.id }}">
|
<input type="hidden" name="book" value="{{ book.id }}">
|
||||||
<label class="label" for="id_list">{% trans "Add to list" %}</label>
|
<label class="label" for="id_list">{% trans "Add to list" %}</label>
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<div class="select control">
|
<div class="select control is-clipped">
|
||||||
<select name="list" id="id_list">
|
<select name="list" id="id_list">
|
||||||
{% for list in user.list_set.all %}
|
{% for list in user.list_set.all %}
|
||||||
<option value="{{ list.id }}">{{ list.name }}</option>
|
<option value="{{ list.id }}">{{ list.name }}</option>
|
||||||
|
@ -381,6 +373,10 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<section class="content block">
|
||||||
|
{% include "book/file_links/links.html" %}
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -390,4 +386,5 @@
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{% static "js/vendor/tabs.js" %}?v={{ js_cache }}"></script>
|
<script src="{% static "js/vendor/tabs.js" %}?v={{ js_cache }}"></script>
|
||||||
|
<script src="{% static "js/autocomplete.js" %}?v={{ js_cache }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
{% extends 'components/modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block modal-title %}
|
||||||
|
{% trans "Add file link" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-form-open %}
|
||||||
|
<form name="add-link" method="POST" action="{% url 'file-link-add' book.id %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="book" value="{{ book.id }}">
|
||||||
|
<input type="hidden" name="added_by" value="{{ request.user.id }}">
|
||||||
|
|
||||||
|
<p class="notification">
|
||||||
|
{% trans "Links from unknown domains will need to be approved by a moderator before they are added." %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-four-fifths">
|
||||||
|
<label class="label" for="id_url">{% trans "URL:" %}</label>
|
||||||
|
<input type="url" name="url" maxlength="255" class="input" required="" id="id_url" value="{% firstof file_link_form.url.value "" %}" placeholder="https://..." aria-describedby="desc_name">
|
||||||
|
{% include 'snippets/form_errors.html' with errors_list=file_link_form.url.errors id="desc_url" %}
|
||||||
|
</div>
|
||||||
|
<div class="column is-one-fifth">
|
||||||
|
<label class="label" for="id_filetype">{% trans "File type:" %}</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="filetype"
|
||||||
|
maxlength="50"
|
||||||
|
class="input"
|
||||||
|
required=""
|
||||||
|
id="id_filetype"
|
||||||
|
value="{% firstof file_link_form.filetype.value '' %}"
|
||||||
|
placeholder="ePub"
|
||||||
|
list="mimetypes-list"
|
||||||
|
data-autocomplete="mimetype"
|
||||||
|
>
|
||||||
|
<datalist id="mimetypes-list"></datalist>
|
||||||
|
{% include 'snippets/form_errors.html' with errors_list=file_link_form.filetype.errors id="desc_filetype" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label" for="id_availability">
|
||||||
|
{% trans "Availability:" %}
|
||||||
|
</label>
|
||||||
|
<div class="select">
|
||||||
|
{{ file_link_form.availability }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<button class="button is-primary" type="submit">{% trans "Save" %}</button>
|
||||||
|
{% if not static %}
|
||||||
|
<button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
{% block modal-form-close %}</form>{% endblock %}
|
|
@ -0,0 +1,114 @@
|
||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Edit links" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<header class="block content">
|
||||||
|
<h1 class="title">
|
||||||
|
{% blocktrans with title=book|book_title %}
|
||||||
|
Links for "<em>{{ title }}</em>"
|
||||||
|
{% endblocktrans %}
|
||||||
|
</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><a href="{% url 'book' book.id %}">{{ book|book_title }}</a></li>
|
||||||
|
<li class="is-active">
|
||||||
|
<a href="#" aria-current="page">
|
||||||
|
{% trans "Edit links" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="block content">
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="is-striped is-fullwidth">
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "URL" %}</th>
|
||||||
|
<th>{% trans "Added by" %}</th>
|
||||||
|
<th>{% trans "Filetype" %}</th>
|
||||||
|
<th>{% trans "Domain" %}</th>
|
||||||
|
<th>{% trans "Status" %}</th>
|
||||||
|
<th colspan="2">{% trans "Actions" %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for link in links %}
|
||||||
|
<tr>
|
||||||
|
<td class="overflow-wrap-anywhere">
|
||||||
|
<a href="{{ link.url }}" target="_blank" rel="noopener noreferrer">{{ link.url }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'user-feed' link.added_by.id %}">{{ link.added_by.display_name }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ link.filelink.filetype }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ link.domain.name }}
|
||||||
|
<p>
|
||||||
|
<a href="{% url 'report-link' link.added_by.id link.id %}">{% trans "Report spam" %}</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% with status=link.domain.status %}
|
||||||
|
<span class="tag {% if status == 'blocked' %}has-background-danger{% elif status == 'approved' %}has-background-primary{% endif %}">
|
||||||
|
<span class="icon" aria-hidden="true">
|
||||||
|
<span class="icon-{% if status == 'blocked' %}x{% elif status == 'approved' %}check{% else %}lock{% endif %}"></span>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ link.domain.get_status_display }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{% endwith %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<form name="edit-link" class="control" method="post" action="{% url 'file-link' book.id link.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="url" value="{{ link.form.url.value }}">
|
||||||
|
<input type="hidden" name="filetype" value="{{ link.form.filetype.value }}">
|
||||||
|
<input type="hidden" name="added_by" value="{{ link.form.added_by.value }}">
|
||||||
|
<input type="hidden" name="book" value="{{ link.form.book.value }}">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control">
|
||||||
|
<div class="select">
|
||||||
|
{{ link.form.availability }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-primary" type="submit">{% trans "Save" %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<form name="delete-link-{{ link.id }}" class="control" method="post" action="{% url 'file-link-delete' book.id link.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button class="button is-danger is-light" type="submit">Delete link</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% if not book.file_links.exists %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5"><em>{% trans "No links available for this book." %}</em></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% url 'file-link-add' book.id as fallback_url %}
|
||||||
|
<form name="add-link" method="get" action="{{ fallback_url }}">
|
||||||
|
<button class="button" type="submit" data-modal-open="add-links">
|
||||||
|
<span class="icon icon-plus m-0-mobile" aria-hidden="true"></span>
|
||||||
|
<span class="is-sr-only-mobile">
|
||||||
|
{% trans "Add link to file" %}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans "File Links" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include "book/file_links/add_link_modal.html" with book=book active=True static=True id="file-link" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script src="{% static "js/autocomplete.js" %}?v={{ js_cache }}"></script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,58 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% load book_display_tags %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% get_book_file_links book as links %}
|
||||||
|
{% if links.exists or request.user.is_authenticated %}
|
||||||
|
<header class="columns is-mobile mb-0">
|
||||||
|
<div class="column">
|
||||||
|
<h2 class="title is-5">{% trans "Get a copy" %}</h2>
|
||||||
|
</div>
|
||||||
|
{% if can_edit_book %}
|
||||||
|
<div class="column is-narrow">
|
||||||
|
{% url 'file-link-add' book.id as fallback_url %}
|
||||||
|
<form name="add-link" method="get" action="{{ fallback_url }}">
|
||||||
|
<button class="button is-small" type="submit" data-modal-open="add-links">
|
||||||
|
<span class="icon icon-plus">
|
||||||
|
<span class="is-sr-only">
|
||||||
|
{% trans "Add link to file" %}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</header>
|
||||||
|
{% if links %}
|
||||||
|
<ul class="mt-0">
|
||||||
|
{% for link in links.all %}
|
||||||
|
{% join "verify" link.id as verify_modal %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ link.url }}" rel="noopener noreferrer" target="_blank" title="{{ link.url }}" data-modal-open="{{ verify_modal }}">{{ link.name }}</a>
|
||||||
|
({{ link.filetype }})
|
||||||
|
|
||||||
|
{% if link.availability != "free" %}
|
||||||
|
<p class="help">
|
||||||
|
{{ link.get_availability_display }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% for link in links.all %}
|
||||||
|
{% join "verify" link.id as verify_modal %}
|
||||||
|
{% include "book/file_links/verification_modal.html" with id=verify_modal %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<em>{% trans "No links available" %}</em>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if can_edit_book and links.exists %}
|
||||||
|
<a href="{% url 'file-link' book.id %}" class="is-pulled-right">
|
||||||
|
<span class="icon icon-pencil" aria-hidden="true"></span>
|
||||||
|
<span>{% trans "Edit links" %}</span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% include 'book/file_links/add_link_modal.html' with book=book id="add-links" %}
|
||||||
|
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,29 @@
|
||||||
|
{% extends 'components/modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-title %}
|
||||||
|
{% trans "Leaving BookWyrm" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
|
||||||
|
{% blocktrans trimmed with link_url=link.url %}
|
||||||
|
This link is taking you to: <code>{{ link_url }}</code>.<br>
|
||||||
|
Is that where you'd like to go?
|
||||||
|
{% endblocktrans %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<a href="{{ link.url }}" target="_blank" rel="noopener noreferrer" class="button is-primary">{% trans "Continue" %}</a>
|
||||||
|
<button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
|
||||||
|
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<div class="has-text-right is-flex-grow-1">
|
||||||
|
<a href="{% url 'report-link' link.added_by.id link.id %}">{% trans "Report spam" %}</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% load bookwyrm_tags %}
|
{% load rating_tags %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load status_display %}
|
{% load status_display %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% load bookwyrm_tags %}
|
{% load landing_page_tags %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load status_display %}
|
{% load status_display %}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends 'feed/layout.html' %}
|
{% extends 'feed/layout.html' %}
|
||||||
|
{% load feed_page_tags %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_tags %}
|
|
||||||
|
|
||||||
{% block opengraph_images %}
|
{% block opengraph_images %}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_tags %}
|
{% load feed_page_tags %}
|
||||||
|
|
||||||
{% suggested_books as suggested_books %}
|
{% suggested_books as suggested_books %}
|
||||||
<section class="block">
|
<section class="block">
|
||||||
|
@ -16,10 +16,7 @@
|
||||||
{% with shelf_counter=forloop.counter %}
|
{% with shelf_counter=forloop.counter %}
|
||||||
<li>
|
<li>
|
||||||
<p>
|
<p>
|
||||||
{% if shelf.identifier == 'to-read' %}{% trans "To Read" %}
|
{% include "snippets/translated_shelf_name.html" with shelf=shelf %}
|
||||||
{% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %}
|
|
||||||
{% elif shelf.identifier == 'read' %}{% trans "Read" %}
|
|
||||||
{% else %}{{ shelf.name }}{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
<div class="tabs is-small is-toggle">
|
<div class="tabs is-small is-toggle">
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{% extends 'groups/layout.html' %}
|
{% extends 'groups/layout.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_tags %}
|
{% load group_tags %}
|
||||||
{% load bookwyrm_group_tags %}
|
|
||||||
{% load markdown %}
|
{% load markdown %}
|
||||||
|
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_group_tags %}
|
{% load group_tags %}
|
||||||
|
|
||||||
{% block title %}{{ group.name }}{% endblock %}
|
{% block title %}{{ group.name }}{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
{% load bookwyrm_tags %}
|
{% load group_tags %}
|
||||||
{% load bookwyrm_group_tags %}
|
|
||||||
|
|
||||||
<h2 class="title is-5">Group Members</h2>
|
<h2 class="title is-5">Group Members</h2>
|
||||||
{% if group.user == request.user %}
|
{% if group.user == request.user %}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
|
|
||||||
{% block tooltip_content %}
|
{% block tooltip_content %}
|
||||||
|
|
||||||
{% trans 'You can download your Goodreads data from the <a href="https://www.goodreads.com/review/import" target="_blank" rel="noopener">Import/Export page</a> of your Goodreads account.' %}
|
{% trans 'You can download your Goodreads data from the <a href="https://www.goodreads.com/review/import" target="_blank" rel="noopener noreferrer">Import/Export page</a> of your Goodreads account.' %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
{% extends 'landing/layout.html' %}
|
{% extends 'landing/layout.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load cache %}
|
{% load cache %}
|
||||||
|
{% load landing_page_tags %}
|
||||||
|
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
|
|
||||||
<div class="block is-hidden-tablet">
|
<div class="block is-hidden-tablet">
|
||||||
<h2 class="title has-text-centered">{% trans "Recent Books" %}</h2>
|
<h2 class="title has-text-centered">{% trans "Recent Books" %}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% cache 60 * 60 %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
|
{% cache 60 * 60 LANGUAGE_CODE %}
|
||||||
|
{% get_landing_books as books %}
|
||||||
<section class="tile is-ancestor">
|
<section class="tile is-ancestor">
|
||||||
<div class="tile is-vertical is-6">
|
<div class="tile is-vertical is-6">
|
||||||
<div class="tile is-parent">
|
<div class="tile is-parent">
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% load bookwyrm_tags %}
|
{% load book_display_tags %}
|
||||||
|
{% load rating_tags %}
|
||||||
{% load markdown %}
|
{% load markdown %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% load bookwyrm_tags %}
|
{% load rating_tags %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% if book %}
|
{% if book %}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{% extends 'embed-layout.html' %}
|
{% extends 'embed-layout.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_tags %}
|
{% load book_display_tags %}
|
||||||
{% load bookwyrm_group_tags %}
|
{% load rating_tags %}
|
||||||
|
{% load group_tags %}
|
||||||
{% load markdown %}
|
{% load markdown %}
|
||||||
|
|
||||||
{% block title %}{% blocktrans with list_name=list.name owner=list.user.display_name %}{{ list_name }}, a list by {{owner}}{% endblocktrans %}{% endblock title %}
|
{% block title %}{% blocktrans with list_name=list.name owner=list.user.display_name %}{{ list_name }}, a list by {{owner}}{% endblocktrans %}{% endblock title %}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{% extends 'lists/layout.html' %}
|
{% extends 'lists/layout.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_tags %}
|
{% load rating_tags %}
|
||||||
{% load bookwyrm_group_tags %}
|
{% load book_display_tags %}
|
||||||
|
{% load group_tags %}
|
||||||
{% load markdown %}
|
{% load markdown %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block primary_link %}{% spaceless %}
|
{% block primary_link %}{% spaceless %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{% load bookwyrm_tags %}
|
{% load notification_page_tags %}
|
||||||
{% related_status notification as related_status %}
|
{% related_status notification as related_status %}
|
||||||
<div class="box is-shadowless has-background-white-ter {% if notification.id in unread %} is-primary{% endif %}">
|
<div class="notification {% if notification.id in unread %}has-background-primary{% endif %}">
|
||||||
<div class="columns is-mobile">
|
<div class="columns is-mobile {% if notification.id in unread %}has-text-white{% else %}has-text-grey{% endif %}">
|
||||||
<div class="column is-narrow is-size-3 {% if notification.id in unread%}has-text-white{% else %}has-text-grey{% endif %}">
|
<div class="column is-narrow is-size-3">
|
||||||
<a class="has-text-dark" href="{% block primary_link %}{% endblock %}">
|
<a class="icon" href="{% block primary_link %}{% endblock %}">
|
||||||
{% block icon %}{% endblock %}
|
{% block icon %}{% endblock %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'notifications/items/item_layout.html' %}
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns="http://a9.com/-/spec/opensearch/1.1/"
|
xmlns="http://a9.com/-/spec/opensearch/1.1/"
|
||||||
xmlns:moz="http://www.mozilla.org/2006/browser/search/"
|
xmlns:moz="http://www.mozilla.org/2006/browser/search/"
|
||||||
>
|
>
|
||||||
<ShortName>BW</ShortName>
|
<ShortName>{{ site_name }}</ShortName>
|
||||||
<Description>{% blocktrans trimmed with site_name=site.name %}
|
<Description>{% blocktrans trimmed with site_name=site.name %}
|
||||||
{{ site_name }} search
|
{{ site_name }} search
|
||||||
{% endblocktrans %}</Description>
|
{% endblocktrans %}</Description>
|
||||||
|
|
|
@ -34,26 +34,26 @@
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{{ form.avatar }}
|
{{ form.avatar }}
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=form.avatar.errors id="desc_avatar" %}
|
{% include 'snippets/form_errors.html' with errors_list=form.avatar.errors id="desc_avatar" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_name">{% trans "Display name:" %}</label>
|
<label class="label" for="id_name">{% trans "Display name:" %}</label>
|
||||||
{{ form.name }}
|
{{ form.name }}
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=form.name.errors id="desc_name" %}
|
{% include 'snippets/form_errors.html' with errors_list=form.name.errors id="desc_name" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_summary">{% trans "Summary:" %}</label>
|
<label class="label" for="id_summary">{% trans "Summary:" %}</label>
|
||||||
{{ form.summary }}
|
{{ form.summary }}
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=form.summary.errors id="desc_summary" %}
|
{% include 'snippets/form_errors.html' with errors_list=form.summary.errors id="desc_summary" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_email">{% trans "Email address:" %}</label>
|
<label class="label" for="id_email">{% trans "Email address:" %}</label>
|
||||||
{{ form.email }}
|
{{ form.email }}
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=form.email.errors id="desc_email" %}
|
{% include 'snippets/form_errors.html' with errors_list=form.email.errors id="desc_email" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% blocktrans trimmed with title=book|book_title %}
|
||||||
|
Update read dates for "<em>{{ title }}</em>"
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% include "readthrough/readthrough_modal.html" with book=book active=True static=True %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -4,6 +4,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="id" value="{{ readthrough.id }}">
|
<input type="hidden" name="id" value="{{ readthrough.id }}">
|
||||||
<input type="hidden" name="book" value="{{ book.id }}">
|
<input type="hidden" name="book" value="{{ book.id }}">
|
||||||
|
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" tabindex="0" id="add_readthrough_focus_{{ readthrough.id }}" for="id_start_date_{{ readthrough.id }}">
|
<label class="label" tabindex="0" id="add_readthrough_focus_{{ readthrough.id }}" for="id_start_date_{{ readthrough.id }}">
|
||||||
{% trans "Started reading" %}
|
{% trans "Started reading" %}
|
|
@ -3,7 +3,7 @@
|
||||||
{% load tz %}
|
{% load tz %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div id="hide_edit_readthrough_{{ readthrough.id }}" class="box is-shadowless has-background-white-bis">
|
<div class="box is-shadowless has-background-white-bis">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{% trans "Progress Updates:" %}
|
{% trans "Progress Updates:" %}
|
||||||
|
@ -58,7 +58,11 @@
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{% trans "Edit read dates" as button_text %}
|
{% trans "Edit read dates" as button_text %}
|
||||||
{% include 'snippets/toggle/toggle_button.html' with class="is-small" text=button_text icon="pencil" controls_text="edit_readthrough" controls_uid=readthrough.id focus="edit_readthrough" %}
|
<button class="button is-small" type="button" data-modal-open="edit_readthrough_{{ readthrough.id }}">
|
||||||
|
<span class="icon icon-pencil" title="{{ button_text }}">
|
||||||
|
<span class="is-sr-only">{{ button_text }}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{% trans "Delete these read dates" as button_text %}
|
{% trans "Delete these read dates" as button_text %}
|
||||||
|
@ -74,16 +78,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box is-hidden" id="edit_readthrough_{{ readthrough.id }}" tabindex="0">
|
{% join "edit_readthrough" readthrough.id as edit_modal_id %}
|
||||||
<h3 class="title is-5">{% trans "Edit read dates" %}</h3>
|
{% include "readthrough/readthrough_modal.html" with readthrough=readthrough id=edit_modal_id %}
|
||||||
<form name="edit-readthrough" action="/edit-readthrough" method="post">
|
{% join "delete_readthrough" readthrough.id as delete_modal_id %}
|
||||||
{% include 'snippets/readthrough_form.html' with readthrough=readthrough %}
|
{% include 'readthrough/delete_readthrough_modal.html' with id=delete_modal_id %}
|
||||||
<div class="field is-grouped">
|
|
||||||
<button class="button is-primary" type="submit">{% trans "Save" %}</button>
|
|
||||||
{% trans "Cancel" as button_text %}
|
|
||||||
{% include 'snippets/toggle/close_button.html' with text=button_text controls_text="edit_readthrough" controls_uid=readthrough.id %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% join "delete_readthrough" readthrough.id as modal_id %}
|
|
||||||
{% include 'book/delete_readthrough_modal.html' with id=modal_id %}
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
{% extends "components/modal.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% block modal-title %}
|
||||||
|
|
||||||
|
{% if readthrough %}
|
||||||
|
{% blocktrans trimmed with title=book|book_title %}
|
||||||
|
Update read dates for "<em>{{ title }}</em>"
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% else %}
|
||||||
|
{% blocktrans trimmed with title=book|book_title %}
|
||||||
|
Add read dates for "<em>{{ title }}</em>"
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-form-open %}
|
||||||
|
<form name="add-readthrough-{{ readthrough.id }}" action="/create-readthrough" method="post">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<input type="hidden" name="id" value="{{ readthrough.id }}">
|
||||||
|
<input type="hidden" name="book" value="{{ book.id }}">
|
||||||
|
<input type="hidden" name="user" value="{{ request.user.id }}">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" tabindex="0" id="add_readthrough_focus_{{ readthrough.id }}" for="id_start_date_{{ readthrough.id }}">
|
||||||
|
{% trans "Started reading" %}
|
||||||
|
</label>
|
||||||
|
{% firstof form.start_date.value readthrough.start_date|date:"Y-m-d" as value %}
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
name="start_date"
|
||||||
|
class="input"
|
||||||
|
id="id_start_date_{{ readthrough.id }}"
|
||||||
|
value="{{ value }}"
|
||||||
|
aria-describedby="desc_start_date"
|
||||||
|
>
|
||||||
|
{% include 'snippets/form_errors.html' with errors_list=form.start_date.errors id="desc_start_date" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Only show progress for editing existing readthroughs #}
|
||||||
|
{% if readthrough.id and not readthrough.finish_date %}
|
||||||
|
{% join "id_progress" readthrough.id as field_id %}
|
||||||
|
<label class="label" for="{{ field_id }}">
|
||||||
|
{% trans "Progress" %}
|
||||||
|
</label>
|
||||||
|
{% include "snippets/progress_field.html" with id=field_id %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="id_finish_date_{{ readthrough.id }}">
|
||||||
|
{% trans "Finished reading" %}
|
||||||
|
</label>
|
||||||
|
{% firstof form.finish_date.value readthrough.finish_date|date:"Y-m-d" as value %}
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
name="finish_date"
|
||||||
|
class="input"
|
||||||
|
id="id_finish_date_{{ readthrough.id }}"
|
||||||
|
value="{{ value }}"
|
||||||
|
aria-describedby="desc_finish_date"
|
||||||
|
>
|
||||||
|
{% include 'snippets/form_errors.html' with errors_list=form.finish_date.errors id="desc_finish_date" %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<button class="button is-primary" type="submit">{% trans "Save" %}</button>
|
||||||
|
{% if not static %}
|
||||||
|
<button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-form-close %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans "Report" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include "snippets/report_modal.html" with user=user active=True static=True %}
|
||||||
|
{% endblock %}
|
|
@ -63,7 +63,7 @@
|
||||||
<strong>
|
<strong>
|
||||||
<a
|
<a
|
||||||
href="{{ result.view_link|default:result.key }}"
|
href="{{ result.view_link|default:result.key }}"
|
||||||
rel="noopener"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>{{ result.title }}</a>
|
>{{ result.title }}</a>
|
||||||
</strong>
|
</strong>
|
||||||
|
@ -75,9 +75,11 @@
|
||||||
<form class="mt-1" action="/resolve-book" method="post">
|
<form class="mt-1" action="/resolve-book" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="remote_id" value="{{ result.key }}">
|
<input type="hidden" name="remote_id" value="{{ result.key }}">
|
||||||
<button type="submit" class="button is-small is-link">
|
<div class="control">
|
||||||
{% trans "Import book" %}
|
<button type="submit" class="button is-small is-link">
|
||||||
</button>
|
{% trans "Import book" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -48,6 +48,17 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if pending_domains %}
|
||||||
|
<div class="column">
|
||||||
|
<a href="{% url 'settings-link-domain' %}" class="notification is-primary is-block">
|
||||||
|
{% blocktrans trimmed count counter=pending_domains with display_count=pending_domains|intcomma %}
|
||||||
|
{{ display_count }} domain needs review
|
||||||
|
{% plural %}
|
||||||
|
{{ display_count }} domains need review
|
||||||
|
{% endblocktrans %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if not site.allow_registration and site.allow_invite_requests and invite_requests %}
|
{% if not site.allow_registration and site.allow_invite_requests and invite_requests %}
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a href="{% url 'settings-invite-requests' %}" class="notification is-block is-success is-light">
|
<a href="{% url 'settings-invite-requests' %}" class="notification is-block is-success is-light">
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_file">JSON data:</label>
|
<label class="label" for="id_file">JSON data:</label>
|
||||||
<aside class="help">
|
<aside class="help">
|
||||||
Expects a json file in the format provided by <a href="https://fediblock.org/" target="_blank" rel="noopener">FediBlock</a>, with a list of entries that have <code>instance</code> and <code>url</code> fields. For example:
|
Expects a json file in the format provided by <a href="https://fediblock.org/" target="_blank" rel="noopener noreferrer">FediBlock</a>, with a list of entries that have <code>instance</code> and <code>url</code> fields. For example:
|
||||||
<pre>
|
<pre>
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
|
@ -62,6 +62,10 @@
|
||||||
{% url 'settings-ip-blocks' as url %}
|
{% url 'settings-ip-blocks' as url %}
|
||||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "IP Address Blocklist" %}</a>
|
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "IP Address Blocklist" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
{% url 'settings-link-domain' as url %}
|
||||||
|
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Link Domains" %}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.bookwyrm.edit_instance_settings %}
|
{% if perms.bookwyrm.edit_instance_settings %}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends 'components/modal.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-title %}
|
||||||
|
{% blocktrans with url=domain.domain %}Set display name for {{ url }}{% endblocktrans %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-form-open %}
|
||||||
|
<form name="edit-domain-{{ domain.id }}" method="post" action="{% url 'settings-link-domain' status=status domain_id=domain.id %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
{% csrf_token %}
|
||||||
|
<label class="label" for="id_name">{% trans "Name:" %}</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="text" id="id_name" class="input" name="name" value="{{ domain.name }}" required>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<button type="submit" class="button is-primary">{% trans "Set" %}</button>
|
||||||
|
<button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-form-close %}</form>{% endblock %}
|
|
@ -0,0 +1,106 @@
|
||||||
|
{% extends 'settings/layout.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Link Domains" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}{% trans "Link Domains" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block panel %}
|
||||||
|
<p class="notification block">
|
||||||
|
{% trans "Link domains must be approved before they are shown on book pages. Please make sure that the domains are not hosting spam, malicious code, or deceptive links before approving." %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<div class="tabs">
|
||||||
|
<ul>
|
||||||
|
{% url 'settings-link-domain' status='pending' as url %}
|
||||||
|
<li {% if request.path in url %}class="is-active" aria-current="page"{% endif %}>
|
||||||
|
<a href="{{ url }}">{% trans "Pending" %} ({{ counts.pending }})</a>
|
||||||
|
</li>
|
||||||
|
{% url 'settings-link-domain' status='approved' as url %}
|
||||||
|
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||||
|
<a href="{{ url }}">{% trans "Approved" %} ({{ counts.approved }})</a>
|
||||||
|
</li>
|
||||||
|
{% url 'settings-link-domain' status='blocked' as url %}
|
||||||
|
<li {% if url in request.path %}class="is-active" aria-current="page"{% endif %}>
|
||||||
|
<a href="{{ url }}">{% trans "Blocked" %} ({{ counts.blocked }})</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for domain in domains %}
|
||||||
|
{% join "domain" domain.id as domain_modal %}
|
||||||
|
<div class="box content" id="{{ domain.id }}">
|
||||||
|
<div class="columns is-mobile">
|
||||||
|
<header class="column">
|
||||||
|
<h2 class="title is-5">
|
||||||
|
{{ domain.name }}
|
||||||
|
(<a href="http://{{ domain.domain }}" target="_blank" rel="noopener noreferrer">{{ domain.domain }}</a>)
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<button type="button" class="button" data-modal-open="{{ domain_modal }}">
|
||||||
|
<span class="icon icon-pencil m-0-mobile" aria-hidden="treu"></span>
|
||||||
|
<span class="is-sr-only-mobile">{% trans "Set display name" %}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<details class="details-panel">
|
||||||
|
<summary>
|
||||||
|
<span role="heading" aria-level="3" class="title is-6 mb-0">
|
||||||
|
{% trans "View links" %}
|
||||||
|
({{ domain.links.count }})
|
||||||
|
</span>
|
||||||
|
<span class="details-close icon icon-x" aria-hidden></span>
|
||||||
|
</summary>
|
||||||
|
|
||||||
|
<div class="table-container mt-4">
|
||||||
|
{% include "settings/link_domains/link_table.html" with links=domain.links.all|slice:10 %}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include "settings/link_domains/edit_domain_modal.html" with domain=domain id=domain_modal %}
|
||||||
|
|
||||||
|
<div class="field has-addons">
|
||||||
|
{% if status != "approved" %}
|
||||||
|
<form
|
||||||
|
name="domain-{{ domains.id }}-approve"
|
||||||
|
class="control"
|
||||||
|
method="post"
|
||||||
|
action="{% url 'settings-link-domain-status' domain.id 'approved' %}"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="button is-success is-light">{% trans "Approve" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% if status != "blocked" %}
|
||||||
|
<form
|
||||||
|
name="domain-{{ domains.id }}-block"
|
||||||
|
class="control"
|
||||||
|
method="post"
|
||||||
|
action="{% url 'settings-link-domain-status' domain.id 'blocked' %}"
|
||||||
|
>
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="button is-danger is-light">{% trans "Block" %}</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if not domains.exists %}
|
||||||
|
{% if status == "approved" %}
|
||||||
|
<em>{% trans "No domains currently approved" %}</em>
|
||||||
|
{% elif status == "pending" %}
|
||||||
|
<em>{% trans "No domains currently pending" %}</em>
|
||||||
|
{% else %}
|
||||||
|
<em>{% trans "No domains currently blocked" %}</em>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
<table class="is-striped is-fullwidth">
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "URL" %}</th>
|
||||||
|
<th>{% trans "Added by" %}</th>
|
||||||
|
<th>{% trans "Filetype" %}</th>
|
||||||
|
<th>{% trans "Book" %}</th>
|
||||||
|
{% block additional_headers %}{% endblock %}
|
||||||
|
</tr>
|
||||||
|
{% for link in links %}
|
||||||
|
<tr>
|
||||||
|
<td class="overflow-wrap-anywhere">
|
||||||
|
<a href="{{ link.url }}" target="_blank" rel="noopener noreferrer">{{ link.url }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'settings-user' link.added_by.id %}">@{{ link.added_by|username }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if link.filelink.filetype %}
|
||||||
|
{{ link.filelink.filetype }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{% if link.filelink.book %}
|
||||||
|
{% with book=link.filelink.book %}
|
||||||
|
{% include "snippets/book_titleby.html" with book=book %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{% block additional_data %}{% endblock %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% if not links %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7"><em>{% trans "No links available for this domain." %}</em></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</table>
|
|
@ -2,10 +2,12 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
|
|
||||||
{% block title %}{% blocktrans with report_id=report.id username=report.user.username %}Report #{{ report_id }}: {{ username }}{% endblocktrans %}{% endblock %}
|
{% block title %}
|
||||||
|
{% include "settings/reports/report_header.html" with report=report %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
{% blocktrans with report_id=report.id username=report.user.username %}Report #{{ report_id }}: {{ username }}{% endblocktrans %}
|
{% include "settings/reports/report_header.html" with report=report %}
|
||||||
<a href="{% url 'settings-reports' %}" class="has-text-weight-normal help">{% trans "Back to reports" %}</a>
|
<a href="{% url 'settings-reports' %}" class="has-text-weight-normal help">{% trans "Back to reports" %}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -15,6 +17,36 @@
|
||||||
{% include 'settings/reports/report_preview.html' with report=report %}
|
{% include 'settings/reports/report_preview.html' with report=report %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if report.statuses.exists %}
|
||||||
|
<div class="block">
|
||||||
|
<h3 class="title is-4">{% trans "Reported statuses" %}</h3>
|
||||||
|
<ul>
|
||||||
|
{% for status in report.statuses.select_subclasses.all %}
|
||||||
|
<li>
|
||||||
|
{% if status.deleted %}
|
||||||
|
<em>{% trans "Status has been deleted" %}</em>
|
||||||
|
{% else %}
|
||||||
|
{% include 'snippets/status/status.html' with status=status moderation_mode=True %}
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if report.links.exists %}
|
||||||
|
<div class="block">
|
||||||
|
<h3 class="title is-4">{% trans "Reported links" %}</h3>
|
||||||
|
<div class="card block">
|
||||||
|
<div class="card-content content">
|
||||||
|
<div class="table-container">
|
||||||
|
{% include "settings/reports/report_links_table.html" with links=report.links.all %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% include 'settings/users/user_info.html' with user=report.user %}
|
{% include 'settings/users/user_info.html' with user=report.user %}
|
||||||
|
|
||||||
{% include 'settings/users/user_moderation_actions.html' with user=report.user %}
|
{% include 'settings/users/user_moderation_actions.html' with user=report.user %}
|
||||||
|
@ -41,23 +73,4 @@
|
||||||
<button class="button">{% trans "Comment" %}</button>
|
<button class="button">{% trans "Comment" %}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
|
||||||
<h3 class="title is-4">{% trans "Reported statuses" %}</h3>
|
|
||||||
{% if not report.statuses.exists %}
|
|
||||||
<em>{% trans "No statuses reported" %}</em>
|
|
||||||
{% else %}
|
|
||||||
<ul>
|
|
||||||
{% for status in report.statuses.select_subclasses.all %}
|
|
||||||
<li>
|
|
||||||
{% if status.deleted %}
|
|
||||||
<em>{% trans "Status has been deleted" %}</em>
|
|
||||||
{% else %}
|
|
||||||
{% include 'snippets/status/status.html' with status=status moderation_mode=True %}
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
|
{% if report.statuses.exists %}
|
||||||
|
|
||||||
|
{% blocktrans trimmed with report_id=report.id username=report.user|username %}
|
||||||
|
Report #{{ report_id }}: Status posted by @{{ username }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
|
||||||
|
{% elif report.links.exists %}
|
||||||
|
|
||||||
|
{% blocktrans trimmed with report_id=report.id username=report.user|username %}
|
||||||
|
Report #{{ report_id }}: Link added by @{{ username }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% blocktrans trimmed with report_id=report.id username=report.user|username %}
|
||||||
|
Report #{{ report_id }}: User @{{ username }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "settings/link_domains/link_table.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block additional_headers %}
|
||||||
|
<th>{% trans "Domain" %}</th>
|
||||||
|
<th>{% trans "Actions" %}</th>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block additional_data %}
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'settings-link-domain' 'pending' %}#{{ link.domain.id }}">
|
||||||
|
{{ link.domain.domain }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<form>
|
||||||
|
<button type="submit" class="button is-danger is-light">{% trans "Block domain" %}</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
{% endblock %}
|
|
@ -1,9 +1,13 @@
|
||||||
{% extends 'components/card.html' %}
|
{% extends 'components/card.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
|
{% load utilities %}
|
||||||
|
|
||||||
{% block card-header %}
|
{% block card-header %}
|
||||||
<h2 class="card-header-title has-background-white-ter is-block">
|
<h2 class="card-header-title has-background-white-ter is-block">
|
||||||
<a href="{% url 'settings-report' report.id %}">{% blocktrans with report_id=report.id username=report.user.username %}Report #{{ report_id }}: {{ username }}{% endblocktrans %}</a>
|
<a href="{% url 'settings-report' report.id %}">
|
||||||
|
{% include "settings/reports/report_header.html" with report=report %}
|
||||||
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -17,7 +21,7 @@
|
||||||
|
|
||||||
{% block card-footer %}
|
{% block card-footer %}
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
<p>{% blocktrans with username=report.reporter.display_name path=report.reporter.local_path %}Reported by <a href="{{ path }}">{{ username }}</a>{% endblocktrans %}</p>
|
<p>{% blocktrans with username=report.reporter|username path=report.reporter.local_path %}Reported by <a href="{{ path }}">@{{ username }}</a>{% endblocktrans %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
{{ report.created_date | naturaltime }}
|
{{ report.created_date | naturaltime }}
|
||||||
|
|
|
@ -5,71 +5,73 @@
|
||||||
{% trans "Permanently deleted" %}
|
{% trans "Permanently deleted" %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<h3>{% trans "Actions" %}</h3>
|
<h3>{% trans "User Actions" %}</h3>
|
||||||
|
|
||||||
<div class="is-flex">
|
<div class="box">
|
||||||
{% if user.is_active %}
|
<div class="is-flex">
|
||||||
<p class="mr-1">
|
{% if user.is_active %}
|
||||||
<a class="button" href="{% url 'direct-messages-user' user.username %}">{% trans "Send direct message" %}</a>
|
<p class="mr-1">
|
||||||
</p>
|
<a class="button" href="{% url 'direct-messages-user' user.username %}">{% trans "Send direct message" %}</a>
|
||||||
{% endif %}
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if user.is_active or user.deactivation_reason == "pending" %}
|
{% if user.is_active or user.deactivation_reason == "pending" %}
|
||||||
<form name="suspend" method="post" action="{% url 'settings-report-suspend' user.id %}" class="mr-1">
|
<form name="suspend" method="post" action="{% url 'settings-report-suspend' user.id %}" class="mr-1">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="button is-danger is-light">{% trans "Suspend user" %}</button>
|
<button type="submit" class="button is-danger is-light">{% trans "Suspend user" %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form name="unsuspend" method="post" action="{% url 'settings-report-unsuspend' user.id %}" class="mr-1">
|
<form name="unsuspend" method="post" action="{% url 'settings-report-unsuspend' user.id %}" class="mr-1">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="button">{% trans "Un-suspend user" %}</button>
|
<button class="button">{% trans "Un-suspend user" %}</button>
|
||||||
</form>
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user.local %}
|
||||||
|
<div>
|
||||||
|
{% trans "Permanently delete user" as button_text %}
|
||||||
|
{% include "snippets/toggle/open_button.html" with controls_text="delete_user" text=button_text class="is-danger is-light" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if user.local %}
|
||||||
|
<div>
|
||||||
|
{% include "settings/users/delete_user_form.html" with controls_text="delete_user" class="mt-2 mb-2" %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if user.local %}
|
{% if user.local %}
|
||||||
<div>
|
<div>
|
||||||
{% trans "Permanently delete user" as button_text %}
|
<form name="permission" method="post" action="{% url 'settings-user' user.id %}">
|
||||||
{% include "snippets/toggle/open_button.html" with controls_text="delete_user" text=button_text class="is-danger is-light" %}
|
{% csrf_token %}
|
||||||
|
<label class="label" for="id_user_group">{% trans "Access level:" %}</label>
|
||||||
|
{% if group_form.non_field_errors %}
|
||||||
|
{{ group_form.non_field_errors }}
|
||||||
|
{% endif %}
|
||||||
|
{% with group=user.groups.first %}
|
||||||
|
<div class="select">
|
||||||
|
<select name="groups" id="id_user_group" aria-describedby="desc_user_group">
|
||||||
|
{% for value, name in group_form.fields.groups.choices %}
|
||||||
|
<option value="{{ value }}" {% if name == group.name %}selected{% endif %}>
|
||||||
|
{{ name|title }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
<option value="" {% if not group %}selected{% endif %}>
|
||||||
|
User
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include 'snippets/form_errors.html' with errors_list=group_form.groups.errors id="desc_user_group" %}
|
||||||
|
{% endwith %}
|
||||||
|
<button class="button">
|
||||||
|
{% trans "Save" %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if user.local %}
|
|
||||||
<div>
|
|
||||||
{% include "settings/users/delete_user_form.html" with controls_text="delete_user" class="mt-2 mb-2" %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if user.local %}
|
|
||||||
<div>
|
|
||||||
<form name="permission" method="post" action="{% url 'settings-user' user.id %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<label class="label" for="id_user_group">{% trans "Access level:" %}</label>
|
|
||||||
{% if group_form.non_field_errors %}
|
|
||||||
{{ group_form.non_field_errors }}
|
|
||||||
{% endif %}
|
|
||||||
{% with group=user.groups.first %}
|
|
||||||
<div class="select">
|
|
||||||
<select name="groups" id="id_user_group" aria-describedby="desc_user_group">
|
|
||||||
{% for value, name in group_form.fields.groups.choices %}
|
|
||||||
<option value="{{ value }}" {% if name == group.name %}selected{% endif %}>
|
|
||||||
{{ name|title }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
<option value="" {% if not group %}selected{% endif %}>
|
|
||||||
User
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include 'snippets/form_errors.html' with errors_list=group_form.groups.errors id="desc_user_group" %}
|
|
||||||
{% endwith %}
|
|
||||||
<button class="button">
|
|
||||||
{% trans "Save" %}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% load bookwyrm_tags %}
|
{% load shelf_tags %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
@ -19,6 +19,17 @@
|
||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><a href="{% url 'user-feed' user|username %}">{% trans "User profile" %}</a></li>
|
||||||
|
<li class="is-active">
|
||||||
|
<a href="#" aria-current="page">
|
||||||
|
{% include "snippets/translated_shelf_name.html" with shelf=shelf %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<nav class="block columns is-mobile scroll-x">
|
<nav class="block columns is-mobile scroll-x">
|
||||||
<div class="column pr-0">
|
<div class="column pr-0">
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
|
@ -34,15 +45,7 @@
|
||||||
href="{{ shelf_tab.local_path }}"
|
href="{{ shelf_tab.local_path }}"
|
||||||
{% if shelf_tab.identifier == shelf.identifier %} aria-current="page"{% endif %}
|
{% if shelf_tab.identifier == shelf.identifier %} aria-current="page"{% endif %}
|
||||||
>
|
>
|
||||||
{% if shelf_tab.identifier == 'to-read' %}
|
{% include 'user/books_header.html' with shelf=shelf_tab %}
|
||||||
{% trans "To Read" %}
|
|
||||||
{% elif shelf_tab.identifier == 'reading' %}
|
|
||||||
{% trans "Currently Reading" %}
|
|
||||||
{% elif shelf_tab.identifier == 'read' %}
|
|
||||||
{% trans "Read" %}
|
|
||||||
{% else %}
|
|
||||||
{{ shelf_tab.name }}
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -89,7 +92,7 @@
|
||||||
</span>
|
</span>
|
||||||
{% with count=books.paginator.count %}
|
{% with count=books.paginator.count %}
|
||||||
{% if count %}
|
{% if count %}
|
||||||
<p class="help">
|
<span class="help">
|
||||||
{% blocktrans trimmed count counter=count with formatted_count=count|intcomma %}
|
{% blocktrans trimmed count counter=count with formatted_count=count|intcomma %}
|
||||||
{{ formatted_count }} book
|
{{ formatted_count }} book
|
||||||
{% plural %}
|
{% plural %}
|
||||||
|
@ -101,7 +104,7 @@
|
||||||
(showing {{ start }}-{{ end }})
|
(showing {{ start }}-{{ end }})
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_group_tags %}
|
{% load group_tags %}
|
||||||
|
|
||||||
{% if request.user == user or not request.user == group.user or not request.user.is_authenticated %}
|
{% if request.user == user or not request.user == group.user or not request.user.is_authenticated %}
|
||||||
{% elif user in request.user.blocks.all %}
|
{% elif user in request.user.blocks.all %}
|
||||||
{% include 'snippets/block_button.html' with blocks=True %}
|
{% include 'snippets/block_button.html' with blocks=True %}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "snippets/create_status/layout.html" %}
|
{% extends "snippets/create_status/layout.html" %}
|
||||||
{% load bookwyrm_tags %}
|
{% load shelf_tags %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load status_display %}
|
{% load status_display %}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
{% load bookwyrm_tags %}
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load status_display %}
|
{% load status_display %}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
{% extends "snippets/create_status/layout.html" %}
|
{% extends "snippets/create_status/layout.html" %}
|
||||||
{% load bookwyrm_tags %}
|
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load status_display %}
|
{% load status_display %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
{% extends "snippets/create_status/layout.html" %}
|
{% extends "snippets/create_status/layout.html" %}
|
||||||
{% load bookwyrm_tags %}
|
|
||||||
{% load utilities %}
|
{% load utilities %}
|
||||||
{% load status_display %}
|
{% load status_display %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
|
@ -3,16 +3,15 @@
|
||||||
<summary class="is-flex is-align-items-center is-flex-wrap-wrap is-gap-2">
|
<summary class="is-flex is-align-items-center is-flex-wrap-wrap is-gap-2">
|
||||||
<span class="mb-0 title {% if size == 'small' %}is-6{% else %}is-5{% endif %} is-flex-shrink-0">
|
<span class="mb-0 title {% if size == 'small' %}is-6{% else %}is-5{% endif %} is-flex-shrink-0">
|
||||||
{% trans "Filters" %}
|
{% trans "Filters" %}
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{% if filters_applied %}
|
{% if filters_applied %}
|
||||||
<span class="tag is-success is-light ml-2 mb-0 is-{{ size|default:'normal' }}">
|
<span class="tag is-success is-light ml-2 mb-0 is-{{ size|default:'normal' }}">
|
||||||
{{ _("Filters are applied") }}
|
{% trans "Filters are applied" %}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if request.GET %}
|
{% if method != "post" and request.GET %}
|
||||||
<span class="mb-0 tags has-addons">
|
<span class="mb-0 tags has-addons">
|
||||||
<span class="mb-0 tag is-success is-light is-{{ size|default:'normal' }}">
|
<span class="mb-0 tag is-success is-light is-{{ size|default:'normal' }}">
|
||||||
{% trans "Filters are applied" %}
|
{% trans "Filters are applied" %}
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load interaction %}
|
||||||
|
|
||||||
{% if request.user == user or not request.user.is_authenticated %}
|
{% if request.user == user or not request.user.is_authenticated %}
|
||||||
{% elif user in request.user.blocks.all %}
|
{# nothing to see here -- either it's yourself or your logged out #}
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% get_relationship user as relationship %}
|
||||||
|
{% if relationship.is_blocked %}
|
||||||
{% include 'snippets/block_button.html' with blocks=True %}
|
{% include 'snippets/block_button.html' with blocks=True %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
<div class="field{% if not minimal %} has-addons{% else %} mb-0{% endif %}">
|
<div class="field{% if not minimal %} has-addons{% else %} mb-0{% endif %}">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<form action="{% url 'follow' %}" method="POST" class="interaction follow_{{ user.id }} {% if request.user in user.followers.all or request.user in user.follower_requests.all %}is-hidden{%endif %}" data-id="follow_{{ user.id }}">
|
<form action="{% url 'follow' %}" method="POST" class="interaction follow_{{ user.id }} {% if relationship.is_following or relationship.is_follow_pending %}is-hidden{%endif %}" data-id="follow_{{ user.id }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="user" value="{{ user.username }}">
|
<input type="hidden" name="user" value="{{ user.username }}">
|
||||||
<button class="button is-small{% if not minimal %} is-link{% endif %}" type="submit">
|
<button class="button is-small{% if not minimal %} is-link{% endif %}" type="submit">
|
||||||
|
@ -18,10 +23,10 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<form action="{% url 'unfollow' %}" method="POST" class="interaction follow_{{ user.id }} {% if not request.user in user.followers.all and not request.user in user.follower_requests.all %}is-hidden{%endif %}" data-id="follow_{{ user.id }}">
|
<form action="{% url 'unfollow' %}" method="POST" class="interaction follow_{{ user.id }} {% if not relationship.is_following and not relationship.is_follow_pending %}is-hidden{%endif %}" data-id="follow_{{ user.id }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="user" value="{{ user.username }}">
|
<input type="hidden" name="user" value="{{ user.username }}">
|
||||||
{% if user.manually_approves_followers and request.user not in user.followers.all %}
|
{% if user.manually_approves_followers and not relationship.is_following %}
|
||||||
<button class="button is-small is-danger is-light" type="submit">
|
<button class="button is-small is-danger is-light" type="submit">
|
||||||
{% trans "Undo follow request" %}
|
{% trans "Undo follow request" %}
|
||||||
</button>
|
</button>
|
||||||
|
@ -42,4 +47,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% if rating %}
|
{% if rating %}
|
||||||
|
|
||||||
{% blocktrans with book_title=book.title|safe display_rating=rating|floatformat:"-1" review_title=name|safe count counter=rating %}Review of "{{ book_title }}" ({{ display_rating }} star): {{ review_title }}{% plural %}Review of "{{ book_title }}" ({{ display_rating }} stars): {{ review_title }}{% endblocktrans %}
|
{% blocktrans trimmed with book_title=book.title|safe book_path=book.local_path display_rating=rating|floatformat:"-1" review_title=name|safe count counter=rating %}
|
||||||
|
Review of "{{ book_title }}" ({{ display_rating }} star): {{ review_title }}
|
||||||
|
{% plural %}
|
||||||
|
Review of "{{ book_title }}" ({{ display_rating }} stars): {{ review_title }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{% blocktrans with book_title=book.title|safe review_title=name|safe %}Review of "{{ book_title }}": {{ review_title }}{% endblocktrans %}
|
{% blocktrans trimmed with book_title=book.title|safe book_path=book.local_path review_title=name|safe %}
|
||||||
|
Review of "{{ book_title }}": {{ review_title }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_group_tags %}
|
{% load group_tags %}
|
||||||
|
|
||||||
{% if group|is_invited:request.user %}
|
{% if group|is_invited:request.user %}
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<form action="/accept-group-invitation/" method="POST">
|
<form action="/accept-group-invitation/" method="POST">
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_tags %}
|
{% load rating_tags %}
|
||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<span class="is-sr-only">{% trans "Leave a rating" %}</span>
|
<span class="is-sr-only">{% trans "Leave a rating" %}</span>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load bookwyrm_group_tags %}
|
{% load group_tags %}
|
||||||
|
|
||||||
{% if request.user == user or not request.user == group.user or not request.user.is_authenticated %}
|
{% if request.user == user or not request.user == group.user or not request.user.is_authenticated %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if user in request.user.blocks.all %}
|
{% if user in request.user.blocks.all %}
|
||||||
|
|
|
@ -12,6 +12,6 @@
|
||||||
>
|
>
|
||||||
{% trans "Report" %}
|
{% trans "Report" %}
|
||||||
</button>
|
</button>
|
||||||
{% include 'snippets/report_modal.html' with user=user reporter=request.user id=modal_id %}
|
{% include 'snippets/report_modal.html' with user=user id=modal_id status=status.id %}
|
||||||
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue