diff --git a/boofilsic/settings.py b/boofilsic/settings.py index 7f4145e3..b525a0af 100644 --- a/boofilsic/settings.py +++ b/boofilsic/settings.py @@ -574,7 +574,10 @@ if SENTRY_DSN: sentry_sdk.init( dsn=SENTRY_DSN, environment=sentry_env or "unknown", - integrations=[LoguruIntegration(), DjangoIntegration()], + integrations=[ + DjangoIntegration(), + LoguruIntegration(event_format="{name}:{function}:{line} - {message}"), + ], release=NEODB_VERSION, send_default_pii=True, traces_sample_rate=env("NEODB_SENTRY_SAMPLE_RATE"), diff --git a/catalog/book/models.py b/catalog/book/models.py index fb56bcf5..b4cef44c 100644 --- a/catalog/book/models.py +++ b/catalog/book/models.py @@ -22,7 +22,7 @@ from os.path import exists from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.utils.translation import gettext_lazy as _ -from loguru import logger as _logger +from loguru import logger from catalog.common import ( BaseSchema, @@ -186,9 +186,9 @@ class Edition(Item): if work_res: work = work_res.item if not work: - _logger.warning(f"Unable to find work for {work_res}") + logger.warning(f"Unable to find work for {work_res}") else: - _logger.warning( + logger.warning( f'Unable to find resource for {w["id_type"]}:{w["id_value"]}' ) work = Work.objects.filter( diff --git a/catalog/common/downloaders.py b/catalog/common/downloaders.py index 2078086a..b1add7ca 100644 --- a/catalog/common/downloaders.py +++ b/catalog/common/downloaders.py @@ -11,14 +11,12 @@ import filetype import requests from django.conf import settings from django.core.cache import cache +from loguru import logger from lxml import etree, html from PIL import Image from requests import Response from requests.exceptions import RequestException -_logger = logging.getLogger(__name__) - - RESPONSE_OK = 0 # response is ready for pasring RESPONSE_INVALID_CONTENT = -1 # content not valid but no need to retry RESPONSE_NETWORK_ERROR = -2 # network error, retry next proxied url @@ -67,11 +65,11 @@ class MockResponse: try: self.content = Path(fn).read_bytes() 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: self.content = b"Error: response file not found" 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 def text(self): @@ -173,7 +171,7 @@ class BasicDownloader: ) as fp: fp.write(resp.text) except Exception: - _logger.warn("Save downloaded data failed.") + logger.warning("Save downloaded data failed.") else: resp = MockResponse(self.url) response_type = self.validate_response(resp) @@ -249,7 +247,7 @@ class RetryDownloader(BasicDownloader): elif self.response_type != RESPONSE_NETWORK_ERROR and retries == 0: raise DownloadError(self) elif retries > 0: - _logger.debug("Retry " + self.url) + logger.debug("Retry " + self.url) time.sleep((settings.DOWNLOADER_RETRIES - retries) * 0.5) raise DownloadError(self, "max out of retries") diff --git a/catalog/common/models.py b/catalog/common/models.py index a3f667d1..1bc55180 100644 --- a/catalog/common/models.py +++ b/catalog/common/models.py @@ -1,4 +1,3 @@ -import logging import re import uuid from functools import cached_property @@ -13,6 +12,7 @@ from django.db import connection, models from django.utils import timezone from django.utils.baseconv import base62 from django.utils.translation import gettext_lazy as _ +from loguru import logger from ninja import Field, Schema from polymorphic.models import PolymorphicModel @@ -24,8 +24,6 @@ from .utils import DEFAULT_ITEM_COVER, item_cover_path, resource_cover_path if TYPE_CHECKING: from users.models import User -_logger = logging.getLogger(__name__) - class SiteName(models.TextChoices): Unknown = "unknown", _("Unknown") @@ -406,7 +404,7 @@ class Item(SoftDeleteMixin, PolymorphicModel): res.save() 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: return self if model not in Item.__subclasses__(): @@ -631,7 +629,7 @@ class ExternalResource(models.Model): site = self.get_site() return site.SITE_NAME if site else SiteName.Unknown except Exception: - _logger.warning(f"Unknown site for {self}") + logger.warning(f"Unknown site for {self}") return SiteName.Unknown @property diff --git a/catalog/common/sites.py b/catalog/common/sites.py index 07d42dea..70f0d258 100644 --- a/catalog/common/sites.py +++ b/catalog/common/sites.py @@ -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 """ import json -import logging import re from dataclasses import dataclass, field from typing import Any, Callable, Type, TypeVar import django_rq import requests +from loguru import logger from validators import url as url_validate from .models import ExternalResource, IdealIdTypes, IdType, Item, SiteName -_logger = logging.getLogger(__name__) - @dataclass class ResourceContent: @@ -238,7 +236,7 @@ class AbstractSite: if resource_content: p.update_content(resource_content) 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 if auto_create: # and (p.item is None or p.item.is_deleted): self.get_item() @@ -259,7 +257,7 @@ class AbstractSite: preloaded_content=linked_resource.get("content"), ) 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: django_rq.get_queue("crawl").enqueue(crawl_related_resources_task, p.pk) if p.item: @@ -338,7 +336,7 @@ class SiteManager: def crawl_related_resources_task(resource_pk): resource = ExternalResource.objects.filter(pk=resource_pk).first() if not resource: - _logger.warn(f"crawl resource not found {resource_pk}") + logger.warning(f"crawl resource not found {resource_pk}") return links = (resource.related_resources or []) + (resource.prematched_resources or []) # 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) item = site.get_item() if item: - _logger.info(f"crawled {w} {item}") + logger.info(f"crawled {w} {item}") else: - _logger.warn(f"crawl {w} failed") + logger.warning(f"crawl {w} failed") except Exception as e: - _logger.warn(f"crawl {w} error {e}") + logger.warning(f"crawl {w} error {e}") diff --git a/catalog/common/utils.py b/catalog/common/utils.py index 08023c09..c4b8053a 100644 --- a/catalog/common/utils.py +++ b/catalog/common/utils.py @@ -1,11 +1,7 @@ -import logging import uuid from django.utils import timezone -_logger = logging.getLogger(__name__) - - DEFAULT_ITEM_COVER = "item/default.svg" diff --git a/catalog/models.py b/catalog/models.py index 4f89258d..dd22d32e 100644 --- a/catalog/models.py +++ b/catalog/models.py @@ -1,8 +1,7 @@ -import logging - from auditlog.registry import auditlog from django.conf import settings from django.contrib.contenttypes.models import ContentType +from loguru import logger from .book.models import Edition, EditionInSchema, EditionSchema, Series, Work from .collection.models import Collection as CatalogCollection @@ -42,8 +41,6 @@ from .tv.models import ( from .search.models import Indexer # isort:skip -_logger = logging.getLogger(__name__) - # class Exhibition(Item): @@ -59,7 +56,7 @@ _logger = logging.getLogger(__name__) def init_catalog_search_models(): if settings.DISABLE_MODEL_SIGNAL: - _logger.warn( + logger.warning( "Catalog models are not being indexed with DISABLE_MODEL_SIGNAL configuration" ) return @@ -100,4 +97,4 @@ def init_catalog_audit_log(): 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()}") diff --git a/catalog/search/external.py b/catalog/search/external.py index aaceed48..cd5aaa9a 100644 --- a/catalog/search/external.py +++ b/catalog/search/external.py @@ -53,7 +53,7 @@ class SearchResultItem: class Goodreads: @classmethod - def search(cls, q, page=1): + def search(cls, q: str, page=1): results = [] search_url = f"https://www.goodreads.com/search?page={page}&q={quote_plus(q)}" try: @@ -115,7 +115,7 @@ class Goodreads: except requests.exceptions.RequestException as e: logger.warning(f"Search {search_url} error: {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 @@ -164,7 +164,7 @@ class GoogleBooks: except requests.exceptions.RequestException as e: logger.warning(f"Search {api_url} error: {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 @@ -206,7 +206,7 @@ class TheMovieDatabase: except requests.exceptions.RequestException as e: logger.warning(f"Search {api_url} error: {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 @@ -242,7 +242,7 @@ class Spotify: except requests.exceptions.RequestException as e: logger.warning(f"Search {api_url} error: {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 @@ -278,7 +278,7 @@ class Bandcamp: except requests.exceptions.RequestException as e: logger.warning(f"Search {search_url} error: {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 @@ -305,7 +305,9 @@ class ApplePodcast: except requests.exceptions.RequestException as e: logger.warning(f"Search {search_url} error: {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 @@ -322,7 +324,10 @@ class Fediverse: ) r = response.json() 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 [] if "data" in r: for item in r["data"]: diff --git a/catalog/search/models.py b/catalog/search/models.py index 2ef4efe1..cf017876 100644 --- a/catalog/search/models.py +++ b/catalog/search/models.py @@ -1,12 +1,12 @@ # pyright: reportFunctionMemberAccess=false import hashlib -import logging import django_rq from auditlog.context import set_actor from django.conf import settings from django.core.cache import cache from django.utils.translation import gettext_lazy as _ +from loguru import logger from rq.job import Job from catalog.common.sites import SiteManager @@ -16,8 +16,6 @@ from .typesense import Indexer as TypeSenseIndexer # from .meilisearch import Indexer as MeiliSearchIndexer -_logger = logging.getLogger(__name__) - class DbIndexer: @classmethod @@ -154,10 +152,10 @@ def _fetch_task(url, is_refetch, user): site.get_resource_ready(ignore_existing_content=is_refetch) item = site.get_item() if item: - _logger.info(f"fetched {url} {item.url} {item}") + logger.info(f"fetched {url} {item.url} {item}") item_url = item.url else: - _logger.error(f"fetch {url} failed") + logger.error(f"fetch {url} failed") except Exception as e: - _logger.error(f"fetch {url} error {e}") + logger.error(f"fetch {url} error", extra={"exception": e}) return item_url diff --git a/catalog/search/typesense.py b/catalog/search/typesense.py index 961f4d9b..a78eebdb 100644 --- a/catalog/search/typesense.py +++ b/catalog/search/typesense.py @@ -1,4 +1,3 @@ -import logging import types from datetime import timedelta from pprint import pprint diff --git a/catalog/sites/igdb.py b/catalog/sites/igdb.py index ed85697c..825078ba 100644 --- a/catalog/sites/igdb.py +++ b/catalog/sites/igdb.py @@ -6,17 +6,16 @@ use (e.g. "portal-2") as id, which is different from real id in IGDB API import datetime import json -import logging import requests from django.conf import settings from django.core.cache import cache from igdb.wrapper import IGDBWrapper +from loguru import logger from catalog.common import * from catalog.models import * -_logger = logging.getLogger(__name__) _cache_key = "igdb_access_token" @@ -32,8 +31,8 @@ def _igdb_access_token(): token = j["access_token"] ttl = j["expires_in"] - 60 cache.set(_cache_key, token, ttl) - except Exception: - _logger.error("unable to obtain IGDB token") + except Exception as e: + logger.error("unable to obtain IGDB token", extra={"exception": e}) token = "" return token @@ -156,7 +155,7 @@ class IGDB(AbstractSite): pd.cover_image = imgdl.download().content pd.cover_image_extention = imgdl.extention except Exception: - _logger.debug( + logger.debug( f'failed to download cover for {self.url} from {pd.metadata["cover_image_url"]}' ) return pd diff --git a/catalog/views.py b/catalog/views.py index e41cd5e3..86574722 100644 --- a/catalog/views.py +++ b/catalog/views.py @@ -1,5 +1,3 @@ -import logging - from django.contrib.auth.decorators import login_required from django.core.cache import cache from django.core.paginator import Paginator @@ -29,9 +27,6 @@ from .models import * from .search.views import * from .views_edit import * -_logger = logging.getLogger(__name__) - - NUM_REVIEWS_ON_ITEM_PAGE = 5 NUM_REVIEWS_ON_LIST_PAGE = 20 diff --git a/catalog/views_edit.py b/catalog/views_edit.py index fc99ba90..9872f09f 100644 --- a/catalog/views_edit.py +++ b/catalog/views_edit.py @@ -1,5 +1,3 @@ -import logging - from auditlog.context import set_actor from django.contrib import messages 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.translation import gettext_lazy as _ from django.views.decorators.http import require_http_methods +from loguru import logger from common.utils import discord_send, get_uuid_or_404 from journal.models import update_journal_for_merged_item @@ -19,8 +18,6 @@ from .models import * from .search.views import * from .sites.imdb import IMDB as IMDB -_logger = logging.getLogger(__name__) - def _add_error_map_detail(e): e.additonal_detail = [] @@ -171,7 +168,7 @@ def recast(request, item_path, item_uuid): raise BadRequest("Invalid target type") if isinstance(item, model): 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( "audit", 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): 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.save(update_fields=["show"]) new_item = item.recast_to(model) @@ -218,7 +215,7 @@ def assign_parent(request, item_path, item_uuid): raise BadRequest("Incompatible child item type") # if not request.user.is_staff and item.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.save() return redirect(item.url) @@ -290,7 +287,7 @@ def merge(request, item_path, item_uuid): _("Cannot merge items in different categories") + 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) update_journal_for_merged_item(item_uuid) discord_send( @@ -302,7 +299,7 @@ def merge(request, item_path, item_uuid): return redirect(new_item.url) else: 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) discord_send( "audit", @@ -334,7 +331,7 @@ def link_edition(request, item_path, item_uuid): "catalog_merge.html", {"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) discord_send( "audit", diff --git a/common/api.py b/common/api.py index 1f4415ff..9e9988c6 100644 --- a/common/api.py +++ b/common/api.py @@ -1,8 +1,8 @@ -import logging from typing import Any, Callable, List, Optional, Tuple, Type from django.conf import settings from django.db.models import QuerySet +from loguru import logger from ninja import NinjaAPI, Schema from ninja.pagination import PageNumberPagination as NinjaPageNumberPagination from ninja.security import HttpBearer @@ -10,9 +10,6 @@ from oauth2_provider.oauth2_backends import OAuthLibCore from oauth2_provider.oauth2_validators import OAuth2Validator from oauthlib.oauth2 import Server -_logger = logging.getLogger(__name__) - - PERMITTED_WRITE_METHODS = ["PUT", "POST", "DELETE", "PATCH"] PERMITTED_READ_METHODS = ["GET", "HEAD", "OPTIONS"] @@ -20,7 +17,7 @@ PERMITTED_READ_METHODS = ["GET", "HEAD", "OPTIONS"] class OAuthAccessTokenAuth(HttpBearer): def authenticate(self, request, token) -> bool: 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 request_scopes = [] request_method = request.method @@ -34,7 +31,7 @@ class OAuthAccessTokenAuth(HttpBearer): core = OAuthLibCore(Server(validator)) valid, oauthlib_req = core.verify_request(request, scopes=request_scopes) 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 diff --git a/common/management/commands/cron.py b/common/management/commands/cron.py index 88a1a7bb..ad287380 100644 --- a/common/management/commands/cron.py +++ b/common/management/commands/cron.py @@ -49,7 +49,7 @@ class Command(BaseCommand): for job_id in options["runonce"]: run = JobManager.run(job_id) if not run: - logger.error(f"Job not found: {job_id}") + logger.error(f"job not found: {job_id}") if options["list"]: all_jobs = [j.__name__ for j in JobManager.registry] logger.info(f"{len(all_jobs)} available jobs: {' '.join(all_jobs)}") diff --git a/journal/importers/douban.py b/journal/importers/douban.py index f22f5109..9523dd34 100644 --- a/journal/importers/douban.py +++ b/journal/importers/douban.py @@ -1,4 +1,3 @@ -import logging import os import re from datetime import datetime @@ -36,8 +35,8 @@ def _fetch_remote_image(url): binary_file.write(raw_img) # logger.info(f'remote image saved as {local_url}') return local_url - except Exception: - logger.error(f"unable to fetch remote image {url}") + except Exception as e: + logger.error(f"unable to fetch image", extra={"url": url, "exception": e}) return url @@ -107,7 +106,9 @@ class DoubanImporter: self.import_from_file_task, job_id=jid ) except Exception as e: - logger.error(e) + logger.error( + f"unable to enqueue import {uploaded_file}", extra={"exception": e} + ) return False return True @@ -327,7 +328,7 @@ class DoubanImporter: # logger.info(f"matched {url}") print(".", end="", flush=True) except Exception as e: - logger.error(f"fetching exception: {url} {e}") + logger.error(f"fetching error: {url}", extra={"exception": e}) if item is None: self.failed.append(str(url)) return item diff --git a/journal/views/common.py b/journal/views/common.py index 639adcef..25a2fb93 100644 --- a/journal/views/common.py +++ b/journal/views/common.py @@ -30,9 +30,9 @@ def render_relogin(request): "common/error.html", { "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": _( - "Redirecting to your Mastodon instance now to re-authenticate." + "Redirecting to your Fediverse instance now to re-authenticate." ), }, ) diff --git a/journal/views/mark.py b/journal/views/mark.py index 30f8169b..0909850a 100644 --- a/journal/views/mark.py +++ b/journal/views/mark.py @@ -1,4 +1,3 @@ -import logging from datetime import datetime from django.conf import settings @@ -10,6 +9,7 @@ from django.utils import timezone from django.utils.dateparse import parse_datetime from django.utils.translation import gettext_lazy as _ from django.views.decorators.http import require_http_methods +from loguru import logger from catalog.models import * 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 .common import render_list, render_relogin, target_identity_required -_logger = logging.getLogger(__name__) PAGE_SIZE = 10 _checkmark = "✔️".encode("utf-8") @@ -101,12 +100,12 @@ def mark(request: AuthedHttpRequest, item_uuid): created_time=mark_date, ) 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) 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 = ( - _("Content too long for your Mastodon instance.") + _("Content too long for your Fediverse instance.") if str(e) == "422" else str(e) ) @@ -114,7 +113,9 @@ def mark(request: AuthedHttpRequest, item_uuid): request, "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, }, ) diff --git a/locale/zh_Hans/LC_MESSAGES/django.po b/locale/zh_Hans/LC_MESSAGES/django.po index 3a24e138..d8a43ab8 100644 --- a/locale/zh_Hans/LC_MESSAGES/django.po +++ b/locale/zh_Hans/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\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" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -92,315 +92,315 @@ msgstr "出品方" msgid "other title" msgstr "其它标题" -#: catalog/common/models.py:31 +#: catalog/common/models.py:29 msgid "Unknown" msgstr "未知" -#: catalog/common/models.py:32 +#: catalog/common/models.py:30 msgid "Douban" msgstr "豆瓣" -#: catalog/common/models.py:33 catalog/common/models.py:66 +#: catalog/common/models.py:31 catalog/common/models.py:64 msgid "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" msgstr "谷歌图书" -#: catalog/common/models.py:35 +#: catalog/common/models.py:33 msgid "BooksTW" 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/tvshow.html:63 msgid "IMDb" msgstr "IMDb" -#: catalog/common/models.py:37 +#: catalog/common/models.py:35 msgid "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" msgstr "Bandcamp" -#: catalog/common/models.py:39 +#: catalog/common/models.py:37 msgid "Spotify" msgstr "Spotify" -#: catalog/common/models.py:40 +#: catalog/common/models.py:38 msgid "IGDB" msgstr "IGDB" -#: catalog/common/models.py:41 +#: catalog/common/models.py:39 msgid "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" msgstr "Bangumi" -#: catalog/common/models.py:43 +#: catalog/common/models.py:41 msgid "BGG" msgstr "BGG" -#: catalog/common/models.py:45 +#: catalog/common/models.py:43 msgid "RSS" msgstr "RSS" -#: catalog/common/models.py:46 +#: catalog/common/models.py:44 msgid "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" msgstr "苹果音乐" -#: catalog/common/models.py:48 catalog/common/models.py:94 +#: catalog/common/models.py:46 catalog/common/models.py:92 msgid "Fediverse" msgstr "联邦宇宙" -#: catalog/common/models.py:52 +#: catalog/common/models.py:50 msgid "WikiData" msgstr "维基数据" -#: catalog/common/models.py:53 +#: catalog/common/models.py:51 msgid "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" msgstr "ISBN" -#: catalog/common/models.py:55 +#: catalog/common/models.py:53 msgid "ASIN" msgstr "ASIN" -#: catalog/common/models.py:56 +#: catalog/common/models.py:54 msgid "ISSN" msgstr "ISSN" -#: catalog/common/models.py:57 +#: catalog/common/models.py:55 msgid "CUBN" msgstr "统一书号" -#: catalog/common/models.py:58 +#: catalog/common/models.py:56 msgid "ISRC" msgstr "ISRC" -#: catalog/common/models.py:59 +#: catalog/common/models.py:57 msgid "GTIN UPC EAN" msgstr "条形码" -#: catalog/common/models.py:60 +#: catalog/common/models.py:58 msgid "RSS Feed URL" msgstr "RSS网址" -#: catalog/common/models.py:62 +#: catalog/common/models.py:60 msgid "TMDB TV Serie" msgstr "TMDB电视剧集" -#: catalog/common/models.py:63 +#: catalog/common/models.py:61 msgid "TMDB TV Season" msgstr "TMDB电视分季" -#: catalog/common/models.py:64 +#: catalog/common/models.py:62 msgid "TMDB TV Episode" msgstr "TMDB电视单集" -#: catalog/common/models.py:65 +#: catalog/common/models.py:63 msgid "TMDB Movie" msgstr "TMDB电影" -#: catalog/common/models.py:67 +#: catalog/common/models.py:65 msgid "Goodreads Work" msgstr "Goodreads著作" -#: catalog/common/models.py:69 +#: catalog/common/models.py:67 msgid "Douban Book" msgstr "豆瓣图书" -#: catalog/common/models.py:70 +#: catalog/common/models.py:68 msgid "Douban Book Work" msgstr "豆瓣图书著作" -#: catalog/common/models.py:71 +#: catalog/common/models.py:69 msgid "Douban Movie" msgstr "豆瓣电影" -#: catalog/common/models.py:72 +#: catalog/common/models.py:70 msgid "Douban Music" msgstr "豆瓣音乐" -#: catalog/common/models.py:73 +#: catalog/common/models.py:71 msgid "Douban Game" msgstr "豆瓣游戏" -#: catalog/common/models.py:74 +#: catalog/common/models.py:72 msgid "Douban Drama" msgstr "豆瓣舞台剧" -#: catalog/common/models.py:75 +#: catalog/common/models.py:73 msgid "Douban Drama Version" msgstr "豆瓣舞台剧版本" -#: catalog/common/models.py:76 +#: catalog/common/models.py:74 msgid "BooksTW Book" msgstr "博客来图书" -#: catalog/common/models.py:78 +#: catalog/common/models.py:76 msgid "Spotify Album" msgstr "Spotify专辑" -#: catalog/common/models.py:79 +#: catalog/common/models.py:77 msgid "Spotify Podcast" msgstr "Spotify播客" -#: catalog/common/models.py:88 +#: catalog/common/models.py:86 msgid "IGDB Game" msgstr "IGDB游戏" -#: catalog/common/models.py:89 +#: catalog/common/models.py:87 msgid "BGG Boardgame" msgstr "BGG桌游" -#: catalog/common/models.py:90 +#: catalog/common/models.py:88 msgid "Steam Game" msgstr "Steam游戏" -#: catalog/common/models.py:92 +#: catalog/common/models.py:90 msgid "Apple Podcast" msgstr "苹果播客" -#: catalog/common/models.py:110 catalog/common/models.py:127 -#: catalog/common/models.py:140 catalog/jobs/discover.py:91 +#: catalog/common/models.py:108 catalog/common/models.py:125 +#: catalog/common/models.py:138 catalog/jobs/discover.py:91 #: common/templates/_header.html:25 msgid "Book" msgstr "书" -#: catalog/common/models.py:111 +#: catalog/common/models.py:109 msgid "TV Serie" 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" msgstr "电视分季" -#: catalog/common/models.py:113 +#: catalog/common/models.py:111 msgid "TV Episode" msgstr "电视单集" -#: catalog/common/models.py:114 catalog/common/models.py:128 -#: catalog/common/models.py:141 catalog/templates/_sidebar_edit.html:133 +#: catalog/common/models.py:112 catalog/common/models.py:126 +#: catalog/common/models.py:139 catalog/templates/_sidebar_edit.html:133 msgid "Movie" msgstr "电影" -#: catalog/common/models.py:115 +#: catalog/common/models.py:113 msgid "Album" msgstr "专辑" -#: catalog/common/models.py:116 catalog/common/models.py:131 -#: catalog/common/models.py:144 common/templates/_header.html:41 +#: catalog/common/models.py:114 catalog/common/models.py:129 +#: catalog/common/models.py:142 common/templates/_header.html:41 msgid "Game" msgstr "游戏" -#: catalog/common/models.py:117 +#: catalog/common/models.py:115 msgid "Podcast Program" msgstr "播客节目" -#: catalog/common/models.py:118 +#: catalog/common/models.py:116 msgid "Podcast Episode" msgstr "播客单集" -#: catalog/common/models.py:119 catalog/common/models.py:133 -#: catalog/common/models.py:146 common/templates/_header.html:45 +#: catalog/common/models.py:117 catalog/common/models.py:131 +#: catalog/common/models.py:144 common/templates/_header.html:45 msgid "Performance" msgstr "演出" -#: catalog/common/models.py:120 +#: catalog/common/models.py:118 msgid "Production" msgstr "上演" -#: catalog/common/models.py:121 +#: catalog/common/models.py:119 msgid "Fanfic" msgstr "网文" -#: catalog/common/models.py:122 catalog/common/models.py:135 +#: catalog/common/models.py:120 catalog/common/models.py:133 msgid "Exhibition" 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_edit.html:9 #: journal/templates/collection_share.html:12 msgid "Collection" msgstr "收藏单" -#: catalog/common/models.py:129 catalog/common/models.py:142 +#: catalog/common/models.py:127 catalog/common/models.py:140 msgid "TV" 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 msgid "Music" 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 msgid "Podcast" msgstr "播客" -#: catalog/common/models.py:134 +#: catalog/common/models.py:132 msgid "FanFic" msgstr "网文" -#: catalog/common/models.py:257 journal/models/collection.py:48 +#: catalog/common/models.py:255 journal/models/collection.py:48 msgid "title" msgstr "标题" -#: catalog/common/models.py:258 journal/models/collection.py:49 +#: catalog/common/models.py:256 journal/models/collection.py:49 msgid "description" msgstr "描述" -#: catalog/common/models.py:260 catalog/forms.py:26 +#: catalog/common/models.py:258 catalog/forms.py:26 msgid "Primary ID Type" msgstr "主要标识类型" -#: catalog/common/models.py:263 catalog/forms.py:31 +#: catalog/common/models.py:261 catalog/forms.py:31 msgid "Primary ID Value" msgstr "主要标识数据" -#: catalog/common/models.py:269 +#: catalog/common/models.py:267 msgid "metadata" msgstr "元数据" -#: catalog/common/models.py:271 +#: catalog/common/models.py:269 msgid "cover" msgstr "封面" -#: catalog/common/models.py:565 +#: catalog/common/models.py:563 msgid "source site" msgstr "来源站点" -#: catalog/common/models.py:567 +#: catalog/common/models.py:565 msgid "ID on source site" msgstr "来源站点标识" -#: catalog/common/models.py:569 +#: catalog/common/models.py:567 msgid "source url" msgstr "来源站点网址" -#: catalog/common/models.py:581 +#: catalog/common/models.py:579 msgid "IdType of the source site" msgstr "来源站点的主要标识类型" -#: catalog/common/models.py:587 +#: catalog/common/models.py:585 msgid "Primary Id on the source site" msgstr "来源站点的主要标识数据" -#: catalog/common/models.py:590 +#: catalog/common/models.py:588 msgid "url to the resource" msgstr "指向外部资源的网址" @@ -1361,17 +1361,17 @@ msgstr "{show_title} 第{season_number}季" msgid "{season_title} E{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" msgstr "条目不存在" -#: catalog/views.py:57 catalog/views.py:84 +#: catalog/views.py:53 catalog/views.py:80 msgid "Item no longer exists" msgstr "条目已不存在" -#: catalog/views_edit.py:125 catalog/views_edit.py:148 -#: catalog/views_edit.py:200 catalog/views_edit.py:276 -#: catalog/views_edit.py:353 journal/views/collection.py:52 +#: catalog/views_edit.py:122 catalog/views_edit.py:145 +#: catalog/views_edit.py:197 catalog/views_edit.py:273 +#: catalog/views_edit.py:350 journal/views/collection.py:52 #: journal/views/collection.py:102 journal/views/collection.py:114 #: journal/views/collection.py:130 journal/views/collection.py:161 #: journal/views/collection.py:180 journal/views/collection.py:200 @@ -1383,34 +1383,34 @@ msgstr "条目已不存在" msgid "Insufficient permission" 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/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 msgid "Invalid parameter" msgstr "无效参数" -#: catalog/views_edit.py:252 +#: catalog/views_edit.py:249 msgid "Must be a TV Season with IMDB id and season id" msgstr "" -#: catalog/views_edit.py:257 +#: catalog/views_edit.py:254 msgid "Updating episodes" msgstr "" -#: catalog/views_edit.py:287 +#: catalog/views_edit.py:284 msgid "Cannot be merged to an item already deleted or merged" msgstr "" -#: catalog/views_edit.py:290 +#: catalog/views_edit.py:287 msgid "Cannot merge items in different categories" msgstr "" -#: catalog/views_edit.py:327 +#: catalog/views_edit.py:324 msgid "Cannot be linked to an item already deleted or merged" msgstr "" -#: catalog/views_edit.py:329 +#: catalog/views_edit.py:326 msgid "Cannot link items other than editions" msgstr "" @@ -2238,7 +2238,7 @@ msgstr "日历" msgid "annual summary" msgstr "年度小结" -#: journal/templates/profile.html:131 mastodon/api.py:676 +#: journal/templates/profile.html:131 mastodon/api.py:677 msgid "collection" msgstr "收藏单" @@ -2386,21 +2386,21 @@ msgstr "找不到条目,请使用本站条目网址。" msgid "Login required" msgstr "登录后访问" -#: journal/views/common.py:33 journal/views/mark.py:117 -msgid "Data saved but unable to repost to Fediverse." -msgstr "数据已保存但未能转发到联邦宇宙。" +#: journal/views/common.py:33 journal/views/mark.py:116 +msgid "Data saved but unable to repost to Fediverse instance." +msgstr "数据已保存但未能转发到联邦实例。" #: journal/views/common.py:35 -msgid "Redirecting to your Mastodon instance now to re-authenticate." -msgstr "正在重定向到你的Mastodon实例以重新认证。" +msgid "Redirecting to your Fediverse instance now to re-authenticate." +msgstr "正在重定向到你的联邦实例以重新认证。" #: journal/views/common.py:42 msgid "List not found." msgstr "列表未找到" -#: journal/views/mark.py:109 -msgid "Content too long for your Mastodon instance." -msgstr "内容过长,超出了你的Mastodon实例的限制。" +#: journal/views/mark.py:107 +msgid "Content too long for your Fediverse instance." +msgstr "内容过长,超出了你的联邦实例的限制。" #: journal/views/mark.py:161 journal/views/review.py:30 msgid "Content not found" @@ -2444,16 +2444,16 @@ msgstr "标签已更新" msgid "Summary posted to timeline." msgstr "总结已发布到时间轴" -#: mastodon/api.py:519 takahe/utils.py:514 +#: mastodon/api.py:520 takahe/utils.py:514 #, python-brace-format msgid "regarding {item_title}, may contain spoiler or triggering content" msgstr "关于 {item_title},可能包含剧透或敏感内容" -#: mastodon/api.py:681 +#: mastodon/api.py:682 msgid "shared my collection" msgstr "分享我的收藏单" -#: mastodon/api.py:684 +#: mastodon/api.py:685 #, python-brace-format msgid "shared {username}'s collection" msgstr "分享 {username} 的收藏单" @@ -2801,8 +2801,8 @@ msgid "Authentication failed" msgstr "认证失败" #: users/account.py:159 -msgid "Invalid response from Mastodon instance." -msgstr "Mastodon实例返回信息无效" +msgid "Invalid response from Fediverse instance." +msgstr "联邦实例返回信息无效" #: users/account.py:169 msgid "Invalid cookie data." @@ -2813,12 +2813,12 @@ msgid "Invalid instance domain" msgstr "实例域名无效" #: users/account.py:182 -msgid "Invalid token from Mastodon instance." -msgstr "Mastodon实例返回了无效的认证令牌。" +msgid "Invalid token from Fediverse instance." +msgstr "联邦实例返回了无效的认证令牌。" #: users/account.py:205 users/account.py:559 -msgid "Invalid account data from Mastodon instance." -msgstr "Mastodon实例返回了无效的账号数据。" +msgid "Invalid account data from Fediverse instance." +msgstr "联邦实例返回了无效的账号数据。" #: users/account.py:230 msgid "Registration is for invitation only" @@ -2953,39 +2953,39 @@ msgstr "登录信息已更新" msgid "Account mismatch." msgstr "账号信息不匹配。" -#: users/data.py:123 +#: users/data.py:124 msgid "Generating exports." msgstr "正在生成导出文件。" -#: users/data.py:135 +#: users/data.py:136 msgid "Export file expired. Please export again." msgstr "导出文件已失效,请重新导出" -#: users/data.py:146 +#: users/data.py:147 msgid "Sync in progress." msgstr "正在同步。" -#: users/data.py:160 +#: users/data.py:161 msgid "Settings saved." msgstr "设置已保存" -#: users/data.py:171 +#: users/data.py:172 msgid "Reset completed." msgstr "重置已完成。" -#: users/data.py:180 +#: users/data.py:181 msgid "Import in progress." msgstr "正在导出" -#: users/data.py:182 +#: users/data.py:183 msgid "Invalid URL." 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." msgstr "文件已上传,等待后台导入。" -#: users/data.py:199 users/data.py:239 +#: users/data.py:200 users/data.py:240 msgid "Invalid file." msgstr "无效文件。" diff --git a/pyproject.toml b/pyproject.toml index 6209feaa..3fce726e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ reportIncompatibleVariableOverride = false reportUnusedImport = false reportUnknownVariableType = false reportConstantRedefinition = false +reportUnusedCallResult = false [tool.djlint] ignore="T002,T003,H005,H006,H019,H020,H021,H023,H030,H031,D018" diff --git a/social/models.py b/social/models.py index 617bb599..940ca79c 100644 --- a/social/models.py +++ b/social/models.py @@ -6,7 +6,6 @@ ActivityManager generates chronological view for user and, in future, ActivitySt """ -import logging from functools import cached_property from typing import Type @@ -15,6 +14,7 @@ from django.db import models from django.db.models import Q from django.db.models.signals import post_delete, post_save, pre_delete from django.utils import timezone +from loguru import logger from catalog.common.models import Item from journal.models import ( @@ -29,8 +29,6 @@ from journal.models import ( ) from users.models import APIdentity -_logger = logging.getLogger(__name__) - class ActivityTemplate(models.TextChoices): MarkItem = "mark_item" @@ -105,7 +103,7 @@ class DataSignalManager: @staticmethod def add_handler_for_model(model): if settings.DISABLE_MODEL_SIGNAL: - _logger.warn( + logger.warning( f"{model.__name__} are not being indexed with DISABLE_MODEL_SIGNAL configuration" ) return diff --git a/social/views.py b/social/views.py index 99e6084f..172d3c95 100644 --- a/social/views.py +++ b/social/views.py @@ -1,5 +1,3 @@ -import logging - from django.contrib.auth.decorators import login_required from django.shortcuts import redirect, render from django.urls import reverse diff --git a/users/account.py b/users/account.py index ec1350a1..790982c1 100644 --- a/users/account.py +++ b/users/account.py @@ -156,7 +156,7 @@ def connect_redirect_back(request): "common/error.html", { "msg": _("Authentication failed"), - "secondary_msg": _("Invalid response from Mastodon instance."), + "secondary_msg": _("Invalid response from Fediverse instance."), }, ) site = request.session.get("mastodon_domain") @@ -179,7 +179,7 @@ def connect_redirect_back(request): "common/error.html", { "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", { "msg": _("Authentication failed"), - "secondary_msg": _("Invalid account data from Mastodon instance."), + "secondary_msg": _("Invalid account data from Fediverse instance."), }, ) return register_new_user( @@ -348,7 +348,7 @@ def send_verification_link(user_id, action, email, code=""): fail_silently=False, ) except Exception as e: - logger.error(e) + logger.error(f"send email {email} failed", extra={"exception": e}) @require_http_methods(["POST"]) @@ -418,7 +418,7 @@ def verify_email(request): else: return register_new_user(request, username=None, email=email) except Exception as e: - logger.error(e) + logger.error("verify email error", extra={"exception": e, "s": s}) error = _("Unable to verify") return render( request, "users/verify_email.html", {"success": False, "error": error} @@ -556,7 +556,7 @@ def swap_login(request, token, site, refresh_token): ) else: 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")) diff --git a/users/models/task.py b/users/models/task.py index 5e4c46a3..424fa64b 100644 --- a/users/models/task.py +++ b/users/models/task.py @@ -66,7 +66,9 @@ class Task(models.Model): task.state = cls.States.complete task.save(update_fields=["state"]) 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.state = cls.States.failed task.save(update_fields=["state", "message"]) diff --git a/users/models/user.py b/users/models/user.py index 5914e297..5a2e06f6 100644 --- a/users/models/user.py +++ b/users/models/user.py @@ -310,7 +310,10 @@ class User(AbstractUser): f = ContentFile(r.content, name=identity.icon_uri.split("/")[-1]) identity.icon.save(f.name, f, save=False) 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() def refresh_mastodon_data(self, skip_detail=False):