diff --git a/requirements-rpc.txt b/requirements-rpc.txt index b164e373..cff5b5ca 100644 --- a/requirements-rpc.txt +++ b/requirements-rpc.txt @@ -1,3 +1,3 @@ # Packages needed for delayed jobs. -celery[redis]==5.3.1 +celery[redis]==5.3.5 django-celery-beat~=2.5.0 diff --git a/requirements-test.txt b/requirements-test.txt index 0aa615e5..9307b2f4 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -3,4 +3,4 @@ coverage~=7.3.2 fakeldap==0.6.1 tblib~=1.7.0 beautifulsoup4~=4.12.2 -httpx~=0.25.0 +httpx~=0.25.1 diff --git a/requirements.txt b/requirements.txt index e7b03635..c23ee7f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ djangorestframework~=3.14.0 drf-yasg==1.21.7 django-filter==23.3 drf_orjson_renderer==1.7.1 -ormsgpack~=1.4.0 +ormsgpack~=1.4.1 pyyaml~=6.0.1 # web server diff --git a/vstutils/__init__.py b/vstutils/__init__.py index 132d1ab5..dd9f247d 100644 --- a/vstutils/__init__.py +++ b/vstutils/__init__.py @@ -1,2 +1,2 @@ # pylint: disable=django-not-available -__version__: str = '5.8.12' +__version__: str = '5.8.13' diff --git a/vstutils/api/apps.py b/vstutils/api/apps.py index 6ef57bb6..d8886849 100644 --- a/vstutils/api/apps.py +++ b/vstutils/api/apps.py @@ -1,7 +1,6 @@ from django.conf import settings from django.apps import AppConfig - -from ..utils import import_class +from django.utils.module_loading import import_string class VSTUtilsAppConfig(AppConfig): @@ -11,7 +10,7 @@ class VSTUtilsAppConfig(AppConfig): def import_models(self): super().import_models() - self.module.notificator_class = import_class( + self.module.notificator_class = import_string( settings.NOTIFICATOR_CLIENT_CLASS ) diff --git a/vstutils/api/health.py b/vstutils/api/health.py index b13fa67a..fe35d553 100644 --- a/vstutils/api/health.py +++ b/vstutils/api/health.py @@ -1,10 +1,11 @@ -from typing import Dict, Tuple, ClassVar, Callable from functools import wraps +from typing import Dict, Tuple, ClassVar, Callable from django.db import connections, DatabaseError +from django.utils.module_loading import import_string from rest_framework import status as st, request as req -from ..utils import BaseVstObject, import_class +from ..utils import BaseVstObject def health_wrapper(method): @@ -81,7 +82,7 @@ def check_health_rpc(self): if not self.get_django_settings('RPC_ENABLED'): return 'disabled' try: - celery_app = import_class( + celery_app = import_string( self.get_django_settings('WORKER_OPTIONS')['app'].replace(':', '.') ) except ImportError: # nocv diff --git a/vstutils/api/models.py b/vstutils/api/models.py index 16b1992f..9d1dc3a4 100644 --- a/vstutils/api/models.py +++ b/vstutils/api/models.py @@ -3,7 +3,7 @@ import pyotp from django.conf import settings from django.db import models -from django.utils.module_loading import import_string as import_class +from django.utils.module_loading import import_string from django.utils.functional import cached_property from ..models import BaseModel, Manager, BQuerySet @@ -37,7 +37,7 @@ def _get_translation_data(self, module_path_string, code, for_server=False): data = {} for dict_name in filter(bool, ['TRANSLATION'] + ['SERVER_TRANSLATION' if for_server else None]): try: - translation_data = import_class(module_path_string + '.translations.' + code + f'.{dict_name}') + translation_data = import_string(module_path_string + '.translations.' + code + f'.{dict_name}') if not isinstance(translation_data, dict): continue data.update(translation_data.copy()) diff --git a/vstutils/api/routers.py b/vstutils/api/routers.py index d9a75aac..70c15ea8 100644 --- a/vstutils/api/routers.py +++ b/vstutils/api/routers.py @@ -2,10 +2,10 @@ from django.urls.conf import include, re_path, path from django.conf import settings from django.core.exceptions import ImproperlyConfigured +from django.utils.module_loading import import_string from rest_framework import routers, permissions, versioning from . import responses -from ..utils import import_class class _AbstractRouter(routers.DefaultRouter): @@ -87,9 +87,9 @@ def unregister(self, prefix): def generate(self, views_list): for prefix, options in views_list.items(): if 'view' in options: - view = import_class(options['view']) + view = import_string(options['view']) elif 'model' in options: - view = import_class(options['model']).generated_view + view = import_string(options['model']).generated_view args = [prefix, view] if 'name' in options: args.append(options['name']) @@ -114,7 +114,7 @@ def __init__(self, *args, **kwargs): self.__register_openapi() def __register_openapi(self): - schema_view = import_class(settings.OPENAPI_VIEW_CLASS) + schema_view = import_string(settings.OPENAPI_VIEW_CLASS) cache_timeout = settings.SCHEMA_CACHE_TIMEOUT swagger_kwargs = {'cache_timeout': 0 if settings.DEBUG or settings.TESTS_RUN else cache_timeout} self.register_view( @@ -243,6 +243,6 @@ def generate_routers(self, api): # Register view for new bulk requests self.register_view( r'endpoint', - import_class(settings.ENDPOINT_VIEW_CLASS), + import_string(settings.ENDPOINT_VIEW_CLASS), 'endpoint' ) diff --git a/vstutils/api/schema/generators.py b/vstutils/api/schema/generators.py index ea4b9821..66f1171f 100644 --- a/vstutils/api/schema/generators.py +++ b/vstutils/api/schema/generators.py @@ -5,9 +5,10 @@ import jwt from rest_framework import request as drf_request from django.conf import settings +from django.utils.module_loading import import_string from drf_yasg import generators from drf_yasg.inspectors import field as field_insp -from vstutils.utils import import_class, raise_context_decorator_with_default +from vstutils.utils import raise_context_decorator_with_default def get_centrifugo_public_address(request: drf_request.Request): @@ -42,7 +43,7 @@ def _get_hooks(self): filter( bool, map( - raise_context_decorator_with_default()(import_class), + raise_context_decorator_with_default()(import_string), getattr(settings, 'OPENAPI_HOOKS', ()) ) ) diff --git a/vstutils/api/views.py b/vstutils/api/views.py index 81e07c97..3525ba3f 100644 --- a/vstutils/api/views.py +++ b/vstutils/api/views.py @@ -3,6 +3,7 @@ from django.conf import settings from django.http import Http404, StreamingHttpResponse +from django.utils.module_loading import import_string from rest_framework import ( permissions as rest_permissions, throttling, @@ -12,7 +13,6 @@ from . import base, serializers, decorators as deco, responses, models from .decorators import subaction -from ..utils import import_class class LanguageSerializer(serializers.VSTSerializer): @@ -111,7 +111,7 @@ class HealthView(base.ListNonModelViewSet): permission_classes = (rest_permissions.AllowAny,) authentication_classes = () throttle_classes = (HealthThrottle,) - health_backend = import_class(settings.HEALTH_BACKEND_CLASS) + health_backend = import_string(settings.HEALTH_BACKEND_CLASS) def list(self, request, *args, **kwargs): return responses.HTTP_200_OK(*self.health_backend(request).get()) @@ -121,7 +121,7 @@ class MetricsView(base.ListNonModelViewSet): permission_classes = (rest_permissions.AllowAny,) authentication_classes = () throttle_classes = (MetricsThrottle,) - metrics_backend = import_class(settings.METRICS_BACKEND_CLASS)() + metrics_backend = import_string(settings.METRICS_BACKEND_CLASS)() def list(self, request, *args, **kwargs): return StreamingHttpResponse( diff --git a/vstutils/gui/context.py b/vstutils/gui/context.py index 4490823d..4a99dc7e 100644 --- a/vstutils/gui/context.py +++ b/vstutils/gui/context.py @@ -4,12 +4,13 @@ from django.conf import settings from django.http.request import HttpRequest from django.utils.functional import lazy, SimpleLazyObject +from django.utils.module_loading import import_string -from ..utils import import_class, raise_context_decorator_with_default +from ..utils import raise_context_decorator_with_default from ..static_files import SPA_STATIC_FILES_PROVIDERS -manifest_object = import_class(settings.MANIFEST_CLASS)() +manifest_object = import_string(settings.MANIFEST_CLASS)() project_lib_version = getattr(settings, 'PROJECT_LIB_VERSION', '') project_version = getattr(settings, 'PROJECT_VERSION', '') vstutils_version = settings.VSTUTILS_VERSION diff --git a/vstutils/models/base.py b/vstutils/models/base.py index e3c5eddb..60f8ec49 100644 --- a/vstutils/models/base.py +++ b/vstutils/models/base.py @@ -14,6 +14,7 @@ from django.db.models.fields.related import ManyToManyField, OneToOneField, ForeignKey from django.utils.functional import SimpleLazyObject, lazy from django.db.models.signals import post_save, post_delete +from django.utils.module_loading import import_string from django.dispatch import receiver from rest_framework.fields import ModelField, JSONField, CharField as drfCharField, ChoiceField from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin @@ -30,7 +31,6 @@ RelatedListField, ) from ..utils import ( - import_class, apply_decorators, classproperty, get_if_lazy, @@ -164,7 +164,7 @@ def _ensure_pk_in_fields(model_class, fields): def _import_class_if_string(value): if isinstance(value, str): - return SimpleLazyObject(lambda: import_class(value)) + return SimpleLazyObject(lambda: import_string(value)) return value @@ -561,7 +561,7 @@ def _get_view_class(cls, view_base_class): elif view_base_class == 'history': return api_base.HistoryModelViewSet # nocv elif isinstance(view_base_class, str): - return import_class(view_base_class) + return import_string(view_base_class) return view_base_class def get_view_class(cls, **extra_options): # noqa: CFQ001 diff --git a/vstutils/models/queryset.py b/vstutils/models/queryset.py index cbb5c176..f8b4a5bc 100644 --- a/vstutils/models/queryset.py +++ b/vstutils/models/queryset.py @@ -141,27 +141,28 @@ def _get_deep_nested_qs_with_cte(self, with_current=False, deep_children=True): # then deep_parents sql_column_to_get, sql_deep_column = sql_deep_column, sql_column_to_get - with_current_sql = f''' - UNION - {self.values(origin_model_pk).order_by().query} - ''' - - sql = f''' - WITH RECURSIVE nested as ( - SELECT {sql_table}.{sql_column_to_get}, {sql_table}.{sql_deep_column} - FROM {sql_table} - WHERE {sql_table}.{sql_deep_column} - IN ({str(self.values(origin_model_pk).order_by().query)}) - UNION - SELECT {sql_table}.{sql_column_to_get}, {sql_table}.{sql_deep_column} - FROM {sql_table} - JOIN nested - ON {sql_table}.{sql_deep_column} = nested.{sql_column_to_get} - ) - SELECT {sql_column_to_get} from nested - ''' # nosec + initial_qs = self.filter() + initial_qs.query.clear_ordering(True) + initial_qs.query.clear_select_fields() + initial_qs.query.clear_select_clause() + initial_qs = initial_qs.values(origin_model_pk) + sql = ' '.join(( + 'WITH RECURSIVE NRQ777 as (', + f'SELECT NU777.{sql_column_to_get}, NU777.{sql_deep_column}', # noqa: E131 + f'FROM {sql_table} NU777', + f'WHERE NU777.{sql_deep_column}', + f'IN ({str(initial_qs.query)})', # noqa: E131 + 'UNION', # noqa: E131 + f'SELECT NU777_1.{sql_column_to_get}, NU777_1.{sql_deep_column}', + f'FROM {sql_table} NU777_1', + 'JOIN NRQ777', + f'ON NU777_1.{sql_deep_column} = NRQ777.{sql_column_to_get}', # noqa: E131 + ')', + f'SELECT {sql_column_to_get} from NRQ777', # nosec + )) + if with_current: - sql += with_current_sql + sql += f' UNION {initial_qs.query}' return self.model.objects.filter(id__in=RawSQL(sql, [])) # nosec def _deep_nested_ids_without_cte(self, accumulated=None, deep_children=True): diff --git a/vstutils/tasks.py b/vstutils/tasks.py index d3315267..24c5880a 100644 --- a/vstutils/tasks.py +++ b/vstutils/tasks.py @@ -8,10 +8,11 @@ from celery.result import AsyncResult from django.conf import settings from django.apps import apps +from django.utils.module_loading import import_string -from .utils import import_class, send_template_email_handler, Lock, translate as _ +from .utils import send_template_email_handler, Lock, translate as _ -celery_app: Celery = import_class( +celery_app: Celery = import_string( settings.WORKER_OPTIONS['app'].replace(':', '.') ) notificator = None diff --git a/vstutils/tests.py b/vstutils/tests.py index 0694dd46..b44a160f 100644 --- a/vstutils/tests.py +++ b/vstutils/tests.py @@ -20,7 +20,7 @@ from django.utils.module_loading import import_string from fastapi.testclient import TestClient -from .utils import import_class, raise_context_decorator_with_default +from .utils import raise_context_decorator_with_default from .api.renderers import ORJSONRenderer User = get_user_model() @@ -222,7 +222,7 @@ def get_model_class(self, model) -> django_models.Model: handlers = ( lambda x: getattr(self.models, x, None) if self.models is not None else None, - import_class, + import_string, apps.get_model ) diff --git a/vstutils/utils.py b/vstutils/utils.py index 1286a464..aebad9ef 100644 --- a/vstutils/utils.py +++ b/vstutils/utils.py @@ -31,7 +31,7 @@ from django.template import loader from django.utils import translation, functional from django.utils.translation import get_language -from django.utils.module_loading import import_string as import_class +from django.utils.module_loading import import_string from django.views.decorators.csrf import csrf_exempt from django.views.generic.base import View @@ -1213,7 +1213,7 @@ def list(self): def _get_backend(self, backend): if backend in self.__loaded_backends__: return self.__loaded_backends__[backend] - self.__loaded_backends__[backend] = import_class(backend) + self.__loaded_backends__[backend] = import_string(backend) return self.__loaded_backends__[backend] def get_backend_data(self, name): diff --git a/yarn.lock b/yarn.lock index 239310db..29f56381 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2838,9 +2838,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541: - version "1.0.30001551" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz#1f2cfa8820bd97c971a57349d7fd8f6e08664a3e" - integrity sha512-vtBAez47BoGMMzlbYhfXrMV1kvRF2WP/lqiMuDu1Sb4EE4LKEgjopFDSRtZfdVnslNRpOqV/woE+Xgrwj6VQlg== + version "1.0.30001562" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz" + integrity sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng== centrifuge@^2.8.0: version "2.8.5"