diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000..1cff6e92e9 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,26 @@ +name: Lint +on: [push, pull_request] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install Python Packages + run: | + pip install --upgrade pip + pip install tox + + - name: Run isort + run: tox -e isort + + - name: Run Black + run: tox -e black diff --git a/api/admin/admin_authentication_provider.py b/api/admin/admin_authentication_provider.py index 57f79e5824..1ac953f377 100644 --- a/api/admin/admin_authentication_provider.py +++ b/api/admin/admin_authentication_provider.py @@ -1,4 +1,3 @@ - class AdminAuthenticationProvider(object): def __init__(self, integration): self.integration = integration diff --git a/api/admin/announcement_list_validator.py b/api/admin/announcement_list_validator.py index 1752086258..006eaba368 100644 --- a/api/admin/announcement_list_validator.py +++ b/api/admin/announcement_list_validator.py @@ -6,17 +6,21 @@ from flask_babel import lazy_gettext as _ from api.admin.validator import Validator - -from core.util.problem_detail import ProblemDetail from core.problem_details import * +from core.util.problem_detail import ProblemDetail class AnnouncementListValidator(Validator): - DATE_FORMAT = '%Y-%m-%d' + DATE_FORMAT = "%Y-%m-%d" - def __init__(self, maximum_announcements=3, minimum_announcement_length=15, - maximum_announcement_length=350, default_duration_days=60): + def __init__( + self, + maximum_announcements=3, + minimum_announcement_length=15, + maximum_announcement_length=350, + default_duration_days=60, + ): super(AnnouncementListValidator, self).__init__() self.maximum_announcements = maximum_announcements self.minimum_announcement_length = minimum_announcement_length @@ -26,8 +30,10 @@ def __init__(self, maximum_announcements=3, minimum_announcement_length=15, def validate_announcements(self, announcements): validated_announcements = [] bad_format = INVALID_INPUT.detailed( - _("Invalid announcement list format: %(announcements)r", - announcements=announcements) + _( + "Invalid announcement list format: %(announcements)r", + announcements=announcements, + ) ) if isinstance(announcements, (bytes, str)): try: @@ -38,8 +44,10 @@ def validate_announcements(self, announcements): return bad_format if len(announcements) > self.maximum_announcements: return INVALID_INPUT.detailed( - _("Too many announcements: maximum is %(maximum)d", - maximum=self.maximum_announcements) + _( + "Too many announcements: maximum is %(maximum)d", + maximum=self.maximum_announcements, + ) ) seen_ids = set() @@ -47,7 +55,7 @@ def validate_announcements(self, announcements): validated = self.validate_announcement(announcement) if isinstance(validated, ProblemDetail): return validated - id = validated['id'] + id = validated["id"] if id in seen_ids: return INVALID_INPUT.detailed(_("Duplicate announcement ID: %s" % id)) seen_ids.add(id) @@ -58,46 +66,47 @@ def validate_announcement(self, announcement): validated = dict() if not isinstance(announcement, dict): return INVALID_INPUT.detailed( - _("Invalid announcement format: %(announcement)r", announcement=announcement) + _( + "Invalid announcement format: %(announcement)r", + announcement=announcement, + ) ) - validated['id'] = announcement.get('id', str(uuid.uuid4())) + validated["id"] = announcement.get("id", str(uuid.uuid4())) - for required_field in ('content',): + for required_field in ("content",): if not required_field in announcement: return INVALID_INPUT.detailed( _("Missing required field: %(field)s", field=required_field) ) # Validate the content of the announcement. - content = announcement['content'] + content = announcement["content"] content = self.validate_length( content, self.minimum_announcement_length, self.maximum_announcement_length ) if isinstance(content, ProblemDetail): return content - validated['content'] = content + validated["content"] = content # Validate the dates associated with the announcement today_local = datetime.date.today() - start = self.validate_date( - 'start', announcement.get('start', today_local) - ) + start = self.validate_date("start", announcement.get("start", today_local)) if isinstance(start, ProblemDetail): return start - validated['start'] = start + validated["start"] = start default_finish = start + datetime.timedelta(days=self.default_duration_days) day_after_start = start + datetime.timedelta(days=1) finish = self.validate_date( - 'finish', - announcement.get('finish', default_finish), + "finish", + announcement.get("finish", default_finish), minimum=day_after_start, ) if isinstance(finish, ProblemDetail): return finish - validated['finish'] = finish + validated["finish"] = finish # That's it! return validated @@ -114,14 +123,22 @@ def validate_length(self, value, minimum, maximum): """ if len(value) < minimum: return INVALID_INPUT.detailed( - _('Value too short (%(length)d versus %(limit)d characters): %(value)s', - length=len(value), limit=minimum, value=value) + _( + "Value too short (%(length)d versus %(limit)d characters): %(value)s", + length=len(value), + limit=minimum, + value=value, + ) ) if len(value) > maximum: return INVALID_INPUT.detailed( - _('Value too long (%(length)d versus %(limit)d characters): %(value)s', - length=len(value), limit=maximum, value=value) + _( + "Value too long (%(length)d versus %(limit)d characters): %(value)s", + length=len(value), + limit=maximum, + value=value, + ) ) return value @@ -145,7 +162,11 @@ def validate_date(cls, field, value, minimum=None): value = value.replace(tzinfo=dateutil.tz.tzlocal()) except ValueError as e: return INVALID_INPUT.detailed( - _("Value for %(field)s is not a date: %(date)s", field=field, date=value) + _( + "Value for %(field)s is not a date: %(date)s", + field=field, + date=value, + ) ) if isinstance(value, datetime.datetime): value = value.date() @@ -153,8 +174,10 @@ def validate_date(cls, field, value, minimum=None): minimum = minimum.date() if minimum and value < minimum: return INVALID_INPUT.detailed( - _("Value for %(field)s must be no earlier than %(minimum)s", - field=field, minimum=minimum.strftime(cls.DATE_FORMAT) + _( + "Value for %(field)s must be no earlier than %(minimum)s", + field=field, + minimum=minimum.strftime(cls.DATE_FORMAT), ) ) return value @@ -162,4 +185,5 @@ def validate_date(cls, field, value, minimum=None): def format_as_string(self, value): """Format the output of validate_announcements for storage in ConfigurationSetting.value""" from ..announcements import Announcements + return json.dumps([x.json_ready for x in Announcements(value).announcements]) diff --git a/api/admin/config.py b/api/admin/config.py index fa4a5360c4..08969fd9f9 100644 --- a/api/admin/config.py +++ b/api/admin/config.py @@ -1,52 +1,54 @@ -from enum import Enum import os +from enum import Enum from urllib.parse import urljoin class OperationalMode(str, Enum): - production = 'production' - development = 'development' + production = "production" + development = "development" class Configuration: - APP_NAME = 'Palace Collection Manager' - PACKAGE_NAME = '@thepalaceproject/circulation-admin' - PACKAGE_VERSION = '0.0.6' + APP_NAME = "Palace Collection Manager" + PACKAGE_NAME = "@thepalaceproject/circulation-admin" + PACKAGE_VERSION = "0.0.6" STATIC_ASSETS = { - 'admin_js': 'circulation-admin.js', - 'admin_css': 'circulation-admin.css', - 'admin_logo': 'PalaceCollectionManagerLogo.svg', + "admin_js": "circulation-admin.js", + "admin_css": "circulation-admin.css", + "admin_logo": "PalaceCollectionManagerLogo.svg", } # For proper operation, `package_url` MUST end with a slash ('/') and # `asset_rel_url` MUST NOT begin with one. PACKAGE_TEMPLATES = { OperationalMode.production: { - 'package_url': 'https://cdn.jsdelivr.net/npm/{name}@{version}/', - 'asset_rel_url': 'dist/{filename}' + "package_url": "https://cdn.jsdelivr.net/npm/{name}@{version}/", + "asset_rel_url": "dist/{filename}", }, OperationalMode.development: { - 'package_url': '/admin/', - 'asset_rel_url': 'static/{filename}', + "package_url": "/admin/", + "asset_rel_url": "static/{filename}", }, } - DEVELOPMENT_MODE_PACKAGE_TEMPLATE = 'node_modules/{name}' - STATIC_ASSETS_REL_PATH = 'dist' + DEVELOPMENT_MODE_PACKAGE_TEMPLATE = "node_modules/{name}" + STATIC_ASSETS_REL_PATH = "dist" ADMIN_DIRECTORY = os.path.abspath(os.path.dirname(__file__)) # Environment variables that contain admin client package information. - ENV_ADMIN_UI_PACKAGE_NAME = 'TPP_CIRCULATION_ADMIN_PACKAGE_NAME' - ENV_ADMIN_UI_PACKAGE_VERSION = 'TPP_CIRCULATION_ADMIN_PACKAGE_VERSION' + ENV_ADMIN_UI_PACKAGE_NAME = "TPP_CIRCULATION_ADMIN_PACKAGE_NAME" + ENV_ADMIN_UI_PACKAGE_VERSION = "TPP_CIRCULATION_ADMIN_PACKAGE_VERSION" @classmethod def operational_mode(cls) -> OperationalMode: - return (OperationalMode.development - if os.path.isdir(cls.package_development_directory()) - else OperationalMode.production) + return ( + OperationalMode.development + if os.path.isdir(cls.package_development_directory()) + else OperationalMode.production + ) @classmethod def _package_name(cls) -> str: @@ -58,7 +60,9 @@ def _package_name(cls) -> str: return os.environ.get(cls.ENV_ADMIN_UI_PACKAGE_NAME) or cls.PACKAGE_NAME @classmethod - def lookup_asset_url(cls, key: str, *, _operational_mode: OperationalMode = None) -> str: + def lookup_asset_url( + cls, key: str, *, _operational_mode: OperationalMode = None + ) -> str: """Get the URL for the asset_type. :param key: The key used to lookup an asset's filename. If the key is @@ -72,8 +76,12 @@ def lookup_asset_url(cls, key: str, *, _operational_mode: OperationalMode = None """ operational_mode = _operational_mode or cls.operational_mode() filename = cls.STATIC_ASSETS.get(key, key) - return urljoin(cls.package_url(_operational_mode=operational_mode), - cls.PACKAGE_TEMPLATES[operational_mode]['asset_rel_url'].format(filename=filename)) + return urljoin( + cls.package_url(_operational_mode=operational_mode), + cls.PACKAGE_TEMPLATES[operational_mode]["asset_rel_url"].format( + filename=filename + ), + ) @classmethod def package_url(cls, *, _operational_mode: OperationalMode = None) -> str: @@ -88,11 +96,13 @@ def package_url(cls, *, _operational_mode: OperationalMode = None) -> str: :rtype: str """ operational_mode = _operational_mode or cls.operational_mode() - version = (os.environ.get(cls.ENV_ADMIN_UI_PACKAGE_VERSION) or cls.PACKAGE_VERSION) - template = cls.PACKAGE_TEMPLATES[operational_mode]['package_url'] + version = ( + os.environ.get(cls.ENV_ADMIN_UI_PACKAGE_VERSION) or cls.PACKAGE_VERSION + ) + template = cls.PACKAGE_TEMPLATES[operational_mode]["package_url"] url = template.format(name=cls._package_name(), version=version) - if not url.endswith('/'): - url += '/' + if not url.endswith("/"): + url += "/" return url @classmethod @@ -105,8 +115,10 @@ def package_development_directory(cls, *, _base_dir: str = None) -> str: :rtype: str """ base_dir = _base_dir or cls.ADMIN_DIRECTORY - return os.path.join(base_dir, - cls.DEVELOPMENT_MODE_PACKAGE_TEMPLATE.format(name=cls._package_name())) + return os.path.join( + base_dir, + cls.DEVELOPMENT_MODE_PACKAGE_TEMPLATE.format(name=cls._package_name()), + ) @classmethod def static_files_directory(cls, *, _base_dir: str = None) -> str: diff --git a/api/admin/controller/__init__.py b/api/admin/controller/__init__.py index 61482e558b..5a864b4d76 100644 --- a/api/admin/controller/__init__.py +++ b/api/admin/controller/__init__.py @@ -5,35 +5,32 @@ import os import sys import urllib.parse -from datetime import ( - date, - datetime, - timedelta, -) +from datetime import date, datetime, timedelta import flask import jwt -from flask import (redirect, Response) +from flask import Response, redirect from flask_babel import lazy_gettext as _ from sqlalchemy.sql import func -from sqlalchemy.sql.expression import (and_, desc, distinct, join, nullslast, select) +from sqlalchemy.sql.expression import and_, desc, distinct, join, nullslast, select from api.admin.config import Configuration as AdminClientConfig from api.admin.exceptions import * -from api.admin.google_oauth_admin_authentication_provider import GoogleOAuthAdminAuthenticationProvider -from api.admin.opds import ( - AdminAnnotator, - AdminFeed, +from api.admin.google_oauth_admin_authentication_provider import ( + GoogleOAuthAdminAuthenticationProvider, +) +from api.admin.opds import AdminAnnotator, AdminFeed +from api.admin.password_admin_authentication_provider import ( + PasswordAdminAuthenticationProvider, ) -from api.admin.password_admin_authentication_provider import PasswordAdminAuthenticationProvider from api.admin.template_styles import * from api.admin.templates import admin as admin_template from api.admin.validator import Validator from api.adobe_vendor_id import AuthdataUtility -from api.authenticator import (CannotCreateLocalPatron, LibraryAuthenticator, PatronData) +from api.authenticator import CannotCreateLocalPatron, LibraryAuthenticator, PatronData from api.axis import Axis360API from api.bibliotheca import BibliothecaAPI -from api.config import (CannotLoadConfiguration, Configuration) +from api.config import CannotLoadConfiguration, Configuration from api.controller import CirculationManagerController from api.enki import EnkiAPI from api.feedbooks import FeedbooksOPDSImporter @@ -41,25 +38,15 @@ from api.lcp.collection import LCPAPI from api.local_analytics_exporter import LocalAnalyticsExporter from api.odilo import OdiloAPI -from api.odl import ( - ODLAPI, - SharedODLAPI, -) +from api.odl import ODLAPI, SharedODLAPI from api.odl2 import ODL2API from api.opds_for_distributors import OPDSForDistributorsAPI from api.overdrive import OverdriveAPI from api.proquest.importer import ProQuestOPDS2Importer -from core.app_server import ( - load_pagination_from_request, -) -from core.classifier import ( - genres -) +from core.app_server import load_pagination_from_request +from core.classifier import genres from core.external_search import ExternalSearchIndex -from core.lane import ( - Lane, - WorkList, -) +from core.lane import Lane, WorkList from core.local_analytics_provider import LocalAnalyticsProvider from core.model import ( Admin, @@ -67,13 +54,10 @@ CirculationEvent, Collection, ConfigurationSetting, - create, CustomList, CustomListEntry, DataSource, ExternalIntegration, - get_one, - get_one_or_create, Hold, Identifier, Library, @@ -82,11 +66,14 @@ Patron, Timestamp, Work, + create, + get_one, + get_one_or_create, ) from core.model.configuration import ExternalIntegrationLink from core.opds import AcquisitionFeed from core.opds2_import import OPDS2Importer -from core.opds_import import (OPDSImporter, OPDSImportMonitor) +from core.opds_import import OPDSImporter, OPDSImportMonitor from core.s3 import S3UploaderConfiguration from core.selftest import HasSelfTests from core.util.datetime_helpers import utc_now @@ -108,6 +95,7 @@ def setup_admin_controllers(manager): manager.admin_sign_in_controller = SignInController(manager) manager.timestamps_controller = TimestampsController(manager) from api.admin.controller.work_editor import WorkController + manager.admin_work_controller = WorkController(manager) manager.admin_feed_controller = FeedController(manager) manager.admin_custom_lists_controller = CustomListsController(manager) @@ -116,50 +104,105 @@ def setup_admin_controllers(manager): manager.admin_settings_controller = SettingsController(manager) manager.admin_patron_controller = PatronController(manager) from api.admin.controller.self_tests import SelfTestsController + manager.admin_self_tests_controller = SelfTestsController(manager) from api.admin.controller.discovery_services import DiscoveryServicesController + manager.admin_discovery_services_controller = DiscoveryServicesController(manager) - from api.admin.controller.discovery_service_library_registrations import DiscoveryServiceLibraryRegistrationsController - manager.admin_discovery_service_library_registrations_controller = DiscoveryServiceLibraryRegistrationsController(manager) + from api.admin.controller.discovery_service_library_registrations import ( + DiscoveryServiceLibraryRegistrationsController, + ) + + manager.admin_discovery_service_library_registrations_controller = ( + DiscoveryServiceLibraryRegistrationsController(manager) + ) from api.admin.controller.cdn_services import CDNServicesController + manager.admin_cdn_services_controller = CDNServicesController(manager) from api.admin.controller.analytics_services import AnalyticsServicesController + manager.admin_analytics_services_controller = AnalyticsServicesController(manager) from api.admin.controller.metadata_services import MetadataServicesController + manager.admin_metadata_services_controller = MetadataServicesController(manager) + from api.admin.controller.metadata_service_self_tests import ( + MetadataServiceSelfTestsController, + ) from api.admin.controller.patron_auth_services import PatronAuthServicesController - from api.admin.controller.metadata_service_self_tests import MetadataServiceSelfTestsController - manager.admin_metadata_service_self_tests_controller = MetadataServiceSelfTestsController(manager) - manager.admin_patron_auth_services_controller = PatronAuthServicesController(manager) - from api.admin.controller.patron_auth_service_self_tests import PatronAuthServiceSelfTestsController - manager.admin_patron_auth_service_self_tests_controller = PatronAuthServiceSelfTestsController(manager) + + manager.admin_metadata_service_self_tests_controller = ( + MetadataServiceSelfTestsController(manager) + ) + manager.admin_patron_auth_services_controller = PatronAuthServicesController( + manager + ) + from api.admin.controller.patron_auth_service_self_tests import ( + PatronAuthServiceSelfTestsController, + ) + + manager.admin_patron_auth_service_self_tests_controller = ( + PatronAuthServiceSelfTestsController(manager) + ) from api.admin.controller.admin_auth_services import AdminAuthServicesController + manager.admin_auth_services_controller = AdminAuthServicesController(manager) from api.admin.controller.collection_settings import CollectionSettingsController + manager.admin_collection_settings_controller = CollectionSettingsController(manager) from api.admin.controller.collection_self_tests import CollectionSelfTestsController - manager.admin_collection_self_tests_controller = CollectionSelfTestsController(manager) - from api.admin.controller.collection_library_registrations import CollectionLibraryRegistrationsController - manager.admin_collection_library_registrations_controller = CollectionLibraryRegistrationsController(manager) - from api.admin.controller.sitewide_settings import SitewideConfigurationSettingsController - manager.admin_sitewide_configuration_settings_controller = SitewideConfigurationSettingsController(manager) + + manager.admin_collection_self_tests_controller = CollectionSelfTestsController( + manager + ) + from api.admin.controller.collection_library_registrations import ( + CollectionLibraryRegistrationsController, + ) + + manager.admin_collection_library_registrations_controller = ( + CollectionLibraryRegistrationsController(manager) + ) + from api.admin.controller.sitewide_settings import ( + SitewideConfigurationSettingsController, + ) + + manager.admin_sitewide_configuration_settings_controller = ( + SitewideConfigurationSettingsController(manager) + ) from api.admin.controller.library_settings import LibrarySettingsController + manager.admin_library_settings_controller = LibrarySettingsController(manager) - from api.admin.controller.individual_admin_settings import IndividualAdminSettingsController - manager.admin_individual_admin_settings_controller = IndividualAdminSettingsController(manager) - from api.admin.controller.sitewide_services import SitewideServicesController, LoggingServicesController, SearchServicesController + from api.admin.controller.individual_admin_settings import ( + IndividualAdminSettingsController, + ) + + manager.admin_individual_admin_settings_controller = ( + IndividualAdminSettingsController(manager) + ) + from api.admin.controller.sitewide_services import ( + LoggingServicesController, + SearchServicesController, + SitewideServicesController, + ) + manager.admin_sitewide_services_controller = SitewideServicesController(manager) manager.admin_logging_services_controller = LoggingServicesController(manager) - from api.admin.controller.search_service_self_tests import SearchServiceSelfTestsController - manager.admin_search_service_self_tests_controller = SearchServiceSelfTestsController(manager) + from api.admin.controller.search_service_self_tests import ( + SearchServiceSelfTestsController, + ) + + manager.admin_search_service_self_tests_controller = ( + SearchServiceSelfTestsController(manager) + ) manager.admin_search_services_controller = SearchServicesController(manager) from api.admin.controller.storage_services import StorageServicesController + manager.admin_storage_services_controller = StorageServicesController(manager) from api.admin.controller.catalog_services import CatalogServicesController + manager.admin_catalog_services_controller = CatalogServicesController(manager) -class AdminController(object): +class AdminController(object): def __init__(self, manager): self.manager = manager self._db = self.manager._db @@ -171,15 +214,19 @@ def admin_auth_providers(self): auth_providers = [] auth_service = ExternalIntegration.admin_authentication(self._db) if auth_service and auth_service.protocol == ExternalIntegration.GOOGLE_OAUTH: - auth_providers.append(GoogleOAuthAdminAuthenticationProvider( - auth_service, - self.url_for('google_auth_callback'), - test_mode=self.manager.testing, - )) + auth_providers.append( + GoogleOAuthAdminAuthenticationProvider( + auth_service, + self.url_for("google_auth_callback"), + test_mode=self.manager.testing, + ) + ) if Admin.with_password(self._db).count() != 0: - auth_providers.append(PasswordAdminAuthenticationProvider( - auth_service, - )) + auth_providers.append( + PasswordAdminAuthenticationProvider( + auth_service, + ) + ) return auth_providers def admin_auth_provider(self, type): @@ -212,23 +259,27 @@ def authenticated_admin_from_request(self): def authenticated_admin(self, admin_details): """Creates or updates an admin with the given details""" - admin, is_new = get_one_or_create( - self._db, Admin, email=admin_details['email'] - ) + admin, is_new = get_one_or_create(self._db, Admin, email=admin_details["email"]) admin.update_credentials( self._db, - credential=admin_details.get('credentials'), + credential=admin_details.get("credentials"), ) if is_new and admin_details.get("roles"): for role in admin_details.get("roles"): if role.get("role") in AdminRole.ROLES: library = Library.lookup(self._db, role.get("library")) if role.get("library") and not library: - self.log.warn("%s authentication provider specifiec an unknown library for a new admin: %s" % (admin_details.get("type"), role.get("library"))) + self.log.warn( + "%s authentication provider specifiec an unknown library for a new admin: %s" + % (admin_details.get("type"), role.get("library")) + ) else: admin.add_role(role.get("role"), library) else: - self.log.warn("%s authentication provider specified an unknown role for a new admin: %s" % (admin_details.get("type"), role.get("role"))) + self.log.warn( + "%s authentication provider specified an unknown role for a new admin: %s" + % (admin_details.get("type"), role.get("role")) + ) # Set up the admin's flask session. flask.session["admin_email"] = admin_details.get("email") @@ -244,11 +295,9 @@ def authenticated_admin(self, admin_details): # current request. This assumes the first authenticated admin # is accessing the admin interface through the hostname they # want to be used for the site itself. - base_url = ConfigurationSetting.sitewide( - self._db, Configuration.BASE_URL_KEY - ) + base_url = ConfigurationSetting.sitewide(self._db, Configuration.BASE_URL_KEY) if not base_url.value: - base_url.value = urllib.parse.urljoin(flask.request.url, '/') + base_url.value = urllib.parse.urljoin(flask.request.url, "/") return admin @@ -270,6 +319,7 @@ def generate_csrf_token(self): """Generate a random CSRF token.""" return base64.b64encode(os.urandom(24)).decode("utf-8") + class AdminCirculationManagerController(CirculationManagerController): """Parent class that provides methods for verifying an admin's roles.""" @@ -304,24 +354,24 @@ def require_higher_than_librarian(self): class ViewController(AdminController): def __call__(self, collection, book, path=None): - setting_up = (self.admin_auth_providers == []) + setting_up = self.admin_auth_providers == [] email = None roles = [] if not setting_up: admin = self.authenticated_admin_from_request() if isinstance(admin, ProblemDetail): redirect_url = flask.request.url - if (collection): + if collection: quoted_collection = urllib.parse.quote(collection) redirect_url = redirect_url.replace( - quoted_collection, - quoted_collection.replace("/", "%2F")) - if (book): + quoted_collection, quoted_collection.replace("/", "%2F") + ) + if book: quoted_book = urllib.parse.quote(book) redirect_url = redirect_url.replace( - quoted_book, - quoted_book.replace("/", "%2F")) - return redirect(self.url_for('admin_sign_in', redirect=redirect_url)) + quoted_book, quoted_book.replace("/", "%2F") + ) + return redirect(self.url_for("admin_sign_in", redirect=redirect_url)) if not collection and not book and not path: if self._db.query(Library).count() > 0: @@ -332,49 +382,62 @@ def __call__(self, collection, book, path=None): library_name = library.short_name break if not library_name: - return Response(_("Your admin account doesn't have access to any libraries. Contact your library manager for assistance."), 200) - return redirect(self.url_for('admin_view', collection=library_name)) + return Response( + _( + "Your admin account doesn't have access to any libraries. Contact your library manager for assistance." + ), + 200, + ) + return redirect(self.url_for("admin_view", collection=library_name)) email = admin.email for role in admin.roles: if role.library: - roles.append({ "role": role.role, "library": role.library }) + roles.append({"role": role.role, "library": role.library}) else: - roles.append({ "role": role.role }) + roles.append({"role": role.role}) - csrf_token = flask.request.cookies.get("csrf_token") or self.generate_csrf_token() - admin_js = AdminClientConfig.lookup_asset_url(key='admin_js') - admin_css = AdminClientConfig.lookup_asset_url(key='admin_css') + csrf_token = ( + flask.request.cookies.get("csrf_token") or self.generate_csrf_token() + ) + admin_js = AdminClientConfig.lookup_asset_url(key="admin_js") + admin_css = AdminClientConfig.lookup_asset_url(key="admin_css") # Find the URL and text to use when rendering the Terms of # Service link in the footer. - sitewide_tos_href = ConfigurationSetting.sitewide( - self._db, Configuration.CUSTOM_TOS_HREF - ).value or Configuration.DEFAULT_TOS_HREF + sitewide_tos_href = ( + ConfigurationSetting.sitewide(self._db, Configuration.CUSTOM_TOS_HREF).value + or Configuration.DEFAULT_TOS_HREF + ) - sitewide_tos_text = ConfigurationSetting.sitewide( - self._db, Configuration.CUSTOM_TOS_TEXT - ).value or Configuration.DEFAULT_TOS_TEXT + sitewide_tos_text = ( + ConfigurationSetting.sitewide(self._db, Configuration.CUSTOM_TOS_TEXT).value + or Configuration.DEFAULT_TOS_TEXT + ) local_analytics = get_one( - self._db, ExternalIntegration, + self._db, + ExternalIntegration, protocol=LocalAnalyticsProvider.__module__, - goal=ExternalIntegration.ANALYTICS_GOAL) - show_circ_events_download = (local_analytics != None) - - response = Response(flask.render_template_string( - admin_template, - app_name=AdminClientConfig.APP_NAME, - csrf_token=csrf_token, - sitewide_tos_href=sitewide_tos_href, - sitewide_tos_text=sitewide_tos_text, - show_circ_events_download=show_circ_events_download, - setting_up=setting_up, - email=email, - roles=roles, - admin_js=admin_js, - admin_css=admin_css - )) + goal=ExternalIntegration.ANALYTICS_GOAL, + ) + show_circ_events_download = local_analytics != None + + response = Response( + flask.render_template_string( + admin_template, + app_name=AdminClientConfig.APP_NAME, + csrf_token=csrf_token, + sitewide_tos_href=sitewide_tos_href, + sitewide_tos_text=sitewide_tos_text, + show_circ_events_download=show_circ_events_download, + setting_up=setting_up, + email=email, + roles=roles, + admin_js=admin_js, + admin_css=admin_css, + ) + ) # The CSRF token is in its own cookie instead of the session cookie, # because if your session expires and you log in again, you should @@ -383,6 +446,7 @@ def __call__(self, collection, book, path=None): response.set_cookie("csrf_token", csrf_token, httponly=True) return response + class TimestampsController(AdminCirculationManagerController): """Returns a dict: each key is a type of service (script, monitor, or coverage provider); each value is a nested dict in which timestamps are organized by service name and then by collection ID.""" @@ -447,9 +511,10 @@ def _extract_info(self, timestamp): exception=timestamp.exception, service=timestamp.service, collection_name=collection_name, - achievements=timestamp.achievements + achievements=timestamp.achievements, ) + class SignInController(AdminController): HEAD_TEMPLATE = """
@@ -458,7 +523,9 @@ class SignInController(AdminController): @import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap'); -""".format(app_name=AdminClientConfig.APP_NAME) +""".format( + app_name=AdminClientConfig.APP_NAME + ) ERROR_RESPONSE_TEMPLATE = """ @@ -468,7 +535,9 @@ class SignInController(AdminController):