improve logging

This commit is contained in:
Your Name 2024-05-25 23:38:11 -04:00 committed by Henri Dickson
parent ce68abb9cc
commit c670b69fdd
25 changed files with 207 additions and 223 deletions

View file

@ -574,7 +574,10 @@ if SENTRY_DSN:
sentry_sdk.init( sentry_sdk.init(
dsn=SENTRY_DSN, dsn=SENTRY_DSN,
environment=sentry_env or "unknown", environment=sentry_env or "unknown",
integrations=[LoguruIntegration(), DjangoIntegration()], integrations=[
DjangoIntegration(),
LoguruIntegration(event_format="{name}:{function}:{line} - {message}"),
],
release=NEODB_VERSION, release=NEODB_VERSION,
send_default_pii=True, send_default_pii=True,
traces_sample_rate=env("NEODB_SENTRY_SAMPLE_RATE"), traces_sample_rate=env("NEODB_SENTRY_SAMPLE_RATE"),

View file

@ -22,7 +22,7 @@ from os.path import exists
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from loguru import logger as _logger from loguru import logger
from catalog.common import ( from catalog.common import (
BaseSchema, BaseSchema,
@ -186,9 +186,9 @@ class Edition(Item):
if work_res: if work_res:
work = work_res.item work = work_res.item
if not work: if not work:
_logger.warning(f"Unable to find work for {work_res}") logger.warning(f"Unable to find work for {work_res}")
else: else:
_logger.warning( logger.warning(
f'Unable to find resource for {w["id_type"]}:{w["id_value"]}' f'Unable to find resource for {w["id_type"]}:{w["id_value"]}'
) )
work = Work.objects.filter( work = Work.objects.filter(

View file

@ -11,14 +11,12 @@ import filetype
import requests import requests
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from loguru import logger
from lxml import etree, html from lxml import etree, html
from PIL import Image from PIL import Image
from requests import Response from requests import Response
from requests.exceptions import RequestException from requests.exceptions import RequestException
_logger = logging.getLogger(__name__)
RESPONSE_OK = 0 # response is ready for pasring RESPONSE_OK = 0 # response is ready for pasring
RESPONSE_INVALID_CONTENT = -1 # content not valid but no need to retry RESPONSE_INVALID_CONTENT = -1 # content not valid but no need to retry
RESPONSE_NETWORK_ERROR = -2 # network error, retry next proxied url RESPONSE_NETWORK_ERROR = -2 # network error, retry next proxied url
@ -67,11 +65,11 @@ class MockResponse:
try: try:
self.content = Path(fn).read_bytes() self.content = Path(fn).read_bytes()
self.status_code = 200 self.status_code = 200
_logger.debug(f"use local response for {url} from {fn}") logger.debug(f"use local response for {url} from {fn}")
except Exception: except Exception:
self.content = b"Error: response file not found" self.content = b"Error: response file not found"
self.status_code = 404 self.status_code = 404
_logger.debug(f"local response not found for {url} at {fn}") logger.debug(f"local response not found for {url} at {fn}")
@property @property
def text(self): def text(self):
@ -173,7 +171,7 @@ class BasicDownloader:
) as fp: ) as fp:
fp.write(resp.text) fp.write(resp.text)
except Exception: except Exception:
_logger.warn("Save downloaded data failed.") logger.warning("Save downloaded data failed.")
else: else:
resp = MockResponse(self.url) resp = MockResponse(self.url)
response_type = self.validate_response(resp) response_type = self.validate_response(resp)
@ -249,7 +247,7 @@ class RetryDownloader(BasicDownloader):
elif self.response_type != RESPONSE_NETWORK_ERROR and retries == 0: elif self.response_type != RESPONSE_NETWORK_ERROR and retries == 0:
raise DownloadError(self) raise DownloadError(self)
elif retries > 0: elif retries > 0:
_logger.debug("Retry " + self.url) logger.debug("Retry " + self.url)
time.sleep((settings.DOWNLOADER_RETRIES - retries) * 0.5) time.sleep((settings.DOWNLOADER_RETRIES - retries) * 0.5)
raise DownloadError(self, "max out of retries") raise DownloadError(self, "max out of retries")

View file

@ -1,4 +1,3 @@
import logging
import re import re
import uuid import uuid
from functools import cached_property from functools import cached_property
@ -13,6 +12,7 @@ from django.db import connection, models
from django.utils import timezone from django.utils import timezone
from django.utils.baseconv import base62 from django.utils.baseconv import base62
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from loguru import logger
from ninja import Field, Schema from ninja import Field, Schema
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
@ -24,8 +24,6 @@ from .utils import DEFAULT_ITEM_COVER, item_cover_path, resource_cover_path
if TYPE_CHECKING: if TYPE_CHECKING:
from users.models import User from users.models import User
_logger = logging.getLogger(__name__)
class SiteName(models.TextChoices): class SiteName(models.TextChoices):
Unknown = "unknown", _("Unknown") Unknown = "unknown", _("Unknown")
@ -406,7 +404,7 @@ class Item(SoftDeleteMixin, PolymorphicModel):
res.save() res.save()
def recast_to(self, model): def recast_to(self, model):
_logger.warn(f"recast item {self} to {model}") logger.warning(f"recast item {self} to {model}")
if self.__class__ == model: if self.__class__ == model:
return self return self
if model not in Item.__subclasses__(): if model not in Item.__subclasses__():
@ -631,7 +629,7 @@ class ExternalResource(models.Model):
site = self.get_site() site = self.get_site()
return site.SITE_NAME if site else SiteName.Unknown return site.SITE_NAME if site else SiteName.Unknown
except Exception: except Exception:
_logger.warning(f"Unknown site for {self}") logger.warning(f"Unknown site for {self}")
return SiteName.Unknown return SiteName.Unknown
@property @property

View file

@ -7,19 +7,17 @@ a Site may scrape a url and store result in ResourceContent
ResourceContent persists as an ExternalResource which may link to an Item ResourceContent persists as an ExternalResource which may link to an Item
""" """
import json import json
import logging
import re import re
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any, Callable, Type, TypeVar from typing import Any, Callable, Type, TypeVar
import django_rq import django_rq
import requests import requests
from loguru import logger
from validators import url as url_validate from validators import url as url_validate
from .models import ExternalResource, IdealIdTypes, IdType, Item, SiteName from .models import ExternalResource, IdealIdTypes, IdType, Item, SiteName
_logger = logging.getLogger(__name__)
@dataclass @dataclass
class ResourceContent: class ResourceContent:
@ -238,7 +236,7 @@ class AbstractSite:
if resource_content: if resource_content:
p.update_content(resource_content) p.update_content(resource_content)
if not p.ready: if not p.ready:
_logger.error(f"unable to get resource {self.url} ready") logger.error(f"unable to get resource {self.url} ready")
return None return None
if auto_create: # and (p.item is None or p.item.is_deleted): if auto_create: # and (p.item is None or p.item.is_deleted):
self.get_item() self.get_item()
@ -259,7 +257,7 @@ class AbstractSite:
preloaded_content=linked_resource.get("content"), preloaded_content=linked_resource.get("content"),
) )
else: else:
_logger.error(f"unable to get site for {linked_url}") logger.error(f"unable to get site for {linked_url}")
if p.related_resources or p.prematched_resources: if p.related_resources or p.prematched_resources:
django_rq.get_queue("crawl").enqueue(crawl_related_resources_task, p.pk) django_rq.get_queue("crawl").enqueue(crawl_related_resources_task, p.pk)
if p.item: if p.item:
@ -338,7 +336,7 @@ class SiteManager:
def crawl_related_resources_task(resource_pk): def crawl_related_resources_task(resource_pk):
resource = ExternalResource.objects.filter(pk=resource_pk).first() resource = ExternalResource.objects.filter(pk=resource_pk).first()
if not resource: if not resource:
_logger.warn(f"crawl resource not found {resource_pk}") logger.warning(f"crawl resource not found {resource_pk}")
return return
links = (resource.related_resources or []) + (resource.prematched_resources or []) # type: ignore links = (resource.related_resources or []) + (resource.prematched_resources or []) # type: ignore
for w in links: # type: ignore for w in links: # type: ignore
@ -353,8 +351,8 @@ def crawl_related_resources_task(resource_pk):
site.get_resource_ready(ignore_existing_content=False, auto_link=True) site.get_resource_ready(ignore_existing_content=False, auto_link=True)
item = site.get_item() item = site.get_item()
if item: if item:
_logger.info(f"crawled {w} {item}") logger.info(f"crawled {w} {item}")
else: else:
_logger.warn(f"crawl {w} failed") logger.warning(f"crawl {w} failed")
except Exception as e: except Exception as e:
_logger.warn(f"crawl {w} error {e}") logger.warning(f"crawl {w} error {e}")

View file

@ -1,11 +1,7 @@
import logging
import uuid import uuid
from django.utils import timezone from django.utils import timezone
_logger = logging.getLogger(__name__)
DEFAULT_ITEM_COVER = "item/default.svg" DEFAULT_ITEM_COVER = "item/default.svg"

View file

@ -1,8 +1,7 @@
import logging
from auditlog.registry import auditlog from auditlog.registry import auditlog
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from loguru import logger
from .book.models import Edition, EditionInSchema, EditionSchema, Series, Work from .book.models import Edition, EditionInSchema, EditionSchema, Series, Work
from .collection.models import Collection as CatalogCollection from .collection.models import Collection as CatalogCollection
@ -42,8 +41,6 @@ from .tv.models import (
from .search.models import Indexer # isort:skip from .search.models import Indexer # isort:skip
_logger = logging.getLogger(__name__)
# class Exhibition(Item): # class Exhibition(Item):
@ -59,7 +56,7 @@ _logger = logging.getLogger(__name__)
def init_catalog_search_models(): def init_catalog_search_models():
if settings.DISABLE_MODEL_SIGNAL: if settings.DISABLE_MODEL_SIGNAL:
_logger.warn( logger.warning(
"Catalog models are not being indexed with DISABLE_MODEL_SIGNAL configuration" "Catalog models are not being indexed with DISABLE_MODEL_SIGNAL configuration"
) )
return return
@ -100,4 +97,4 @@ def init_catalog_audit_log():
ExternalResource, include_fields=["item", "id_type", "id_value", "url"] ExternalResource, include_fields=["item", "id_type", "id_value", "url"]
) )
# _logger.debug(f"Catalog audit log initialized for {item_content_types().values()}") # logger.debug(f"Catalog audit log initialized for {item_content_types().values()}")

View file

@ -53,7 +53,7 @@ class SearchResultItem:
class Goodreads: class Goodreads:
@classmethod @classmethod
def search(cls, q, page=1): def search(cls, q: str, page=1):
results = [] results = []
search_url = f"https://www.goodreads.com/search?page={page}&q={quote_plus(q)}" search_url = f"https://www.goodreads.com/search?page={page}&q={quote_plus(q)}"
try: try:
@ -115,7 +115,7 @@ class Goodreads:
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.warning(f"Search {search_url} error: {e}") logger.warning(f"Search {search_url} error: {e}")
except Exception as e: except Exception as e:
logger.error(f"Goodreads search '{q}' error: {e}") logger.error("Goodreads search error", extra={"query": q, "exception": e})
return results return results
@ -164,7 +164,7 @@ class GoogleBooks:
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.warning(f"Search {api_url} error: {e}") logger.warning(f"Search {api_url} error: {e}")
except Exception as e: except Exception as e:
logger.error(f"GoogleBooks search '{q}' error: {e}") logger.error("GoogleBooks search error", extra={"query": q, "exception": e})
return results return results
@ -206,7 +206,7 @@ class TheMovieDatabase:
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.warning(f"Search {api_url} error: {e}") logger.warning(f"Search {api_url} error: {e}")
except Exception as e: except Exception as e:
logger.error(f"TMDb search '{q}' error: {e}") logger.error("TMDb search error", extra={"query": q, "exception": e})
return results return results
@ -242,7 +242,7 @@ class Spotify:
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.warning(f"Search {api_url} error: {e}") logger.warning(f"Search {api_url} error: {e}")
except Exception as e: except Exception as e:
logger.error(f"Spotify search '{q}' error: {e}") logger.error("Spotify search error", extra={"query": q, "exception": e})
return results return results
@ -278,7 +278,7 @@ class Bandcamp:
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.warning(f"Search {search_url} error: {e}") logger.warning(f"Search {search_url} error: {e}")
except Exception as e: except Exception as e:
logger.error(f"Goodreads search '{q}' error: {e}") logger.error("Bandcamp search error", extra={"query": q, "exception": e})
return results return results
@ -305,7 +305,9 @@ class ApplePodcast:
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.warning(f"Search {search_url} error: {e}") logger.warning(f"Search {search_url} error: {e}")
except Exception as e: except Exception as e:
logger.error(f"ApplePodcast search '{q}' error: {e}") logger.error(
"ApplePodcast search error", extra={"query": q, "exception": e}
)
return results return results
@ -322,7 +324,10 @@ class Fediverse:
) )
r = response.json() r = response.json()
except Exception as e: except Exception as e:
logger.warning(f"Search {api_url} error: {e}") logger.error(
f"Fediverse search {host} error",
extra={"url": api_url, "query": q, "exception": e},
)
return [] return []
if "data" in r: if "data" in r:
for item in r["data"]: for item in r["data"]:

View file

@ -1,12 +1,12 @@
# pyright: reportFunctionMemberAccess=false # pyright: reportFunctionMemberAccess=false
import hashlib import hashlib
import logging
import django_rq import django_rq
from auditlog.context import set_actor from auditlog.context import set_actor
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from loguru import logger
from rq.job import Job from rq.job import Job
from catalog.common.sites import SiteManager from catalog.common.sites import SiteManager
@ -16,8 +16,6 @@ from .typesense import Indexer as TypeSenseIndexer
# from .meilisearch import Indexer as MeiliSearchIndexer # from .meilisearch import Indexer as MeiliSearchIndexer
_logger = logging.getLogger(__name__)
class DbIndexer: class DbIndexer:
@classmethod @classmethod
@ -154,10 +152,10 @@ def _fetch_task(url, is_refetch, user):
site.get_resource_ready(ignore_existing_content=is_refetch) site.get_resource_ready(ignore_existing_content=is_refetch)
item = site.get_item() item = site.get_item()
if item: if item:
_logger.info(f"fetched {url} {item.url} {item}") logger.info(f"fetched {url} {item.url} {item}")
item_url = item.url item_url = item.url
else: else:
_logger.error(f"fetch {url} failed") logger.error(f"fetch {url} failed")
except Exception as e: except Exception as e:
_logger.error(f"fetch {url} error {e}") logger.error(f"fetch {url} error", extra={"exception": e})
return item_url return item_url

View file

@ -1,4 +1,3 @@
import logging
import types import types
from datetime import timedelta from datetime import timedelta
from pprint import pprint from pprint import pprint

View file

@ -6,17 +6,16 @@ use (e.g. "portal-2") as id, which is different from real id in IGDB API
import datetime import datetime
import json import json
import logging
import requests import requests
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from igdb.wrapper import IGDBWrapper from igdb.wrapper import IGDBWrapper
from loguru import logger
from catalog.common import * from catalog.common import *
from catalog.models import * from catalog.models import *
_logger = logging.getLogger(__name__)
_cache_key = "igdb_access_token" _cache_key = "igdb_access_token"
@ -32,8 +31,8 @@ def _igdb_access_token():
token = j["access_token"] token = j["access_token"]
ttl = j["expires_in"] - 60 ttl = j["expires_in"] - 60
cache.set(_cache_key, token, ttl) cache.set(_cache_key, token, ttl)
except Exception: except Exception as e:
_logger.error("unable to obtain IGDB token") logger.error("unable to obtain IGDB token", extra={"exception": e})
token = "<invalid>" token = "<invalid>"
return token return token
@ -156,7 +155,7 @@ class IGDB(AbstractSite):
pd.cover_image = imgdl.download().content pd.cover_image = imgdl.download().content
pd.cover_image_extention = imgdl.extention pd.cover_image_extention = imgdl.extention
except Exception: except Exception:
_logger.debug( logger.debug(
f'failed to download cover for {self.url} from {pd.metadata["cover_image_url"]}' f'failed to download cover for {self.url} from {pd.metadata["cover_image_url"]}'
) )
return pd return pd

View file

@ -1,5 +1,3 @@
import logging
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.cache import cache from django.core.cache import cache
from django.core.paginator import Paginator from django.core.paginator import Paginator
@ -29,9 +27,6 @@ from .models import *
from .search.views import * from .search.views import *
from .views_edit import * from .views_edit import *
_logger = logging.getLogger(__name__)
NUM_REVIEWS_ON_ITEM_PAGE = 5 NUM_REVIEWS_ON_ITEM_PAGE = 5
NUM_REVIEWS_ON_LIST_PAGE = 20 NUM_REVIEWS_ON_LIST_PAGE = 20

View file

@ -1,5 +1,3 @@
import logging
from auditlog.context import set_actor from auditlog.context import set_actor
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -9,6 +7,7 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from loguru import logger
from common.utils import discord_send, get_uuid_or_404 from common.utils import discord_send, get_uuid_or_404
from journal.models import update_journal_for_merged_item from journal.models import update_journal_for_merged_item
@ -19,8 +18,6 @@ from .models import *
from .search.views import * from .search.views import *
from .sites.imdb import IMDB as IMDB from .sites.imdb import IMDB as IMDB
_logger = logging.getLogger(__name__)
def _add_error_map_detail(e): def _add_error_map_detail(e):
e.additonal_detail = [] e.additonal_detail = []
@ -171,7 +168,7 @@ def recast(request, item_path, item_uuid):
raise BadRequest("Invalid target type") raise BadRequest("Invalid target type")
if isinstance(item, model): if isinstance(item, model):
raise BadRequest("Same target type") raise BadRequest("Same target type")
_logger.warn(f"{request.user} recasting {item} to {model}") logger.warning(f"{request.user} recasting {item} to {model}")
discord_send( discord_send(
"audit", "audit",
f"{item.absolute_url}\n{item.__class__.__name__}{model.__name__}\nby [@{request.user.username}]({request.user.absolute_url})", f"{item.absolute_url}\n{item.__class__.__name__}{model.__name__}\nby [@{request.user.username}]({request.user.absolute_url})",
@ -180,7 +177,7 @@ def recast(request, item_path, item_uuid):
) )
if isinstance(item, TVShow): if isinstance(item, TVShow):
for season in item.seasons.all(): for season in item.seasons.all():
_logger.warn(f"{request.user} recast orphaning season {season}") logger.warning(f"{request.user} recast orphaning season {season}")
season.show = None season.show = None
season.save(update_fields=["show"]) season.save(update_fields=["show"])
new_item = item.recast_to(model) new_item = item.recast_to(model)
@ -218,7 +215,7 @@ def assign_parent(request, item_path, item_uuid):
raise BadRequest("Incompatible child item type") raise BadRequest("Incompatible child item type")
# if not request.user.is_staff and item.parent_item: # if not request.user.is_staff and item.parent_item:
# raise BadRequest("Already assigned to a parent item") # raise BadRequest("Already assigned to a parent item")
_logger.warn(f"{request.user} assign {item} to {parent_item}") logger.warning(f"{request.user} assign {item} to {parent_item}")
item.set_parent_item(parent_item) item.set_parent_item(parent_item)
item.save() item.save()
return redirect(item.url) return redirect(item.url)
@ -290,7 +287,7 @@ def merge(request, item_path, item_uuid):
_("Cannot merge items in different categories") _("Cannot merge items in different categories")
+ f" ({item.class_name} to {new_item.class_name})" + f" ({item.class_name} to {new_item.class_name})"
) )
_logger.warn(f"{request.user} merges {item} to {new_item}") logger.warning(f"{request.user} merges {item} to {new_item}")
item.merge_to(new_item) item.merge_to(new_item)
update_journal_for_merged_item(item_uuid) update_journal_for_merged_item(item_uuid)
discord_send( discord_send(
@ -302,7 +299,7 @@ def merge(request, item_path, item_uuid):
return redirect(new_item.url) return redirect(new_item.url)
else: else:
if item.merged_to_item: if item.merged_to_item:
_logger.warn(f"{request.user} cancels merge for {item}") logger.warning(f"{request.user} cancels merge for {item}")
item.merge_to(None) item.merge_to(None)
discord_send( discord_send(
"audit", "audit",
@ -334,7 +331,7 @@ def link_edition(request, item_path, item_uuid):
"catalog_merge.html", "catalog_merge.html",
{"item": item, "new_item": new_item, "mode": "link"}, {"item": item, "new_item": new_item, "mode": "link"},
) )
_logger.warn(f"{request.user} merges {item} to {new_item}") logger.warning(f"{request.user} merges {item} to {new_item}")
item.link_to_related_book(new_item) item.link_to_related_book(new_item)
discord_send( discord_send(
"audit", "audit",

View file

@ -1,8 +1,8 @@
import logging
from typing import Any, Callable, List, Optional, Tuple, Type from typing import Any, Callable, List, Optional, Tuple, Type
from django.conf import settings from django.conf import settings
from django.db.models import QuerySet from django.db.models import QuerySet
from loguru import logger
from ninja import NinjaAPI, Schema from ninja import NinjaAPI, Schema
from ninja.pagination import PageNumberPagination as NinjaPageNumberPagination from ninja.pagination import PageNumberPagination as NinjaPageNumberPagination
from ninja.security import HttpBearer from ninja.security import HttpBearer
@ -10,9 +10,6 @@ from oauth2_provider.oauth2_backends import OAuthLibCore
from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.oauth2_validators import OAuth2Validator
from oauthlib.oauth2 import Server from oauthlib.oauth2 import Server
_logger = logging.getLogger(__name__)
PERMITTED_WRITE_METHODS = ["PUT", "POST", "DELETE", "PATCH"] PERMITTED_WRITE_METHODS = ["PUT", "POST", "DELETE", "PATCH"]
PERMITTED_READ_METHODS = ["GET", "HEAD", "OPTIONS"] PERMITTED_READ_METHODS = ["GET", "HEAD", "OPTIONS"]
@ -20,7 +17,7 @@ PERMITTED_READ_METHODS = ["GET", "HEAD", "OPTIONS"]
class OAuthAccessTokenAuth(HttpBearer): class OAuthAccessTokenAuth(HttpBearer):
def authenticate(self, request, token) -> bool: def authenticate(self, request, token) -> bool:
if not token or not request.user.is_authenticated: if not token or not request.user.is_authenticated:
_logger.debug("API auth: no access token or user not authenticated") logger.debug("API auth: no access token or user not authenticated")
return False return False
request_scopes = [] request_scopes = []
request_method = request.method request_method = request.method
@ -34,7 +31,7 @@ class OAuthAccessTokenAuth(HttpBearer):
core = OAuthLibCore(Server(validator)) core = OAuthLibCore(Server(validator))
valid, oauthlib_req = core.verify_request(request, scopes=request_scopes) valid, oauthlib_req = core.verify_request(request, scopes=request_scopes)
if not valid: if not valid:
_logger.debug(f"API auth: request scope {request_scopes} not verified") logger.debug(f"API auth: request scope {request_scopes} not verified")
return valid return valid

View file

@ -49,7 +49,7 @@ class Command(BaseCommand):
for job_id in options["runonce"]: for job_id in options["runonce"]:
run = JobManager.run(job_id) run = JobManager.run(job_id)
if not run: if not run:
logger.error(f"Job not found: {job_id}") logger.error(f"job not found: {job_id}")
if options["list"]: if options["list"]:
all_jobs = [j.__name__ for j in JobManager.registry] all_jobs = [j.__name__ for j in JobManager.registry]
logger.info(f"{len(all_jobs)} available jobs: {' '.join(all_jobs)}") logger.info(f"{len(all_jobs)} available jobs: {' '.join(all_jobs)}")

View file

@ -1,4 +1,3 @@
import logging
import os import os
import re import re
from datetime import datetime from datetime import datetime
@ -36,8 +35,8 @@ def _fetch_remote_image(url):
binary_file.write(raw_img) binary_file.write(raw_img)
# logger.info(f'remote image saved as {local_url}') # logger.info(f'remote image saved as {local_url}')
return local_url return local_url
except Exception: except Exception as e:
logger.error(f"unable to fetch remote image {url}") logger.error(f"unable to fetch image", extra={"url": url, "exception": e})
return url return url
@ -107,7 +106,9 @@ class DoubanImporter:
self.import_from_file_task, job_id=jid self.import_from_file_task, job_id=jid
) )
except Exception as e: except Exception as e:
logger.error(e) logger.error(
f"unable to enqueue import {uploaded_file}", extra={"exception": e}
)
return False return False
return True return True
@ -327,7 +328,7 @@ class DoubanImporter:
# logger.info(f"matched {url}") # logger.info(f"matched {url}")
print(".", end="", flush=True) print(".", end="", flush=True)
except Exception as e: except Exception as e:
logger.error(f"fetching exception: {url} {e}") logger.error(f"fetching error: {url}", extra={"exception": e})
if item is None: if item is None:
self.failed.append(str(url)) self.failed.append(str(url))
return item return item

View file

@ -30,9 +30,9 @@ def render_relogin(request):
"common/error.html", "common/error.html",
{ {
"url": reverse("users:connect") + "?domain=" + request.user.mastodon_site, "url": reverse("users:connect") + "?domain=" + request.user.mastodon_site,
"msg": _("Data saved but unable to repost to Fediverse."), "msg": _("Data saved but unable to repost to Fediverse instance."),
"secondary_msg": _( "secondary_msg": _(
"Redirecting to your Mastodon instance now to re-authenticate." "Redirecting to your Fediverse instance now to re-authenticate."
), ),
}, },
) )

View file

@ -1,4 +1,3 @@
import logging
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
@ -10,6 +9,7 @@ from django.utils import timezone
from django.utils.dateparse import parse_datetime from django.utils.dateparse import parse_datetime
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from loguru import logger
from catalog.models import * from catalog.models import *
from common.utils import AuthedHttpRequest, get_uuid_or_404 from common.utils import AuthedHttpRequest, get_uuid_or_404
@ -19,7 +19,6 @@ from takahe.utils import Takahe
from ..models import Comment, Mark, ShelfManager, ShelfType, TagManager from ..models import Comment, Mark, ShelfManager, ShelfType, TagManager
from .common import render_list, render_relogin, target_identity_required from .common import render_list, render_relogin, target_identity_required
_logger = logging.getLogger(__name__)
PAGE_SIZE = 10 PAGE_SIZE = 10
_checkmark = "✔️".encode("utf-8") _checkmark = "✔️".encode("utf-8")
@ -101,12 +100,12 @@ def mark(request: AuthedHttpRequest, item_uuid):
created_time=mark_date, created_time=mark_date,
) )
except PermissionDenied: except PermissionDenied:
_logger.warn(f"post to mastodon error 401 {request.user}") logger.warning(f"post to mastodon error 401 {request.user}")
return render_relogin(request) return render_relogin(request)
except ValueError as e: except ValueError as e:
_logger.warn(f"post to mastodon error {e} {request.user}") logger.warning(f"post to mastodon error {e} {request.user}")
err = ( err = (
_("Content too long for your Mastodon instance.") _("Content too long for your Fediverse instance.")
if str(e) == "422" if str(e) == "422"
else str(e) else str(e)
) )
@ -114,7 +113,9 @@ def mark(request: AuthedHttpRequest, item_uuid):
request, request,
"common/error.html", "common/error.html",
{ {
"msg": _("Data saved but unable to repost to Fediverse."), "msg": _(
"Data saved but unable to repost to Fediverse instance."
),
"secondary_msg": err, "secondary_msg": err,
}, },
) )

View file

@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-23 11:27-0400\n" "POT-Creation-Date: 2024-05-25 23:36-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -92,315 +92,315 @@ msgstr "出品方"
msgid "other title" msgid "other title"
msgstr "其它标题" msgstr "其它标题"
#: catalog/common/models.py:31 #: catalog/common/models.py:29
msgid "Unknown" msgid "Unknown"
msgstr "未知" msgstr "未知"
#: catalog/common/models.py:32 #: catalog/common/models.py:30
msgid "Douban" msgid "Douban"
msgstr "豆瓣" msgstr "豆瓣"
#: catalog/common/models.py:33 catalog/common/models.py:66 #: catalog/common/models.py:31 catalog/common/models.py:64
msgid "Goodreads" msgid "Goodreads"
msgstr "Goodreads" msgstr "Goodreads"
#: catalog/common/models.py:34 catalog/common/models.py:68 #: catalog/common/models.py:32 catalog/common/models.py:66
msgid "Google Books" msgid "Google Books"
msgstr "谷歌图书" msgstr "谷歌图书"
#: catalog/common/models.py:35 #: catalog/common/models.py:33
msgid "BooksTW" msgid "BooksTW"
msgstr "博客來" msgstr "博客來"
#: catalog/common/models.py:36 catalog/common/models.py:61 #: catalog/common/models.py:34 catalog/common/models.py:59
#: catalog/templates/movie.html:51 catalog/templates/tvseason.html:68 #: catalog/templates/movie.html:51 catalog/templates/tvseason.html:68
#: catalog/templates/tvshow.html:63 #: catalog/templates/tvshow.html:63
msgid "IMDb" msgid "IMDb"
msgstr "IMDb" msgstr "IMDb"
#: catalog/common/models.py:37 #: catalog/common/models.py:35
msgid "TMDB" msgid "TMDB"
msgstr "TMDB" msgstr "TMDB"
#: catalog/common/models.py:38 catalog/common/models.py:77 #: catalog/common/models.py:36 catalog/common/models.py:75
msgid "Bandcamp" msgid "Bandcamp"
msgstr "Bandcamp" msgstr "Bandcamp"
#: catalog/common/models.py:39 #: catalog/common/models.py:37
msgid "Spotify" msgid "Spotify"
msgstr "Spotify" msgstr "Spotify"
#: catalog/common/models.py:40 #: catalog/common/models.py:38
msgid "IGDB" msgid "IGDB"
msgstr "IGDB" msgstr "IGDB"
#: catalog/common/models.py:41 #: catalog/common/models.py:39
msgid "Steam" msgid "Steam"
msgstr "Steam" msgstr "Steam"
#: catalog/common/models.py:42 catalog/common/models.py:91 #: catalog/common/models.py:40 catalog/common/models.py:89
msgid "Bangumi" msgid "Bangumi"
msgstr "Bangumi" msgstr "Bangumi"
#: catalog/common/models.py:43 #: catalog/common/models.py:41
msgid "BGG" msgid "BGG"
msgstr "BGG" msgstr "BGG"
#: catalog/common/models.py:45 #: catalog/common/models.py:43
msgid "RSS" msgid "RSS"
msgstr "RSS" msgstr "RSS"
#: catalog/common/models.py:46 #: catalog/common/models.py:44
msgid "Discogs" msgid "Discogs"
msgstr "Discogs" msgstr "Discogs"
#: catalog/common/models.py:47 catalog/common/models.py:93 #: catalog/common/models.py:45 catalog/common/models.py:91
msgid "Apple Music" msgid "Apple Music"
msgstr "苹果音乐" msgstr "苹果音乐"
#: catalog/common/models.py:48 catalog/common/models.py:94 #: catalog/common/models.py:46 catalog/common/models.py:92
msgid "Fediverse" msgid "Fediverse"
msgstr "联邦宇宙" msgstr "联邦宇宙"
#: catalog/common/models.py:52 #: catalog/common/models.py:50
msgid "WikiData" msgid "WikiData"
msgstr "维基数据" msgstr "维基数据"
#: catalog/common/models.py:53 #: catalog/common/models.py:51
msgid "ISBN10" msgid "ISBN10"
msgstr "ISBN10" msgstr "ISBN10"
#: catalog/common/models.py:54 catalog/templates/edition.html:19 #: catalog/common/models.py:52 catalog/templates/edition.html:19
msgid "ISBN" msgid "ISBN"
msgstr "ISBN" msgstr "ISBN"
#: catalog/common/models.py:55 #: catalog/common/models.py:53
msgid "ASIN" msgid "ASIN"
msgstr "ASIN" msgstr "ASIN"
#: catalog/common/models.py:56 #: catalog/common/models.py:54
msgid "ISSN" msgid "ISSN"
msgstr "ISSN" msgstr "ISSN"
#: catalog/common/models.py:57 #: catalog/common/models.py:55
msgid "CUBN" msgid "CUBN"
msgstr "统一书号" msgstr "统一书号"
#: catalog/common/models.py:58 #: catalog/common/models.py:56
msgid "ISRC" msgid "ISRC"
msgstr "ISRC" msgstr "ISRC"
#: catalog/common/models.py:59 #: catalog/common/models.py:57
msgid "GTIN UPC EAN" msgid "GTIN UPC EAN"
msgstr "条形码" msgstr "条形码"
#: catalog/common/models.py:60 #: catalog/common/models.py:58
msgid "RSS Feed URL" msgid "RSS Feed URL"
msgstr "RSS网址" msgstr "RSS网址"
#: catalog/common/models.py:62 #: catalog/common/models.py:60
msgid "TMDB TV Serie" msgid "TMDB TV Serie"
msgstr "TMDB电视剧集" msgstr "TMDB电视剧集"
#: catalog/common/models.py:63 #: catalog/common/models.py:61
msgid "TMDB TV Season" msgid "TMDB TV Season"
msgstr "TMDB电视分季" msgstr "TMDB电视分季"
#: catalog/common/models.py:64 #: catalog/common/models.py:62
msgid "TMDB TV Episode" msgid "TMDB TV Episode"
msgstr "TMDB电视单集" msgstr "TMDB电视单集"
#: catalog/common/models.py:65 #: catalog/common/models.py:63
msgid "TMDB Movie" msgid "TMDB Movie"
msgstr "TMDB电影" msgstr "TMDB电影"
#: catalog/common/models.py:67 #: catalog/common/models.py:65
msgid "Goodreads Work" msgid "Goodreads Work"
msgstr "Goodreads著作" msgstr "Goodreads著作"
#: catalog/common/models.py:69 #: catalog/common/models.py:67
msgid "Douban Book" msgid "Douban Book"
msgstr "豆瓣图书" msgstr "豆瓣图书"
#: catalog/common/models.py:70 #: catalog/common/models.py:68
msgid "Douban Book Work" msgid "Douban Book Work"
msgstr "豆瓣图书著作" msgstr "豆瓣图书著作"
#: catalog/common/models.py:71 #: catalog/common/models.py:69
msgid "Douban Movie" msgid "Douban Movie"
msgstr "豆瓣电影" msgstr "豆瓣电影"
#: catalog/common/models.py:72 #: catalog/common/models.py:70
msgid "Douban Music" msgid "Douban Music"
msgstr "豆瓣音乐" msgstr "豆瓣音乐"
#: catalog/common/models.py:73 #: catalog/common/models.py:71
msgid "Douban Game" msgid "Douban Game"
msgstr "豆瓣游戏" msgstr "豆瓣游戏"
#: catalog/common/models.py:74 #: catalog/common/models.py:72
msgid "Douban Drama" msgid "Douban Drama"
msgstr "豆瓣舞台剧" msgstr "豆瓣舞台剧"
#: catalog/common/models.py:75 #: catalog/common/models.py:73
msgid "Douban Drama Version" msgid "Douban Drama Version"
msgstr "豆瓣舞台剧版本" msgstr "豆瓣舞台剧版本"
#: catalog/common/models.py:76 #: catalog/common/models.py:74
msgid "BooksTW Book" msgid "BooksTW Book"
msgstr "博客来图书" msgstr "博客来图书"
#: catalog/common/models.py:78 #: catalog/common/models.py:76
msgid "Spotify Album" msgid "Spotify Album"
msgstr "Spotify专辑" msgstr "Spotify专辑"
#: catalog/common/models.py:79 #: catalog/common/models.py:77
msgid "Spotify Podcast" msgid "Spotify Podcast"
msgstr "Spotify播客" msgstr "Spotify播客"
#: catalog/common/models.py:88 #: catalog/common/models.py:86
msgid "IGDB Game" msgid "IGDB Game"
msgstr "IGDB游戏" msgstr "IGDB游戏"
#: catalog/common/models.py:89 #: catalog/common/models.py:87
msgid "BGG Boardgame" msgid "BGG Boardgame"
msgstr "BGG桌游" msgstr "BGG桌游"
#: catalog/common/models.py:90 #: catalog/common/models.py:88
msgid "Steam Game" msgid "Steam Game"
msgstr "Steam游戏" msgstr "Steam游戏"
#: catalog/common/models.py:92 #: catalog/common/models.py:90
msgid "Apple Podcast" msgid "Apple Podcast"
msgstr "苹果播客" msgstr "苹果播客"
#: catalog/common/models.py:110 catalog/common/models.py:127 #: catalog/common/models.py:108 catalog/common/models.py:125
#: catalog/common/models.py:140 catalog/jobs/discover.py:91 #: catalog/common/models.py:138 catalog/jobs/discover.py:91
#: common/templates/_header.html:25 #: common/templates/_header.html:25
msgid "Book" msgid "Book"
msgstr "书" msgstr "书"
#: catalog/common/models.py:111 #: catalog/common/models.py:109
msgid "TV Serie" msgid "TV Serie"
msgstr "电视剧集" msgstr "电视剧集"
#: catalog/common/models.py:112 catalog/templates/_sidebar_edit.html:140 #: catalog/common/models.py:110 catalog/templates/_sidebar_edit.html:140
msgid "TV Season" msgid "TV Season"
msgstr "电视分季" msgstr "电视分季"
#: catalog/common/models.py:113 #: catalog/common/models.py:111
msgid "TV Episode" msgid "TV Episode"
msgstr "电视单集" msgstr "电视单集"
#: catalog/common/models.py:114 catalog/common/models.py:128 #: catalog/common/models.py:112 catalog/common/models.py:126
#: catalog/common/models.py:141 catalog/templates/_sidebar_edit.html:133 #: catalog/common/models.py:139 catalog/templates/_sidebar_edit.html:133
msgid "Movie" msgid "Movie"
msgstr "电影" msgstr "电影"
#: catalog/common/models.py:115 #: catalog/common/models.py:113
msgid "Album" msgid "Album"
msgstr "专辑" msgstr "专辑"
#: catalog/common/models.py:116 catalog/common/models.py:131 #: catalog/common/models.py:114 catalog/common/models.py:129
#: catalog/common/models.py:144 common/templates/_header.html:41 #: catalog/common/models.py:142 common/templates/_header.html:41
msgid "Game" msgid "Game"
msgstr "游戏" msgstr "游戏"
#: catalog/common/models.py:117 #: catalog/common/models.py:115
msgid "Podcast Program" msgid "Podcast Program"
msgstr "播客节目" msgstr "播客节目"
#: catalog/common/models.py:118 #: catalog/common/models.py:116
msgid "Podcast Episode" msgid "Podcast Episode"
msgstr "播客单集" msgstr "播客单集"
#: catalog/common/models.py:119 catalog/common/models.py:133 #: catalog/common/models.py:117 catalog/common/models.py:131
#: catalog/common/models.py:146 common/templates/_header.html:45 #: catalog/common/models.py:144 common/templates/_header.html:45
msgid "Performance" msgid "Performance"
msgstr "演出" msgstr "演出"
#: catalog/common/models.py:120 #: catalog/common/models.py:118
msgid "Production" msgid "Production"
msgstr "上演" msgstr "上演"
#: catalog/common/models.py:121 #: catalog/common/models.py:119
msgid "Fanfic" msgid "Fanfic"
msgstr "网文" msgstr "网文"
#: catalog/common/models.py:122 catalog/common/models.py:135 #: catalog/common/models.py:120 catalog/common/models.py:133
msgid "Exhibition" msgid "Exhibition"
msgstr "展览" msgstr "展览"
#: catalog/common/models.py:123 catalog/common/models.py:136 #: catalog/common/models.py:121 catalog/common/models.py:134
#: journal/templates/collection.html:15 journal/templates/collection.html:22 #: journal/templates/collection.html:15 journal/templates/collection.html:22
#: journal/templates/collection_edit.html:9 #: journal/templates/collection_edit.html:9
#: journal/templates/collection_share.html:12 #: journal/templates/collection_share.html:12
msgid "Collection" msgid "Collection"
msgstr "收藏单" msgstr "收藏单"
#: catalog/common/models.py:129 catalog/common/models.py:142 #: catalog/common/models.py:127 catalog/common/models.py:140
msgid "TV" msgid "TV"
msgstr "剧集" msgstr "剧集"
#: catalog/common/models.py:130 catalog/common/models.py:143 #: catalog/common/models.py:128 catalog/common/models.py:141
#: common/templates/_header.html:37 #: common/templates/_header.html:37
msgid "Music" msgid "Music"
msgstr "音乐" msgstr "音乐"
#: catalog/common/models.py:132 catalog/common/models.py:145 #: catalog/common/models.py:130 catalog/common/models.py:143
#: catalog/templates/_sidebar_edit.html:152 common/templates/_header.html:33 #: catalog/templates/_sidebar_edit.html:152 common/templates/_header.html:33
msgid "Podcast" msgid "Podcast"
msgstr "播客" msgstr "播客"
#: catalog/common/models.py:134 #: catalog/common/models.py:132
msgid "FanFic" msgid "FanFic"
msgstr "网文" msgstr "网文"
#: catalog/common/models.py:257 journal/models/collection.py:48 #: catalog/common/models.py:255 journal/models/collection.py:48
msgid "title" msgid "title"
msgstr "标题" msgstr "标题"
#: catalog/common/models.py:258 journal/models/collection.py:49 #: catalog/common/models.py:256 journal/models/collection.py:49
msgid "description" msgid "description"
msgstr "描述" msgstr "描述"
#: catalog/common/models.py:260 catalog/forms.py:26 #: catalog/common/models.py:258 catalog/forms.py:26
msgid "Primary ID Type" msgid "Primary ID Type"
msgstr "主要标识类型" msgstr "主要标识类型"
#: catalog/common/models.py:263 catalog/forms.py:31 #: catalog/common/models.py:261 catalog/forms.py:31
msgid "Primary ID Value" msgid "Primary ID Value"
msgstr "主要标识数据" msgstr "主要标识数据"
#: catalog/common/models.py:269 #: catalog/common/models.py:267
msgid "metadata" msgid "metadata"
msgstr "元数据" msgstr "元数据"
#: catalog/common/models.py:271 #: catalog/common/models.py:269
msgid "cover" msgid "cover"
msgstr "封面" msgstr "封面"
#: catalog/common/models.py:565 #: catalog/common/models.py:563
msgid "source site" msgid "source site"
msgstr "来源站点" msgstr "来源站点"
#: catalog/common/models.py:567 #: catalog/common/models.py:565
msgid "ID on source site" msgid "ID on source site"
msgstr "来源站点标识" msgstr "来源站点标识"
#: catalog/common/models.py:569 #: catalog/common/models.py:567
msgid "source url" msgid "source url"
msgstr "来源站点网址" msgstr "来源站点网址"
#: catalog/common/models.py:581 #: catalog/common/models.py:579
msgid "IdType of the source site" msgid "IdType of the source site"
msgstr "来源站点的主要标识类型" msgstr "来源站点的主要标识类型"
#: catalog/common/models.py:587 #: catalog/common/models.py:585
msgid "Primary Id on the source site" msgid "Primary Id on the source site"
msgstr "来源站点的主要标识数据" msgstr "来源站点的主要标识数据"
#: catalog/common/models.py:590 #: catalog/common/models.py:588
msgid "url to the resource" msgid "url to the resource"
msgstr "指向外部资源的网址" msgstr "指向外部资源的网址"
@ -1361,17 +1361,17 @@ msgstr "{show_title} 第{season_number}季"
msgid "{season_title} E{episode_number}" msgid "{season_title} E{episode_number}"
msgstr "{season_title} 第{episode_number}集" msgstr "{season_title} 第{episode_number}集"
#: catalog/views.py:53 catalog/views.py:76 #: catalog/views.py:49 catalog/views.py:72
msgid "Item not found" msgid "Item not found"
msgstr "条目不存在" msgstr "条目不存在"
#: catalog/views.py:57 catalog/views.py:84 #: catalog/views.py:53 catalog/views.py:80
msgid "Item no longer exists" msgid "Item no longer exists"
msgstr "条目已不存在" msgstr "条目已不存在"
#: catalog/views_edit.py:125 catalog/views_edit.py:148 #: catalog/views_edit.py:122 catalog/views_edit.py:145
#: catalog/views_edit.py:200 catalog/views_edit.py:276 #: catalog/views_edit.py:197 catalog/views_edit.py:273
#: catalog/views_edit.py:353 journal/views/collection.py:52 #: catalog/views_edit.py:350 journal/views/collection.py:52
#: journal/views/collection.py:102 journal/views/collection.py:114 #: journal/views/collection.py:102 journal/views/collection.py:114
#: journal/views/collection.py:130 journal/views/collection.py:161 #: journal/views/collection.py:130 journal/views/collection.py:161
#: journal/views/collection.py:180 journal/views/collection.py:200 #: journal/views/collection.py:180 journal/views/collection.py:200
@ -1383,34 +1383,34 @@ msgstr "条目已不存在"
msgid "Insufficient permission" msgid "Insufficient permission"
msgstr "权限不足" msgstr "权限不足"
#: catalog/views_edit.py:203 journal/views/collection.py:229 #: catalog/views_edit.py:200 journal/views/collection.py:229
#: journal/views/collection.py:296 journal/views/common.py:81 #: journal/views/collection.py:296 journal/views/common.py:81
#: journal/views/mark.py:139 journal/views/post.py:41 journal/views/post.py:55 #: journal/views/mark.py:139 journal/views/post.py:41 journal/views/post.py:55
#: journal/views/review.py:93 journal/views/review.py:96 users/views.py:169 #: journal/views/review.py:93 journal/views/review.py:96 users/views.py:169
msgid "Invalid parameter" msgid "Invalid parameter"
msgstr "无效参数" msgstr "无效参数"
#: catalog/views_edit.py:252 #: catalog/views_edit.py:249
msgid "Must be a TV Season with IMDB id and season id" msgid "Must be a TV Season with IMDB id and season id"
msgstr "" msgstr ""
#: catalog/views_edit.py:257 #: catalog/views_edit.py:254
msgid "Updating episodes" msgid "Updating episodes"
msgstr "" msgstr ""
#: catalog/views_edit.py:287 #: catalog/views_edit.py:284
msgid "Cannot be merged to an item already deleted or merged" msgid "Cannot be merged to an item already deleted or merged"
msgstr "" msgstr ""
#: catalog/views_edit.py:290 #: catalog/views_edit.py:287
msgid "Cannot merge items in different categories" msgid "Cannot merge items in different categories"
msgstr "" msgstr ""
#: catalog/views_edit.py:327 #: catalog/views_edit.py:324
msgid "Cannot be linked to an item already deleted or merged" msgid "Cannot be linked to an item already deleted or merged"
msgstr "" msgstr ""
#: catalog/views_edit.py:329 #: catalog/views_edit.py:326
msgid "Cannot link items other than editions" msgid "Cannot link items other than editions"
msgstr "" msgstr ""
@ -2238,7 +2238,7 @@ msgstr "日历"
msgid "annual summary" msgid "annual summary"
msgstr "年度小结" msgstr "年度小结"
#: journal/templates/profile.html:131 mastodon/api.py:676 #: journal/templates/profile.html:131 mastodon/api.py:677
msgid "collection" msgid "collection"
msgstr "收藏单" msgstr "收藏单"
@ -2386,21 +2386,21 @@ msgstr "找不到条目,请使用本站条目网址。"
msgid "Login required" msgid "Login required"
msgstr "登录后访问" msgstr "登录后访问"
#: journal/views/common.py:33 journal/views/mark.py:117 #: journal/views/common.py:33 journal/views/mark.py:116
msgid "Data saved but unable to repost to Fediverse." msgid "Data saved but unable to repost to Fediverse instance."
msgstr "数据已保存但未能转发到联邦宇宙。" msgstr "数据已保存但未能转发到联邦实例。"
#: journal/views/common.py:35 #: journal/views/common.py:35
msgid "Redirecting to your Mastodon instance now to re-authenticate." msgid "Redirecting to your Fediverse instance now to re-authenticate."
msgstr "正在重定向到你的Mastodon实例以重新认证。" msgstr "正在重定向到你的联邦实例以重新认证。"
#: journal/views/common.py:42 #: journal/views/common.py:42
msgid "List not found." msgid "List not found."
msgstr "列表未找到" msgstr "列表未找到"
#: journal/views/mark.py:109 #: journal/views/mark.py:107
msgid "Content too long for your Mastodon instance." msgid "Content too long for your Fediverse instance."
msgstr "内容过长,超出了你的Mastodon实例的限制。" msgstr "内容过长,超出了你的联邦实例的限制。"
#: journal/views/mark.py:161 journal/views/review.py:30 #: journal/views/mark.py:161 journal/views/review.py:30
msgid "Content not found" msgid "Content not found"
@ -2444,16 +2444,16 @@ msgstr "标签已更新"
msgid "Summary posted to timeline." msgid "Summary posted to timeline."
msgstr "总结已发布到时间轴" msgstr "总结已发布到时间轴"
#: mastodon/api.py:519 takahe/utils.py:514 #: mastodon/api.py:520 takahe/utils.py:514
#, python-brace-format #, python-brace-format
msgid "regarding {item_title}, may contain spoiler or triggering content" msgid "regarding {item_title}, may contain spoiler or triggering content"
msgstr "关于 {item_title},可能包含剧透或敏感内容" msgstr "关于 {item_title},可能包含剧透或敏感内容"
#: mastodon/api.py:681 #: mastodon/api.py:682
msgid "shared my collection" msgid "shared my collection"
msgstr "分享我的收藏单" msgstr "分享我的收藏单"
#: mastodon/api.py:684 #: mastodon/api.py:685
#, python-brace-format #, python-brace-format
msgid "shared {username}'s collection" msgid "shared {username}'s collection"
msgstr "分享 {username} 的收藏单" msgstr "分享 {username} 的收藏单"
@ -2801,8 +2801,8 @@ msgid "Authentication failed"
msgstr "认证失败" msgstr "认证失败"
#: users/account.py:159 #: users/account.py:159
msgid "Invalid response from Mastodon instance." msgid "Invalid response from Fediverse instance."
msgstr "Mastodon实例返回信息无效" msgstr "联邦实例返回信息无效"
#: users/account.py:169 #: users/account.py:169
msgid "Invalid cookie data." msgid "Invalid cookie data."
@ -2813,12 +2813,12 @@ msgid "Invalid instance domain"
msgstr "实例域名无效" msgstr "实例域名无效"
#: users/account.py:182 #: users/account.py:182
msgid "Invalid token from Mastodon instance." msgid "Invalid token from Fediverse instance."
msgstr "Mastodon实例返回了无效的认证令牌。" msgstr "联邦实例返回了无效的认证令牌。"
#: users/account.py:205 users/account.py:559 #: users/account.py:205 users/account.py:559
msgid "Invalid account data from Mastodon instance." msgid "Invalid account data from Fediverse instance."
msgstr "Mastodon实例返回了无效的账号数据。" msgstr "联邦实例返回了无效的账号数据。"
#: users/account.py:230 #: users/account.py:230
msgid "Registration is for invitation only" msgid "Registration is for invitation only"
@ -2953,39 +2953,39 @@ msgstr "登录信息已更新"
msgid "Account mismatch." msgid "Account mismatch."
msgstr "账号信息不匹配。" msgstr "账号信息不匹配。"
#: users/data.py:123 #: users/data.py:124
msgid "Generating exports." msgid "Generating exports."
msgstr "正在生成导出文件。" msgstr "正在生成导出文件。"
#: users/data.py:135 #: users/data.py:136
msgid "Export file expired. Please export again." msgid "Export file expired. Please export again."
msgstr "导出文件已失效,请重新导出" msgstr "导出文件已失效,请重新导出"
#: users/data.py:146 #: users/data.py:147
msgid "Sync in progress." msgid "Sync in progress."
msgstr "正在同步。" msgstr "正在同步。"
#: users/data.py:160 #: users/data.py:161
msgid "Settings saved." msgid "Settings saved."
msgstr "设置已保存" msgstr "设置已保存"
#: users/data.py:171 #: users/data.py:172
msgid "Reset completed." msgid "Reset completed."
msgstr "重置已完成。" msgstr "重置已完成。"
#: users/data.py:180 #: users/data.py:181
msgid "Import in progress." msgid "Import in progress."
msgstr "正在导出" msgstr "正在导出"
#: users/data.py:182 #: users/data.py:183
msgid "Invalid URL." msgid "Invalid URL."
msgstr "无效网址。" msgstr "无效网址。"
#: users/data.py:196 users/data.py:221 users/data.py:236 #: users/data.py:197 users/data.py:222 users/data.py:237
msgid "File is uploaded and will be imported soon." msgid "File is uploaded and will be imported soon."
msgstr "文件已上传,等待后台导入。" msgstr "文件已上传,等待后台导入。"
#: users/data.py:199 users/data.py:239 #: users/data.py:200 users/data.py:240
msgid "Invalid file." msgid "Invalid file."
msgstr "无效文件。" msgstr "无效文件。"

View file

@ -4,6 +4,7 @@ reportIncompatibleVariableOverride = false
reportUnusedImport = false reportUnusedImport = false
reportUnknownVariableType = false reportUnknownVariableType = false
reportConstantRedefinition = false reportConstantRedefinition = false
reportUnusedCallResult = false
[tool.djlint] [tool.djlint]
ignore="T002,T003,H005,H006,H019,H020,H021,H023,H030,H031,D018" ignore="T002,T003,H005,H006,H019,H020,H021,H023,H030,H031,D018"

View file

@ -6,7 +6,6 @@ ActivityManager generates chronological view for user and, in future, ActivitySt
""" """
import logging
from functools import cached_property from functools import cached_property
from typing import Type from typing import Type
@ -15,6 +14,7 @@ from django.db import models
from django.db.models import Q from django.db.models import Q
from django.db.models.signals import post_delete, post_save, pre_delete from django.db.models.signals import post_delete, post_save, pre_delete
from django.utils import timezone from django.utils import timezone
from loguru import logger
from catalog.common.models import Item from catalog.common.models import Item
from journal.models import ( from journal.models import (
@ -29,8 +29,6 @@ from journal.models import (
) )
from users.models import APIdentity from users.models import APIdentity
_logger = logging.getLogger(__name__)
class ActivityTemplate(models.TextChoices): class ActivityTemplate(models.TextChoices):
MarkItem = "mark_item" MarkItem = "mark_item"
@ -105,7 +103,7 @@ class DataSignalManager:
@staticmethod @staticmethod
def add_handler_for_model(model): def add_handler_for_model(model):
if settings.DISABLE_MODEL_SIGNAL: if settings.DISABLE_MODEL_SIGNAL:
_logger.warn( logger.warning(
f"{model.__name__} are not being indexed with DISABLE_MODEL_SIGNAL configuration" f"{model.__name__} are not being indexed with DISABLE_MODEL_SIGNAL configuration"
) )
return return

View file

@ -1,5 +1,3 @@
import logging
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.urls import reverse from django.urls import reverse

View file

@ -156,7 +156,7 @@ def connect_redirect_back(request):
"common/error.html", "common/error.html",
{ {
"msg": _("Authentication failed"), "msg": _("Authentication failed"),
"secondary_msg": _("Invalid response from Mastodon instance."), "secondary_msg": _("Invalid response from Fediverse instance."),
}, },
) )
site = request.session.get("mastodon_domain") site = request.session.get("mastodon_domain")
@ -179,7 +179,7 @@ def connect_redirect_back(request):
"common/error.html", "common/error.html",
{ {
"msg": _("Authentication failed"), "msg": _("Authentication failed"),
"secondary_msg": _("Invalid token from Mastodon instance."), "secondary_msg": _("Invalid token from Fediverse instance."),
}, },
) )
@ -202,7 +202,7 @@ def connect_redirect_back(request):
"common/error.html", "common/error.html",
{ {
"msg": _("Authentication failed"), "msg": _("Authentication failed"),
"secondary_msg": _("Invalid account data from Mastodon instance."), "secondary_msg": _("Invalid account data from Fediverse instance."),
}, },
) )
return register_new_user( return register_new_user(
@ -348,7 +348,7 @@ def send_verification_link(user_id, action, email, code=""):
fail_silently=False, fail_silently=False,
) )
except Exception as e: except Exception as e:
logger.error(e) logger.error(f"send email {email} failed", extra={"exception": e})
@require_http_methods(["POST"]) @require_http_methods(["POST"])
@ -418,7 +418,7 @@ def verify_email(request):
else: else:
return register_new_user(request, username=None, email=email) return register_new_user(request, username=None, email=email)
except Exception as e: except Exception as e:
logger.error(e) logger.error("verify email error", extra={"exception": e, "s": s})
error = _("Unable to verify") error = _("Unable to verify")
return render( return render(
request, "users/verify_email.html", {"success": False, "error": error} request, "users/verify_email.html", {"success": False, "error": error}
@ -556,7 +556,7 @@ def swap_login(request, token, site, refresh_token):
) )
else: else:
messages.add_message( messages.add_message(
request, messages.ERROR, _("Invalid account data from Mastodon instance.") request, messages.ERROR, _("Invalid account data from Fediverse instance.")
) )
return redirect(reverse("users:data")) return redirect(reverse("users:data"))

View file

@ -66,7 +66,9 @@ class Task(models.Model):
task.state = cls.States.complete task.state = cls.States.complete
task.save(update_fields=["state"]) task.save(update_fields=["state"])
except Exception as e: except Exception as e:
logger.error(f"Task {task} Exception {e}") logger.error(
f"error running {cls.__name__}", extra={"exception": e, "task": task_id}
)
task.message = "Error occured." task.message = "Error occured."
task.state = cls.States.failed task.state = cls.States.failed
task.save(update_fields=["state", "message"]) task.save(update_fields=["state", "message"])

View file

@ -310,7 +310,10 @@ class User(AbstractUser):
f = ContentFile(r.content, name=identity.icon_uri.split("/")[-1]) f = ContentFile(r.content, name=identity.icon_uri.split("/")[-1])
identity.icon.save(f.name, f, save=False) identity.icon.save(f.name, f, save=False)
except Exception as e: except Exception as e:
logger.error(f"Get icon failed: {identity} {identity.icon_uri} {e}") logger.error(
f"fetch icon failed: {identity} {identity.icon_uri}",
extra={"exception": e},
)
identity.save() identity.save()
def refresh_mastodon_data(self, skip_detail=False): def refresh_mastodon_data(self, skip_detail=False):