From 1e8a1b3c02419d86c095f7dad90f9062ebe020e9 Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Thu, 7 Dec 2023 13:43:21 +1000 Subject: [PATCH 01/20] Feature(backend): Add Etag-based caching for swagger schema. --- doc/conf.py | 1 - requirements-doc.txt | 2 +- requirements-stubs.txt | 6 +- test_src/test_proj/tests.py | 23 ++ tox.ini | 2 +- vstutils/api/doc_generator.py | 410 ---------------------------------- vstutils/api/endpoint.py | 6 +- vstutils/api/schema/views.py | 33 +++ 8 files changed, 66 insertions(+), 417 deletions(-) delete mode 100644 vstutils/api/doc_generator.py diff --git a/doc/conf.py b/doc/conf.py index ec73af3f..53047094 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -61,7 +61,6 @@ 'sphinx_autodoc_typehints', 'sphinxcontrib.mermaid', 'sphinxcontrib.httpdomain', - 'vstutils.api.doc_generator', 'sphinx.ext.intersphinx', 'sphinx.ext.extlinks', ] diff --git a/requirements-doc.txt b/requirements-doc.txt index d14746c4..c91caa5d 100644 --- a/requirements-doc.txt +++ b/requirements-doc.txt @@ -5,4 +5,4 @@ sphinxcontrib-httpdomain~=1.8.1 sphinxcontrib-websupport~=1.2.4 sphinxcontrib-mermaid~=0.7.1 sphinx-autodoc-typehints~=1.23.0 -sphinx-rtd-theme~=1.3.0 +sphinx-rtd-theme~=2.0.0 diff --git a/requirements-stubs.txt b/requirements-stubs.txt index cf18c563..2305cace 100644 --- a/requirements-stubs.txt +++ b/requirements-stubs.txt @@ -1,10 +1,10 @@ -django-stubs[compatible-mypy]~=4.2.6 -djangorestframework-stubs[compatible-mypy]~=3.14.4 +django-stubs[compatible-mypy]~=4.2.7 +djangorestframework-stubs[compatible-mypy]~=3.14.5 celery-stubs~=0.1.3 drf-yasg-stubs~=0.1.3 django-filter-stubs~=0.1.3 types-PyMySQL==1.1.0.1 -types-Markdown==3.5.0.0 +types-Markdown==3.5.0.3 types-docutils==0.20.0.3 types-aiofiles~=23.2.0.0 typing-extensions~=4.8.0 diff --git a/test_src/test_proj/tests.py b/test_src/test_proj/tests.py index 7e81eea2..a488cd4f 100644 --- a/test_src/test_proj/tests.py +++ b/test_src/test_proj/tests.py @@ -2331,6 +2331,29 @@ def test_search_fields(self): ('name', 'phone', 'masked') ) + def test_etag(self): + client = self.client + + response1 = client.get('/api/endpoint/?format=openapi') + self.assertEqual(response1.status_code, 200) + + headers = {'if-none-match': response1.headers['Etag']} + + response2 = client.get('/api/endpoint/?format=openapi', headers=headers) + self.assertEqual(response2.status_code, 304) + + response3 = client.get('/api/endpoint/?format=openapi&version=v2', headers=headers) + self.assertEqual(response3.status_code, 200) + + self._login() + headers['Cookie'] = f'{settings.SESSION_COOKIE_NAME}={self.client.session.session_key}; lang=en' + response4 = client.get('/api/endpoint/?format=openapi', headers=headers) + self.assertEqual(response4.status_code, 200, response4.content) + + headers['if-none-match'] = response4.headers['Etag'] + response5 = client.get('/api/endpoint/?format=openapi', headers=headers) + self.assertEqual(response5.status_code, 304) + def test_api_version_request(self): api = self.get_result('get', '/api/endpoint/?format=openapi&version=v2', 200) paths_which_is_tech = (r'settings', r'_lang') diff --git a/tox.ini b/tox.ini index 03f28f66..507a7d61 100644 --- a/tox.ini +++ b/tox.ini @@ -95,7 +95,7 @@ changedir = ./ setenv = DONT_YARN = true deps = - mypy==1.6.1 + mypy==1.7.1 commands = pip uninstall vstutils -y pip install -U -e .[stubs] diff --git a/vstutils/api/doc_generator.py b/vstutils/api/doc_generator.py deleted file mode 100644 index 348457aa..00000000 --- a/vstutils/api/doc_generator.py +++ /dev/null @@ -1,410 +0,0 @@ -# pylint: disable=import-error -import collections -import io -import re -import json - -import yaml -from docutils.statemachine import ViewList -from docutils.parsers.rst import Directive, directives -from docutils import nodes -from sphinx.util.nodes import nested_parse_with_titles -from sphinxcontrib.httpdomain import HTTP_STATUS_CODES # type: ignore - - -class _YamlOrderedLoader(yaml.SafeLoader): - pass - - -_YamlOrderedLoader.add_constructor( - yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, - lambda loader, node: collections.OrderedDict(loader.construct_pairs(node)) -) - - -class VSTOpenApiBase(Directive): - find_model = re.compile(r"\"\$ref\":.*\"#\/(?P.*)\/(?P.*)\"") - find_param = re.compile(r"(?<=\{)[\w]+(?=\})") - required_arguments = 1 # path to openapi spec - final_argument_whitespace = True # path may contain whitespaces - openapi_version = None - models_path = 'definitions' - path_path = 'paths' - indent_depth = 2 - indent = ' ' - type_dict = { - 'fk': 1, - 'integer': 1, - 'uri': 'http://localhost:8080{}', - 'string': 'example {}', - 'textarea': 'example\ntext\narea\n', - 'boolean': True, - 'select2': 'username', - 'dynamic': 'test_dynamic', - 'uptime': '22:11:34', - 'date_time': '2019-01-07T06:10:31+10:00', - 'html': 'test_html', - 'email': 'example@mail.com', - 'file': 'value data', - 'secretfile': 'secret data', - } - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.__view_list = ViewList() - self.spec = {} - self.paths = {} - self.definitions = {} - self.current_path = None - - def load_yaml(self): - """ - Load OpenAPI specification from yaml file. Path to file taking from command - `vst_openapi`. - :return: - """ - env = self.state.document.settings.env - relpath, abspath = env.relfn2path(directives.path(self.arguments[0])) - - env.note_dependency(relpath) - - encoding = self.options.get('encoding', env.config.source_encoding) - with io.open(abspath, 'rt', encoding=encoding) as stream: - spec = yaml.load(stream, _YamlOrderedLoader) # nosec - self.spec = spec - self.paths = spec[self.path_path] - self.definitions = spec[self.models_path] - self.openapi_version = spec.get('swagger', None) or spec['openapi'] - self.options.setdefault('uri', f'file://{abspath}') - - def write(self, value, indent_depth=0): - """ - Add lines to ViewList, for further rendering. - :param value: --line that would be added to render list - :type value: str, unicode - :param indent_depth: --value that show indent from left border - :type indent_depth: integer - :return: - """ - self.__view_list.append(self.indent * indent_depth + value, '') - - def run(self): - """ - Main function for prepare and render OpenAPI specification - :return: - """ - # Loading yaml - self.load_yaml() - - # Print paths from schema - section_title = '**API Paths**' - self.write(section_title) - self.write('=' * len(section_title)) - self.print_paths() - - # Print models - section_title = '**Schemas Description**' - self.write(section_title) - self.write('=' * len(section_title)) - self.print_schemas() - - # Render by sphinx - node = nodes.section() - node.document = self.state.document - nested_parse_with_titles(self.state, self.__view_list, node) - return node.children - - def print_paths(self): - """ - Cycle for prepare information about paths - :return: - """ - for path_key, path_value in self.paths.items(): - # Handler for request in path - self.current_path = path_key - for request_key, request_value in path_value.items(): - if request_key == 'parameters': - continue - self.get_main_title(path_key, request_key) - self.get_description(request_value) - self.get_status_code_and_schema_rst(request_value['responses']) - self.get_params(path_value['parameters'], 'param') - self.get_params(request_value['parameters'], 'query') - - def print_schemas(self): - """ - Print all schemas, one by one - :return: - """ - self.indent_depth += 1 - for i in self.definitions: - def_name = i.split('/')[-1] - self.write(f'.. _{def_name}:') - self.write('') - self.write(f'{def_name} Schema') - self.write(f'{"`" * (len(def_name) + 7)}') - self.write('') - self.write('.. code-block:: json', self.indent_depth) - self.indent_depth += 1 - self.write('') - self.definition_rst(def_name) - self.indent_depth -= 1 - self.write('') - self.write('') - self.indent_depth -= 1 - - def get_main_title(self, path_name, request_name): - """ - Get title, from request type and path - - :param path_name: --path for create title - :type path_name: str, unicode - :param request_name: --name of request - :type request_name: str, unicode - :return: - """ - main_title = f'.. http:{request_name}:: {path_name}' - self.write(main_title) - self.write('') - - def get_status_code_and_schema_rst(self, responses): - """ - Function for prepare information about responses with example, prepare only - responses with status code from `101` to `299` - :param responses: -- dictionary that contains responses, with status code as key - :type responses: dict - :return: - """ - for status_code, response_schema in responses.items(): - status_code = int(status_code) - schema = response_schema.get('schema', None) - status = HTTP_STATUS_CODES.get(status_code, None) - if status is None or not (100 < status_code < 300): - continue - self.write('**Example Response**', 1) - self.write('') - self.write('.. code-block:: http', 1) - self.write('') - self.write(f'HTTP/1.1 {status_code} {status}', 2) - self.write(f'Vary: {response_schema["description"]}', 2) - self.write('Content-Type: application/json', 2) - self.write('') - - if schema: - self.schema_handler(schema) - else: - self.write('{}', self.indent_depth) - - def schema_handler(self, schema): - """ - Function prepare body of response with examples and create detailed information - about response fields - - :param schema: --dictionary with information about answer - :type schema: dict - :return: - """ - dict_for_render = schema.get('properties', {}).items() - if schema.get('$ref', None): - def_name = schema.get('$ref').split('/')[-1] - dict_for_render = self.definitions[def_name].get('properties', {}).items() - elif schema.get('properties', None) is None: - return '' - - answer_dict = {} - json_dict = {} - for opt_name, opt_value in dict_for_render: - var_type = opt_value.get('format', None) or opt_value.get('type', None) or 'object' - json_name = self.indent + f':jsonparameter {var_type} {opt_name}:' - json_dict[json_name] = self.get_json_props_for_response(var_type, opt_value) - - answer_dict[opt_name] = self.get_response_example(opt_name, var_type, opt_value) - if var_type == 'string': - answer_dict[opt_name] = answer_dict[opt_name].format(opt_name) - - self.write('') - for line in json.dumps(answer_dict, indent=4).split('\n'): - self.write(line, self.indent_depth) - - self.write('') - for json_param_name, json_param_value in json_dict.items(): - desc = f'{json_param_value["title"]}{json_param_value["props_str"]}' or 'None' - self.write(json_param_name + ' ' + desc) - - def get_json_props_for_response(self, var_type, option_value): - """ - Prepare JSON section with detailed information about response - - :param var_type: --contains variable type - :type var_type: , unicode - :param option_value: --dictionary that contains information about property - :type option_value: dict - :return: dictionary that contains, title and all properties of field - :rtype: dict - """ - props = [] - for name, value in option_value.items(): - if var_type in ['dynamic', 'select2']: # pylint: disable=no-else-break - break - elif name in ['format', 'title', 'type']: - continue - elif isinstance(value, dict) and value.get('$ref', None): - props.append(f':ref:`{value["$ref"].split("/")[-1]}`') - elif '$ref' in name: - props.append(f':ref:`{value.split("/")[-1]}`') - elif var_type == 'autocomplete': - props.append('Example values: ' + ', '.join(value)) - else: - props.append(f'{name}={value}') - - if len(props): - props_str = '(' + ', '.join(props) + ')' - else: - props_str = '' - return {'props_str': props_str, 'title': option_value.get('title', '')} - - def get_response_example(self, opt_name, var_type, opt_values): - """ - Depends on type of variable, return string with example - :param opt_name: --option name - :type opt_name: str,unicode - :param var_type: --type of variable - :type var_type: str, unicode - :param opt_values: --dictionary with properties of this variable - :type opt_values: dict - :return: example for `var_type` variable - :rtype: str, unicode - """ - if opt_name == 'previous' and var_type == 'uri': - result = None - elif var_type == 'uri': - params = {i.group(0): 1 for i in self.find_param.finditer(self.current_path)} - result = self.type_dict[var_type].format(self.current_path.format(**params)) - if opt_name == 'next': - result += '?limit=1&offset=1' - elif opt_name == 'count' and var_type == 'integer': - result = 2 - elif var_type == 'array': - items = opt_values.get('items', {}).get('$ref', None) - item = 'array_example' - if items: - item = self.get_object_example(items.split('/')[-1]) - result = [item] - elif var_type == 'autocomplete': - result = opt_values.get('enum', [])[0] - elif var_type in [None, 'object']: - def_name = (opt_values.get('$ref') or '').split('/')[-1] - result = self.get_object_example(def_name) - elif var_type == 'select2': - def_name = opt_values['x-options']['model']['$ref'].split('/')[-1] - value_field_name = opt_values['x-options']['value_field'] - def_model = self.definitions[def_name].get('properties') - value_field = def_model.get(value_field_name, None) - var_type = value_field.get('format', None) or value_field.get('type', None) - result = self.get_response_example(opt_name, var_type, def_model) - else: - var_type = var_type.replace('-', '_') - result = opt_values.get('default', None) or self.type_dict.get(var_type, None) - return result - - def get_object_example(self, def_name): - """ - Create example for response, from object structure - - :param def_name: --deffinition name of structure - :type def_name: str, unicode - :return: example of object - :rtype: dict - """ - def_model = self.definitions.get(def_name, {}) - example = {} - for opt_name, opt_value in def_model.get('properties', {}).items(): - var_type = opt_value.get('format', None) or opt_value.get('type', None) - example[opt_name] = self.get_response_example(opt_name, var_type, opt_value) - if var_type == 'string': - example[opt_name] = example[opt_name].format(opt_name) - return example - - def definition_rst(self, definition, spec_path=None): - """ - Prepare and write information about definition - :param definition: --name of definition that would be prepared for render - :type definition: str, unicode - :param spec_path: --path to definitions - :type spec_path: str, unicode - :return: - """ - spec_path = spec_path or self.models_path - definitions = self.spec[spec_path] - definition_property = definitions[definition]['properties'].copy() - if not definition_property: - self.write('{}', self.indent_depth) - return - self.indent_depth += 1 - definition_property = self.find_nested_models(definition_property, definitions) - json_str = json.dumps(definition_property, indent=4) - for line in json_str.split('\n'): - self.write(line, self.indent_depth) - self.indent_depth -= 1 - - def find_nested_models(self, model, definitions): - """ - Prepare dictionary with reference to another definitions, create one dictionary - that contains full information about model, with all nested reference - :param model: --dictionary that contains information about model - :type model: dict - :param definitions: --dictionary that contains copy of all definitions - :type definitions: dict - :return: dictionary with all nested reference - :rtype: dict - """ - for key, value in model.items(): - if isinstance(value, dict): - model[key] = self.find_nested_models(value, definitions) - elif key == '$ref': - def_name = value.split('/')[-1] - def_property = definitions[def_name]['properties'] - return self.find_nested_models(def_property, definitions) - return model - - def get_params(self, params, name_request): - """ - Prepare and add for further render parameters. - :param params: --dictionary with parameters - :type params: dict - :param name_request: --type of the parameters - :type name_request: str, unicode - :return: - """ - self.write('') - for elem in params: - request_type = elem['type'] if elem.get('type', None) else 'schema' - name = elem['name'] - if elem.get('required', None): - name += '(required)' - schema = elem.get('schema', None) - name = f':{name_request} {request_type} {name}:' - if schema: - definition = schema['$ref'].split('/')[-1] - self.write(name + f' :ref:`{definition}`', 1) - self.write('') - else: - desc = elem.get('description', '') - self.write(name) - self.write(f'{desc}', self.indent_depth + 1) - self.write('') - - def get_description(self, request_value): - """ - Add description about this path and type - :param request_value: --dictionary with information about this path - :type request_value: dict - :return: - """ - self.write(request_value['description'], 1) - self.write('') - - -def setup(app): - app.setup_extension('sphinxcontrib.httpdomain') - app.add_directive('vst_openapi', VSTOpenApiBase) diff --git a/vstutils/api/endpoint.py b/vstutils/api/endpoint.py index ca19b47a..2d81e47b 100644 --- a/vstutils/api/endpoint.py +++ b/vstutils/api/endpoint.py @@ -451,7 +451,11 @@ def get(self, request: BulkRequestType) -> HttpResponse: else: should_gzip = False - response = self.get_client(request).get(url, secure=request.is_secure()) + headers = {} + if 'if-none-match' in request.headers: + headers['if-none-match'] = request.headers['if-none-match'] + + response = self.get_client(request).get(url, secure=request.is_secure(), headers=headers) if should_gzip: patch_gzip_response(response, request) diff --git a/vstutils/api/schema/views.py b/vstutils/api/schema/views.py index 81679f5c..e1c5504f 100644 --- a/vstutils/api/schema/views.py +++ b/vstutils/api/schema/views.py @@ -1,7 +1,40 @@ +import hashlib + +from django.conf import settings from drf_yasg.views import get_schema_view from rest_framework import permissions, versioning +from ..base import check_request_etag, CachableHeadMixin + class OpenApiView(get_schema_view()): # type: ignore permission_classes = (permissions.AllowAny,) versioning_class = versioning.NamespaceVersioning + + def get_etag_dependencies(self, request): + dependencies = [settings.FULL_VERSION] + if request.user.is_authenticated: + dependencies.append(request.user.id) + if version := getattr(request, 'version', None): + dependencies.append(version) + return dependencies + + def get_etag_value(self, request): + value = hashlib.blake2s( + "_".join(map(str, self.get_etag_dependencies(request))).encode("utf-8"), + digest_size=8 + ).hexdigest() + return f'"{value}"' + + def initial(self, request, *args, **kwargs): + super().initial(request, *args, **kwargs) + + self.etag_value, matched = check_request_etag(request, self.get_etag_value(request)) + if matched: + raise CachableHeadMixin.NotModifiedException() + + def finalize_response(self, request, response, *args, **kwargs): + response = super().finalize_response(request, response, *args, **kwargs) + if etag_value := getattr(self, 'etag_value', None): + response['Etag'] = etag_value + return response From cb1354ca02afe5ca4344443f9ce4089b1c819697 Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Wed, 6 Dec 2023 22:36:14 -0800 Subject: [PATCH 02/20] Fix(backend): Update LDAP libs and search problems. --- requirements-ldap.txt | 2 +- requirements-rtd.txt | 4 ++-- requirements-test.txt | 4 ++-- requirements.txt | 2 +- test_src/test_proj/tests.py | 4 ++-- vstutils/ldap_utils.py | 18 ++++++++++-------- vstutils/settings.py | 2 ++ 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/requirements-ldap.txt b/requirements-ldap.txt index b1715dbc..efbe953c 100644 --- a/requirements-ldap.txt +++ b/requirements-ldap.txt @@ -1,2 +1,2 @@ # Packages needed for ldap-auth -python-ldap==3.4.0 +python-ldap==3.4.4 diff --git a/requirements-rtd.txt b/requirements-rtd.txt index 7233b9b7..7ba0c2e6 100644 --- a/requirements-rtd.txt +++ b/requirements-rtd.txt @@ -1,7 +1,7 @@ -rrequirements.txt -rrequirements-doc.txt -rrequirements-rpc.txt -django~=4.2.7 -httpx>=0.25.1 +django~=4.2.8 +httpx>=0.25.2 typing-extensions sphinx-intl~=2.1.0 diff --git a/requirements-test.txt b/requirements-test.txt index 9307b2f4..17316ca1 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,6 +1,6 @@ # Packages needed for test coverage~=7.3.2 -fakeldap==0.6.1 -tblib~=1.7.0 +fakeldap~=0.6.6 +tblib==3.0.0 beautifulsoup4~=4.12.2 httpx~=0.25.1 diff --git a/requirements.txt b/requirements.txt index ac0cf7c1..48cf2526 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ django-environ~=0.11.2 # REST API packages djangorestframework~=3.14.0 drf-yasg==1.21.7 -django-filter==23.4 +django-filter==23.5 drf_orjson_renderer==1.7.1 ormsgpack~=1.4.1 pyyaml~=6.0.1 diff --git a/test_src/test_proj/tests.py b/test_src/test_proj/tests.py index a488cd4f..ffe35220 100644 --- a/test_src/test_proj/tests.py +++ b/test_src/test_proj/tests.py @@ -373,8 +373,8 @@ def test_ldap_auth(self, ldap_obj): ldap_backend.auth(admin, admin_password) self.assertTrue(ldap_backend.isAuth()) self.assertEqual( - json.loads(ldap_backend.group_list())["dc=test,dc=lan"], - tree["dc=test,dc=lan"] + json.loads(ldap_backend.group_list())['cn=admin,dc=test,dc=lan'], + tree["dc=test,dc=lan"]['cn=admin,dc=test,dc=lan'] ) def test_model_handler(self): diff --git a/vstutils/ldap_utils.py b/vstutils/ldap_utils.py index d63dfdb0..98f0b780 100644 --- a/vstutils/ldap_utils.py +++ b/vstutils/ldap_utils.py @@ -29,8 +29,9 @@ class LDAP: 'username', 'password', 'domain', - '__conn', + '_conn', 'user_format', + 'search_scope', ) fields = ['cn', 'sAMAccountName', 'accountExpires', 'name', 'memberOf'] LdapError = ldap.LDAPError @@ -52,6 +53,7 @@ def __init__(self, connection_string: Text, username: Text = '', password: Text :param domain: domain for easy use users """ self.settings = settings + self.search_scope = getattr(ldap, f'SCOPE_{settings.LDAP_SEARCH_SCOPE}') self.user_format = settings.LDAP_FORMAT.replace('<', "{").replace('>', '}') self.logger = logging.getLogger(settings.VST_PROJECT_LIB) self.connection_string = connection_string @@ -78,7 +80,7 @@ def __init__(self, connection_string: Text, username: Text = '', password: Text self.auth(self.username, self.password) def auth(self, username: Text = None, password: Text = None) -> None: - self.__conn = self.__authenticate( + self._conn = self.__authenticate( self.connection_string, str(username or self.username), str(password or self.password) @@ -145,11 +147,11 @@ def isAuth(self) -> bool: Indicates that object auth worked :return: True or False """ - if isinstance(self.__conn, ldap.ldapobject.LDAPObject) or self.__conn: + if isinstance(self._conn, ldap.ldapobject.LDAPObject) or self._conn: return True return False - def __ldap_filter(self, *filters): + def _ldap_filter(self, *filters): dc_list = [f"dc={i}" for i in self.domain_name.split('.') if i] additinal_filter = "".join([f"({i})" for i in filters if i]) s_filter = f'(&(objectCategory=user){additinal_filter})' @@ -157,14 +159,14 @@ def __ldap_filter(self, *filters): self.logger.debug( f'Search in LDAP: {json.dumps(odict(BASE_DN=base_dn, FILTER=s_filter, FIELDS=self.fields))}' ) - return base_dn, ldap.SCOPE_SUBTREE, s_filter, self.fields + return base_dn, self.search_scope, s_filter, self.fields def group_list(self, *args) -> Text: if not self.isAuth(): raise self.NotAuth("Invalid auth.") try: data = { - k: v for k, v in self.__conn.search_s(*self.__ldap_filter(*args)) if k + k: v for k, v in self._conn.search_s(*self._ldap_filter(*args)) if k } return json.dumps(data, indent=4, ensure_ascii=False, default=json_default) except Exception: # nocv @@ -182,5 +184,5 @@ def __str__(self): # nocv return f'[ {msg} {self.connection_string} -> {self.username} ]' def __del__(self): - if isinstance(getattr(self, '__conn', None), ldap.ldapobject.LDAPObject): - self.__conn.unbind_s() # nocv + if isinstance(getattr(self, '_conn', None), ldap.ldapobject.LDAPObject): + self._conn.unbind_s() # nocv diff --git a/vstutils/settings.py b/vstutils/settings.py index bf68e440..3955f520 100644 --- a/vstutils/settings.py +++ b/vstutils/settings.py @@ -443,6 +443,7 @@ class DjangoEnv(environ.Env): 'ldap-server': env.str(f'{ENV_NAME}_LDAP_CONNECTION', default=None), 'ldap-default-domain': env.str(f'{ENV_NAME}_LDAP_DOMAIN', default=''), 'ldap-auth_format': env.str(f'{ENV_NAME}_LDAP_AUTH_FORMAT', default='cn=,'), + 'ldap-search-scope': env.str(f'{ENV_NAME}_LDAP_SEARCH_SCOPE', default='ONELEVEL'), 'language_cookie_name': env.str(f'{ENV_NAME}_LANGUAGE_COOKIE_NAME', default='lang'), 'agreement_terms_path': env.str(f'{ENV_NAME}_TERMS_PATH', default=f'/etc/{VST_PROJECT_LIB}/terms.md'), 'consent_to_processing_path': env.str( @@ -743,6 +744,7 @@ def secret_key(secret_file, default='*sg17)9wa_e+4$n%7n7r_(kqwlsc^^xdoc3&px$hs)s LDAP_SERVER: _t.Optional[_t.Text] = main["ldap-server"] LDAP_DOMAIN: _t.Optional[_t.Text] = main["ldap-default-domain"] LDAP_FORMAT: _t.Text = main["ldap-auth_format"] +LDAP_SEARCH_SCOPE: _t.Text = main["ldap-search-scope"] DEFAULT_AUTH_PLUGINS: SIMPLE_OBJECT_SETTINGS_TYPE = { 'LDAP': { From 69d0e0dfee1280317c1f7f3307d85d451213cb9e Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Thu, 14 Dec 2023 15:53:04 +1000 Subject: [PATCH 03/20] Feature(backend): Add tarantool drivers. --- doc/config.rst | 70 ++++- doc/locale/ru/LC_MESSAGES/config.po | 424 +++++++++++++++++++--------- pyproject.toml | 3 + requirements-prod.txt | 1 + requirements-stubs.txt | 2 +- requirements.txt | 2 +- vstutils/drivers/__init__.py | 7 + vstutils/drivers/cache.py | 203 +++++++++++++ vstutils/drivers/kombu.py | 261 +++++++++++++++++ vstutils/environment.py | 4 +- vstutils/settings.py | 1 + 11 files changed, 847 insertions(+), 131 deletions(-) create mode 100644 vstutils/drivers/__init__.py create mode 100644 vstutils/drivers/cache.py create mode 100644 vstutils/drivers/kombu.py diff --git a/doc/config.rst b/doc/config.rst index 06db0ace..ceac81db 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -11,8 +11,8 @@ vstutils-based application deeply by tweaking ``/etc/{{app_name or app_lib_name} The most important thing to keep in mind when planning your application architecture is that vstutils-based applications have a service-oriented structure. To build a distributed scalable system you only need to connect to a shared database_, -shared cache_, locks_ and a shared rpc_ service (MQ such as RabbitMQ, Redis, etc.). -A shared file storage may be required in some cases, a but vstutils does not require it. +shared cache_, locks_ and a shared rpc_ service (MQ such as RabbitMQ, Redis, Tarantool, etc.). +A shared file storage may be required in some cases, but vstutils does not require it. Let's cover the main sections of the config and its parameters: @@ -173,6 +173,51 @@ additional plugins. You can find details about cache configs supported using client-server cache realizations. We recommend to use Redis in production environments. +Tarantool Cache Backend for Django +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``TarantoolCache`` is a custom cache backend for Django that allows you to use Tarantool as a caching mechanism. +To use this backend, you need to configure the following settings in your project's configuration: + +.. sourcecode:: bash + + [cache] + location = localhost:3301 + backend = vstutils.drivers.cache.TarantoolCache + + [cache.options] + space = default + user = guest + password = guest + +Explanation of Settings: + +* **location** - The host name and port for connecting to the Tarantool server. +* **backend** - The path to the TarantoolCache backend class. +* **space** - The name of the space in Tarantool to use as the cache (default is ``DJANGO_CACHE``). +* **user** - The username for connecting to the Tarantool server (default is ``guest``). +* **password** - The password for connecting to the Tarantool server. Optional. + +Additionally, you can set the ``connect_on_start`` variable in the ``[cache.options]`` section. +When set to ``true`` value, this variable triggers an initial connection to the Tarantool server +to configure spaces and set up the service for automatic removal of outdated entries. + +.. warning:: + Note that this requires the ``expirationd`` module to be installed on the Tarantool server. + +.. note:: + When utilizing Tarantool as a cache backend in VST Utils, temporary spaces are automatically created to facilitate seamless operation. + These temporary spaces are dynamically generated as needed and are essential for storing temporary data efficiently. + + It's important to mention that while temporary spaces are automatically handled, if you intend to use persistent spaces on disk, + it is necessary to pre-create them on the Tarantool server with schema settings similar to those used by the VST Utils configuration. + Ensure that any persistent spaces required for your application are appropriately set up on the Tarantool server + with the same schema configurations for consistent and reliable operation. + +.. note:: + It's important to note that this cache driver is unique to vstutils and tailored to seamlessly + integrate with the VST Utils framework. + .. _locks: @@ -236,6 +281,27 @@ are also supported (with the corresponding types): * **task_send_sent_event** - :celery_docs:`CELERY_DEFAULT_DELIVERY_MODE ` * **worker_send_task_events** - :celery_docs:`CELERY_DEFAULT_DELIVERY_MODE ` +VST Utils provides seamless support for using Tarantool as a transport for Celery, allowing efficient and reliable message passing between distributed components. +To enable this feature, ensure that the Tarantool server has the `queue` module installed. + +To configure the connection, use the following example URL: ``tarantool://guest@localhost:3301/rpc`` + +* ``tarantool://``: Specifies the transport. +* ``guest``: Authentication parameters (in this case, no password). +* ``localhost``: Server address. +* ``3301``: Port for connection. +* ``rpc``: Prefix for queue names and/or result storage. + +VST Utils also supports Tarantool as a backend for storing Celery task results. Connection string is similar to the transport. + +.. note:: + When utilizing Tarantool as a result backend or transport in VST Utils, temporary spaces and queues are automatically created to facilitate seamless operation. + These temporary spaces are dynamically generated as needed and are essential for storing temporary data efficiently. + + It's important to mention that while temporary spaces are automatically handled, if you intend to use persistent spaces on disk, + it is necessary to pre-create them on the Tarantool server with schema settings similar to those used by the VST Utils configuration. + Ensure that any persistent spaces required for your application are appropriately set up on the Tarantool server + with the same schema configurations for consistent and reliable operation. .. _worker: diff --git a/doc/locale/ru/LC_MESSAGES/config.po b/doc/locale/ru/LC_MESSAGES/config.po index 2a5ce5d2..5ca305e4 100644 --- a/doc/locale/ru/LC_MESSAGES/config.po +++ b/doc/locale/ru/LC_MESSAGES/config.po @@ -7,14 +7,14 @@ msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-06-01 08:19+0000\n" +"POT-Creation-Date: 2023-12-14 02:43+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.12.1\n" +"Generated-By: Babel 2.13.1\n" #: ../../config.rst:2 msgid "Configuration manual" @@ -45,15 +45,16 @@ msgid "" "architecture is that vstutils-based applications have a service-oriented " "structure. To build a distributed scalable system you only need to " "connect to a shared database_, shared cache_, locks_ and a shared rpc_ " -"service (MQ such as RabbitMQ, Redis, etc.). A shared file storage may be " -"required in some cases, a but vstutils does not require it." +"service (MQ such as RabbitMQ, Redis, Tarantool, etc.). A shared file " +"storage may be required in some cases, but vstutils does not require " +"it." msgstr "" "Самое важное, о чем нужно помнить при планировании архитектуры вашего " "приложения, это то, что приложения, основанные на vstutils, имеют " "сервисно-ориентированную структуру. Чтобы построить распределенную " "масштабируемую систему, вам нужно только подключиться к :ref:`общей базе " "данных `, :ref:`общему кэшу `, :ref:`блокировкам " -"` и общей :ref:`службе rpc ` (MQ, такому как RabbitMQ, Redis " +"` и общей :ref:`службе rpc ` (MQ, такому как RabbitMQ, Redis, Tarantool " "и т. д.). В некоторых случаях может потребоваться общее файловое " "хранилище, но vstutils не требует его." @@ -426,15 +427,127 @@ msgstr "" "клиент-серверного кэша. Мы рекомендуем использовать Redis в " "производственных окружениях." -#: ../../config.rst:180 +#: ../../config.rst:177 +msgid "Tarantool Cache Backend for Django" +msgstr "" +"Tarantool в качестве сервера кеша для Django" + +#: ../../config.rst:179 +msgid "" +"The ``TarantoolCache`` is a custom cache backend for Django that allows " +"you to use Tarantool as a caching mechanism. To use this backend, you " +"need to configure the following settings in your project's configuration:" +msgstr "" +"``TarantoolCache`` это уникальный бэкенд для кеша Django, который позволяет " +"использовать Tarantool как сервер-хранилище кешей. Чтобы использовать этот механизм " +"необходимо выставить следующие настройки в конфигурации проекта:" + +#: ../../config.rst:193 +msgid "Explanation of Settings:" +msgstr "Расшифровка настроек" + +#: ../../config.rst:195 +msgid "" +"**location** - The host name and port for connecting to the Tarantool " +"server." +msgstr "" +"**location** - Имя хоста и порт для подключения к серверу Tarantool." + +#: ../../config.rst:196 +msgid "**backend** - The path to the TarantoolCache backend class." +msgstr "" +"**backend** - Путь до класса TarantoolCache бэкенда." + +#: ../../config.rst:197 +msgid "" +"**space** - The name of the space in Tarantool to use as the cache " +"(default is ``DJANGO_CACHE``)." +msgstr "" +"**space** - Имя спейса в Tarantool для использования кешем " +"(по умолчанию ``DJANGO_CACHE``)." + +#: ../../config.rst:198 +msgid "" +"**user** - The username for connecting to the Tarantool server (default " +"is ``guest``)." +msgstr "" +"**user** - Имя пользователя для подключения к Tarantool-серверу. (По умолчанию:" +" ``guest``)." + +#: ../../config.rst:199 +msgid "" +"**password** - The password for connecting to the Tarantool server. " +"Optional." +msgstr "" +"**password** - Пароль для подключения к серверу Tarantool. " +"Не обязательный." + +#: ../../config.rst:201 +msgid "" +"Additionally, you can set the ``connect_on_start`` variable in the " +"``[cache.options]`` section. When set to ``true`` value, this variable " +"triggers an initial connection to the Tarantool server to configure " +"spaces and set up the service for automatic removal of outdated entries." +msgstr "" +"Можно дополнительно указать переменную ``connect_on_start`` в " +"секции ``[cache.options]``. Когда задано значение ``true``, это указывает на " +"вызов дополнительной инициализации на Tarantool сервере для настройки " +"спейсов и настройки сервиса для автоматического удаления просроченных объектов." + +#: ../../config.rst:206 +msgid "" +"Note that this requires the ``expirationd`` module to be installed on the" +" Tarantool server." +msgstr "" +"Обратите внимание, что это требует установки модуля ``expirationd`` на" +" сервере Tarantool." + +#: ../../config.rst:209 +msgid "" +"When utilizing Tarantool as a cache backend in VST Utils, temporary " +"spaces are automatically created to facilitate seamless operation. These " +"temporary spaces are dynamically generated as needed and are essential " +"for storing temporary data efficiently." +msgstr "" +"Когда используется Tarantool в качестве кеша то, " +"автоматически создаются временные (в памяти) спейсы для операций с кешем. " +"Эти временные спейсы динамически создаются по необходимости и нужны, чтобы " +"хранить временные данные максимально эффективно." + +#: ../../config.rst:212 ../../config.rst:301 +msgid "" +"It's important to mention that while temporary spaces are automatically " +"handled, if you intend to use persistent spaces on disk, it is necessary " +"to pre-create them on the Tarantool server with schema settings similar " +"to those used by the VST Utils configuration. Ensure that any persistent " +"spaces required for your application are appropriately set up on the " +"Tarantool server with the same schema configurations for consistent and " +"reliable operation." +msgstr "" +"Это важно, что эти спейсы автоматически создаются в памяти, и если необходимо " +"сделать, чтобы эти спейсы хранили данные на дисках, то необходимо " +"заранее создать их на серевере Tarantool с такой же схемой и аналогичными " +"настройками, которые используются в VST Utils. Убедитесь, что вам необходимо " +"использовать устойчивые хранилища в вашем приложении, прежде чем создавать их " +"на сервере Tarantool с аналогичными названиями. Это важно для стабильной и устойчивой работы." + +#: ../../config.rst:218 +msgid "" +"It's important to note that this cache driver is unique to vstutils and " +"tailored to seamlessly integrate with the VST Utils framework." +msgstr "" +"Важно отметить, что этот драйвер кеша уникальный для vstutils и " +"адаптирован для полной интеграции с фреймворком VST Utils." + +#: ../../config.rst:225 msgid "Locks settings" msgstr "Настройки блокировок" -#: ../../config.rst:182 +#: ../../config.rst:227 msgid "Section ``[locks]``." msgstr "Раздел ``[locks]``." -#: ../../config.rst:184 +#: ../../config.rst:229 msgid "" "Locks is a system that vstutils-based application uses to avoid damage " "from parallel actions working on the same entity simultaneously. It is " @@ -460,15 +573,15 @@ msgstr "" "бэкэнда для этой цели. Бэкэнд кэша и блокировок могут быть одним и тем " "же, но не забывайте о требованиях, о которых мы говорили выше." -#: ../../config.rst:197 +#: ../../config.rst:242 msgid "Session cache settings" msgstr "Настройки кэша сессий" -#: ../../config.rst:199 +#: ../../config.rst:244 msgid "Section ``[session]``." msgstr "Раздел ``[session]``." -#: ../../config.rst:201 +#: ../../config.rst:246 msgid "" "vstutils-based application store sessions in database_, but for better " "performance, we use a cache-based session backend. It is based on Django " @@ -481,15 +594,15 @@ msgstr "" "группа настроек, аналогичных настройкам cache_. По умолчанию настройки " "получаются из cache_." -#: ../../config.rst:210 +#: ../../config.rst:255 msgid "Rpc settings" msgstr "Настройки RPC" -#: ../../config.rst:212 +#: ../../config.rst:257 msgid "Section ``[rpc]``." msgstr "Раздел ``[rpc]``." -#: ../../config.rst:214 +#: ../../config.rst:259 msgid "" "vstutils-based application uses Celery for long-running async tasks. " "Celery is based on message queue concept, so between web-service and " @@ -508,11 +621,11 @@ msgstr "" "некоторые настройки, используемые для устранения проблем взаимодействия " "сервера, брокера и рабочих процессов." -#: ../../config.rst:222 +#: ../../config.rst:267 msgid "This section require vstutils with `rpc` extra dependency." msgstr "Для этого раздела требуется vstutils с дополнительной зависимостью rpc." -#: ../../config.rst:224 +#: ../../config.rst:269 msgid "" "**connection** - Celery :celery_docs:`broker connection " "`. Default: " @@ -522,13 +635,13 @@ msgstr "" "`. По умолчанию: " "``filesystem:///var/tmp``." -#: ../../config.rst:225 +#: ../../config.rst:270 msgid "**concurrency** - Count of celery worker threads. Default: 4." msgstr "" "**concurrency** - Количество потоков рабочего процесса Celery. По " "умолчанию: 4." -#: ../../config.rst:226 +#: ../../config.rst:271 msgid "" "**heartbeat** - Interval between sending heartbeat packages, which says " "that connection still alive. Default: 10." @@ -536,7 +649,7 @@ msgstr "" "**heartbeat** - Интервал между отправкой пакетов-сигналов, которые " "говорят, что соединение все еще активно. По умолчанию: 10." -#: ../../config.rst:227 +#: ../../config.rst:272 msgid "" "**enable_worker** - Enable or disable worker with webserver. Default: " "true." @@ -544,7 +657,7 @@ msgstr "" "**enable_worker** - Включить или отключить рабочий процесс с " "веб-сервером. По умолчанию: true." -#: ../../config.rst:229 +#: ../../config.rst:274 msgid "" "The following variables from :celery_docs:`Django settings " "` are also supported" @@ -554,61 +667,119 @@ msgstr "" "Django ` (с " "соответствующими типами):" -#: ../../config.rst:232 +#: ../../config.rst:277 msgid "" "**prefetch_multiplier** - :celery_docs:`CELERYD_PREFETCH_MULTIPLIER " "`" msgstr "" -#: ../../config.rst:233 +#: ../../config.rst:278 msgid "" "**max_tasks_per_child** - :celery_docs:`CELERYD_MAX_TASKS_PER_CHILD " "`" msgstr "" -#: ../../config.rst:234 +#: ../../config.rst:279 msgid "" "**results_expiry_days** - :celery_docs:`CELERY_RESULT_EXPIRES " "`" msgstr "" -#: ../../config.rst:235 +#: ../../config.rst:280 msgid "" "**default_delivery_mode** - :celery_docs:`CELERY_DEFAULT_DELIVERY_MODE " "`" msgstr "" -#: ../../config.rst:236 +#: ../../config.rst:281 msgid "" "**task_send_sent_event** - :celery_docs:`CELERY_DEFAULT_DELIVERY_MODE " "`" msgstr "" -#: ../../config.rst:237 +#: ../../config.rst:282 msgid "" "**worker_send_task_events** - :celery_docs:`CELERY_DEFAULT_DELIVERY_MODE " "`" msgstr "" -#: ../../config.rst:243 +#: ../../config.rst:284 +msgid "" +"VST Utils provides seamless support for using Tarantool as a transport " +"for Celery, allowing efficient and reliable message passing between " +"distributed components. To enable this feature, ensure that the Tarantool" +" server has the `queue` module installed." +msgstr "" +"VST Utils так же предоставляет поддержку для использования Tarantool сервера как транспорта " +"в Celery, обеспечивая эффективную и надежную передачу сообщений между распределёнными компонентами. " +"Для использования этой возможности на сервере Tarantool должен быть установлен модуль `queue`." + +#: ../../config.rst:287 +msgid "" +"To configure the connection, use the following example URL: " +"``tarantool://guest@localhost:3301/rpc``" +msgstr "" +"Для настройки подключения используйте в качестве примера следующий URL: " +"``tarantool://guest@localhost:3301/rpc``" + +#: ../../config.rst:289 +msgid "``tarantool://``: Specifies the transport." +msgstr "``tarantool://``: Указывает тип транспорта." + +#: ../../config.rst:290 +msgid "``guest``: Authentication parameters (in this case, no password)." +msgstr "`guest``: Параметры аутентификации (в данном случае без пароля)." + +#: ../../config.rst:291 +msgid "``localhost``: Server address." +msgstr "``localhost``: Адрес сервера." + +#: ../../config.rst:292 +msgid "``3301``: Port for connection." +msgstr "``3301``: Порт для подключения." + +#: ../../config.rst:293 +msgid "``rpc``: Prefix for queue names and/or result storage." +msgstr "``rpc``: Префикс для имён очередей и/или спейса хранилища результата" + +#: ../../config.rst:295 +msgid "" +"VST Utils also supports Tarantool as a backend for storing Celery task " +"results. Connection string is similar to the transport." +msgstr "" +"VST Utils так же поддерживает Tarantool в качестве бэкенда для хранения результатов задач Celery. " +"Строка подключения аналогична как и у транспорта." + +#: ../../config.rst:298 +msgid "" +"When utilizing Tarantool as a result backend or transport in VST Utils, " +"temporary spaces and queues are automatically created to facilitate " +"seamless operation. These temporary spaces are dynamically generated as " +"needed and are essential for storing temporary data efficiently." +msgstr "" +"Когда Tarantool используется для хранения результатов или в качестве транспорта в VST Utils, " +"то автоматически создаются спейсы и очереди для этих операций. " +"Эти временные хранилища создаются динамически по мере необходимости и необходимы для эффективного хранения временных данных." + +#: ../../config.rst:309 msgid "Worker settings" msgstr "Настройки рабочего процесса (worker`a)" -#: ../../config.rst:245 +#: ../../config.rst:311 msgid "Section ``[worker]``." msgstr "Раздел ``[worker]``." -#: ../../config.rst:248 +#: ../../config.rst:314 msgid "These settings are needed only for rpc-enabled applications." msgstr "" "Эти настройки необходимы только для приложений с включенной поддержкой " "RPC." -#: ../../config.rst:250 +#: ../../config.rst:316 msgid "Celery worker options:" msgstr "Настройки рабочего процесса celery:" -#: ../../config.rst:252 +#: ../../config.rst:318 msgid "" "**loglevel** - Celery worker log level. Default: from main_ section " "``log_level``." @@ -616,7 +787,7 @@ msgstr "" "**loglevel** - Уровень логгирования рабочего процесса. По умолчанию: из " "раздела main_ ``log_level``." -#: ../../config.rst:253 +#: ../../config.rst:319 msgid "" "**pidfile** - Celery worker pidfile. Default: " "``/run/{app_name}_worker.pid``" @@ -624,7 +795,7 @@ msgstr "" "**pidfile** - Файл pid для рабочего процесса Celery. по умолчанию: " "``/run/{app_name}_worker.pid``\"" -#: ../../config.rst:254 +#: ../../config.rst:320 msgid "" "**autoscale** - Options for autoscaling. Two comma separated numbers: " "max,min." @@ -632,25 +803,25 @@ msgstr "" "**autoscale** - Параметры для автомасштабирования. Два числа, разделенных" " запятой: максимальное,минимальное." -#: ../../config.rst:255 +#: ../../config.rst:321 msgid "**beat** - Enable or disable celery beat scheduler. Default: ``true``." msgstr "" "**beat** - Включить или отключить планировщик celery beat. По умолчанию: " "``true``." -#: ../../config.rst:257 +#: ../../config.rst:323 msgid "See other settings via ``celery worker --help`` command." msgstr "Другие настройки можно увидеть с помощью команды ``celery worker --help``" -#: ../../config.rst:264 +#: ../../config.rst:330 msgid "SMTP settings" msgstr "SMTP-настройки" -#: ../../config.rst:266 +#: ../../config.rst:332 msgid "Section ``[mail]``." msgstr "Раздел ``[mail]``." -#: ../../config.rst:268 +#: ../../config.rst:334 msgid "" "Django comes with several email sending backends. With the exception of " "the SMTP backend (default when ``host`` is set), these backends are " @@ -661,7 +832,7 @@ msgstr "" "установке ``host``), эти бэкэнды полезны только для тестирования и " "разработки." -#: ../../config.rst:271 +#: ../../config.rst:337 msgid "" "Applications based on vstutils uses only ``smtp`` and ``console`` " "backends." @@ -669,7 +840,7 @@ msgstr "" "Приложения, основанные на vstutils, используют только бэкэнды ``smtp`` и " "``console``." -#: ../../config.rst:273 +#: ../../config.rst:339 msgid "" "**host** - IP or domain for smtp-server. If it not set vstutils uses " "``console`` backends. Default: ``None``." @@ -677,17 +848,17 @@ msgstr "" "**host** - IP-адрес или доменное имя smtp-сервера. Если не указано, " "vstutils использует бэкэнд ``console``. По умолчанию: ``None``." -#: ../../config.rst:274 +#: ../../config.rst:340 msgid "**port** - Port for smtp-server connection. Default: ``25``." msgstr "**port** - Порт для подключения к smtp-серверу. По умолчанию: ``25``." -#: ../../config.rst:275 +#: ../../config.rst:341 msgid "**user** - Username for smtp-server connection. Default: ``\"\"``." msgstr "" "**user** - Имя пользователя для подключения к SMTP-серверу. По умолчанию:" " ``\"\"``." -#: ../../config.rst:276 +#: ../../config.rst:342 msgid "" "**password** - Auth password for smtp-server connection. Default: " "``\"\"``." @@ -695,7 +866,7 @@ msgstr "" "**password** - Пароль для аутентификации на smtp-сервере. По умолчанию: " "``\"\"``." -#: ../../config.rst:277 +#: ../../config.rst:343 msgid "" "**tls** - Enable/disable tls for smtp-server connection. Default: " "``False``." @@ -703,7 +874,7 @@ msgstr "" "**tls** - Включить или отключить TLS для подключения к smtp-серверу. По " "умолчанию: ``False``" -#: ../../config.rst:278 +#: ../../config.rst:344 msgid "" "**send_confirmation** - Enable/disable confirmation message after " "registration. Default: ``False``." @@ -711,7 +882,7 @@ msgstr "" "**send_confirmation** - Включить или отключить отправку сообщения с " "подтверждением после регистрации. По умолчанию: ``False``." -#: ../../config.rst:279 +#: ../../config.rst:345 msgid "" "**authenticate_after_registration** - Enable/disable autologin after " "registration confirmation. Default: ``False``." @@ -720,15 +891,15 @@ msgstr "" "автоматический вход пользователя после подтверждения регистрации. По " "умолчанию: ``False``." -#: ../../config.rst:285 +#: ../../config.rst:351 msgid "Web settings" msgstr "Web-настройки" -#: ../../config.rst:287 +#: ../../config.rst:353 msgid "Section ``[web]``." msgstr "Раздел ``[web]``." -#: ../../config.rst:289 +#: ../../config.rst:355 msgid "" "These settings are related to web-server. Those settings includes: " "session_timeout, static_files_url and pagination limit." @@ -736,13 +907,13 @@ msgstr "" "Эти настройки относятся к веб-серверу. Среди них: session_timeout, " "static_files_url и лимит пагинации." -#: ../../config.rst:292 +#: ../../config.rst:358 msgid "**allow_cors** - enable cross-origin resource sharing. Default: ``False``." msgstr "" "**allow_cors** - включить cross-origin resource sharing. По умолчанию: " "``False``." -#: ../../config.rst:293 +#: ../../config.rst:359 msgid "" "**cors_allowed_origins**, **cors_allowed_origins_regexes**, " "**cors_expose_headers**, **cors_allow_methods**, **cors_allow_headers**, " @@ -756,7 +927,7 @@ msgstr "" "/django-cors-headers#configuration>`_ из библиотеки ``django-cors-" "headers`` со значениями по умолчанию." -#: ../../config.rst:296 +#: ../../config.rst:362 msgid "" "**enable_gravatar** - Enable/disable gravatar service using for users. " "Default: ``True``." @@ -764,7 +935,7 @@ msgstr "" "**enable_gravatar** - Включить/отключить использование сервиса Gravatar " "для пользователей. По умолчанию: ``True``." -#: ../../config.rst:297 +#: ../../config.rst:363 msgid "" "**rest_swagger_description** - Help string in Swagger schema. Useful for " "dev-integrations." @@ -772,7 +943,7 @@ msgstr "" "**rest_swagger_description** - Строка справки в схеме Swagger. Полезно " "для разработки интеграций." -#: ../../config.rst:298 +#: ../../config.rst:364 msgid "" "**openapi_cache_timeout** - Cache timeout for storing schema data. " "Default: ``120``." @@ -780,7 +951,7 @@ msgstr "" "**openapi_cache_timeout** - Время кэширования данных схемы. По умолчанию:" " ``120``." -#: ../../config.rst:299 +#: ../../config.rst:365 msgid "" "**health_throttle_rate** - Count of requests to ``/api/health/`` " "endpoint. Default: ``60``." @@ -788,7 +959,7 @@ msgstr "" "Количество запросов к конечной точке ``/api/health/``. По умолчанию: " "``60``." -#: ../../config.rst:300 +#: ../../config.rst:366 msgid "" "**bulk_threads** - Threads count for PATCH ``/api/endpoint/`` endpoint. " "Default: ``3``." @@ -796,13 +967,13 @@ msgstr "" "**bulk_threads** - Количество потоков для PATCH ``/api/endpoint/``. По " "умолчанию: ``3``." -#: ../../config.rst:301 +#: ../../config.rst:367 msgid "**session_timeout** - Session lifetime. Default: ``2w`` (two weeks)." msgstr "" "**session_timeout** - Время жизни сессии. По умолчанию: ``2w`` (две " "недели)." -#: ../../config.rst:302 +#: ../../config.rst:368 msgid "" "**etag_default_timeout** - Cache timeout for Etag headers to control " "models caching. Default: ``1d`` (one day)." @@ -810,7 +981,7 @@ msgstr "" "**etag_default_timeout** - Время кэширования заголовков Etag для " "управления кэшированием моделей. По умолчанию: ``1d`` (один день)." -#: ../../config.rst:303 +#: ../../config.rst:369 msgid "" "**rest_page_limit** and **page_limit** - Default limit of objects in API " "list. Default: ``1000``." @@ -818,7 +989,7 @@ msgstr "" "**rest_page_limit** and **page_limit** - Максимальное количество объектов" " в списке API. По умолчанию: ``1000``." -#: ../../config.rst:304 +#: ../../config.rst:370 msgid "" "**session_cookie_domain** - The domain to use for session cookies. Read " ":django_docs:`more `. " @@ -828,7 +999,7 @@ msgstr "" ":django_docs:`Подробнее `. " "По умолчанию: ``None``." -#: ../../config.rst:306 +#: ../../config.rst:372 msgid "" "**csrf_trusted_origins** - A list of hosts which are trusted origins for " "unsafe requests. Read :django_docs:`more `. По " "умолчанию: значение из **session_cookie_domain**." -#: ../../config.rst:308 +#: ../../config.rst:374 msgid "" "**case_sensitive_api_filter** - Enables/disables case sensitive search " "for name filtering. Default: ``True``." @@ -846,7 +1017,7 @@ msgstr "" "**case_sensitive_api_filter** - Включить/отключить чувствительность к " "регистру при фильтрации по имени. По умолчанию: ``True``." -#: ../../config.rst:310 +#: ../../config.rst:376 msgid "" "**secure_proxy_ssl_header_name** - Header name which activates SSL urls " "in responses. Read :django_docs:`more `. По умолчанию: " "``HTTP_X_FORWARDED_PROTOCOL``." -#: ../../config.rst:312 +#: ../../config.rst:378 msgid "" "**secure_proxy_ssl_header_value** - Header value which activates SSL urls" " in responses. Read :django_docs:`more `. По " "умолчанию: ``https``." -#: ../../config.rst:316 +#: ../../config.rst:382 msgid "" "The following variables from Django settings are also supported (with the" " corresponding types):" @@ -876,70 +1047,70 @@ msgstr "" "Также поддерживаются следующие переменные из настроек Django (с " "соответствующими типами):" -#: ../../config.rst:318 +#: ../../config.rst:384 msgid "" "**secure_browser_xss_filter** - :django_docs:`SECURE_BROWSER_XSS_FILTER " "`" msgstr "" -#: ../../config.rst:319 +#: ../../config.rst:385 msgid "" "**secure_content_type_nosniff** - " ":django_docs:`SECURE_CONTENT_TYPE_NOSNIFF `" msgstr "" -#: ../../config.rst:320 +#: ../../config.rst:386 msgid "" "**secure_hsts_include_subdomains** - " ":django_docs:`SECURE_HSTS_INCLUDE_SUBDOMAINS `" msgstr "" -#: ../../config.rst:321 +#: ../../config.rst:387 msgid "" "**secure_hsts_preload** - :django_docs:`SECURE_HSTS_PRELOAD `" msgstr "" -#: ../../config.rst:322 +#: ../../config.rst:388 msgid "" "**secure_hsts_seconds** - :django_docs:`SECURE_HSTS_SECONDS `" msgstr "" -#: ../../config.rst:323 +#: ../../config.rst:389 msgid "" "**password_reset_timeout_days** - " ":django_docs:`PASSWORD_RESET_TIMEOUT_DAYS `" msgstr "" -#: ../../config.rst:324 +#: ../../config.rst:390 msgid "" "**request_max_size** - :django_docs:`DATA_UPLOAD_MAX_MEMORY_SIZE " "`" msgstr "" -#: ../../config.rst:325 +#: ../../config.rst:391 msgid "" "**x_frame_options** - :django_docs:`X_FRAME_OPTIONS `" msgstr "" -#: ../../config.rst:326 +#: ../../config.rst:392 msgid "" "**use_x_forwarded_host** - :django_docs:`USE_X_FORWARDED_HOST " "`" msgstr "" -#: ../../config.rst:327 +#: ../../config.rst:393 msgid "" "**use_x_forwarded_port** - :django_docs:`USE_X_FORWARDED_PORT " "`" msgstr "" -#: ../../config.rst:329 +#: ../../config.rst:395 msgid "" "The following settings affects prometheus metrics endpoint (which can be " "used for monitoring application):" @@ -947,7 +1118,7 @@ msgstr "" "Следующие настройки влияют на эндпоинт метрик Prometheus (который может " "использоваться для мониторинга приложения):" -#: ../../config.rst:331 +#: ../../config.rst:397 msgid "" "**metrics_throttle_rate** - Count of requests to ``/api/metrics/`` " "endpoint. Default: ``120``." @@ -955,7 +1126,7 @@ msgstr "" "**metrics_throttle_rate** - Количество запросов к эндпоинту " "``/api/metrics/``. По умолчанию: ``120``." -#: ../../config.rst:332 +#: ../../config.rst:398 msgid "" "**enable_metrics** - Enable/disable ``/api/metrics/`` endpoint for app. " "Default: ``true``" @@ -963,7 +1134,7 @@ msgstr "" "**enable_metrics** - Включить/отключить эндпоинт ``/api/metrics/`` для " "приложения. По умолчанию: ``true``." -#: ../../config.rst:333 +#: ../../config.rst:399 msgid "" "**metrics_backend** - Python class path with metrics collector backend. " "Default: ``vstutils.api.metrics.DefaultBackend`` Default backend collects" @@ -973,11 +1144,11 @@ msgstr "" " умолчанию: ``vstutils.api.metrics.DefaultBackend``. Стандартный бэкенд " "собирает метрики из рабочих процессов uwsgi и информацию о версии Python." -#: ../../config.rst:337 +#: ../../config.rst:403 msgid "Section ``[uvicorn]``." msgstr "Раздел ``[uvicorn]``." -#: ../../config.rst:339 +#: ../../config.rst:405 msgid "" "You can configure the necessary settings to run the uvicorn server. " "``vstutils`` supports almost all options from the cli, except for those " @@ -987,21 +1158,21 @@ msgstr "" "``vstutils`` поддерживает практически все опции из командной строки, за " "исключением тех, которые настраивают приложение и соединение." -#: ../../config.rst:342 +#: ../../config.rst:408 msgid "See all available uvicorn settings via ``uvicorn --help`` command." msgstr "" "Вы можете посмотреть все доступные настройки uvicorn, введя команду " "``uvicorn --help``" -#: ../../config.rst:347 +#: ../../config.rst:413 msgid "Centrifugo client settings" msgstr "Настройки клиента Centrifugo" -#: ../../config.rst:349 +#: ../../config.rst:415 msgid "Section ``[centrifugo]``." msgstr "Раздел ``[centrifugo]``." -#: ../../config.rst:351 +#: ../../config.rst:417 msgid "" "To install app with centrifugo client, ``[centrifugo]`` section must be " "set. Centrifugo is used by application to auto-update page data. When " @@ -1018,27 +1189,27 @@ msgstr "" "службы все клиенты GUI получают данные страницы каждые 5 секунд (по " "умолчанию)." -#: ../../config.rst:357 +#: ../../config.rst:423 msgid "**address** - Centrifugo server address." msgstr "**address** - Адрес сервера Centrifugo." -#: ../../config.rst:358 +#: ../../config.rst:424 msgid "**api_key** - API key for clients." msgstr "**api_key** - Ключ API для клиентов." -#: ../../config.rst:359 +#: ../../config.rst:425 msgid "**token_hmac_secret_key** - API key for jwt-token generation." msgstr "**token_hmac_secret_key** - Ключ API для генерации JWT-токена." -#: ../../config.rst:360 +#: ../../config.rst:426 msgid "**timeout** - Connection timeout." msgstr "**timeout** - Таймаут подключения." -#: ../../config.rst:361 +#: ../../config.rst:427 msgid "**verify** - Connection verification." msgstr "**verify** - Проверка подключения." -#: ../../config.rst:362 +#: ../../config.rst:428 msgid "" "**subscriptions_prefix** - Prefix used for generating update channels, by" " default \"{VST_PROJECT}.update\"." @@ -1046,7 +1217,7 @@ msgstr "" "**subscriptions_prefix** - Префикс, используемый для генерации каналов " "обновления, по умолчанию \"{VST_PROJECT}.update\"." -#: ../../config.rst:365 +#: ../../config.rst:431 msgid "" "These settings also add parameters to the OpenAPI schema and change how " "the auto-update system works in the GUI. ``token_hmac_secret_key`` is " @@ -1058,15 +1229,15 @@ msgstr "" "генерации JWT-токена (на основе времени истечения сессии). Токен будет " "использоваться для клиента Centrifugo-JS." -#: ../../config.rst:373 +#: ../../config.rst:439 msgid "Storage settings" msgstr "Настройки хранилища" -#: ../../config.rst:375 +#: ../../config.rst:441 msgid "Section ``[storages]``." msgstr "Раздел ``[storages]``." -#: ../../config.rst:377 +#: ../../config.rst:443 msgid "" "Applications based on ``vstutils`` supports filesystem storage out of " "box. Setup ``media_root`` and ``media_url`` in ``[storages.filesystem]`` " @@ -1079,7 +1250,7 @@ msgstr "" "``media_url`` в разделе [storages.filesystem]. По умолчанию они будут " "равны ``{/path/to/project/module}/media`` и ``/media/``." -#: ../../config.rst:382 +#: ../../config.rst:448 msgid "" "Applications based on ``vstutils`` supports store files in external " "services with `Apache Libcloud `_ and `Boto3" @@ -1090,7 +1261,7 @@ msgstr "" "`_ и `Boto3 " "`_." -#: ../../config.rst:385 +#: ../../config.rst:451 msgid "" "Apache Libcloud settings grouped by sections named " "``[storages.libcloud.provider]``, where ``provider`` is name of storage. " @@ -1107,7 +1278,7 @@ msgstr "" "storages.readthedocs.io/en/latest/backends/apache_libcloud.html#libcloud-" "providers>`_." -#: ../../config.rst:390 +#: ../../config.rst:456 msgid "" "This setting is required to configure connections to cloud storage " "providers. Each entry corresponds to a single ‘bucket’ of storage. You " @@ -1120,7 +1291,7 @@ msgstr "" "(например, несколько buckets S3), и вы можете определить buckets в " "нескольких провайдерах." -#: ../../config.rst:395 +#: ../../config.rst:461 msgid "" "For ``Boto3`` all settings grouped by section named ``[storages.boto3]``." " Section must contain following keys: ``access_key_id``, " @@ -1135,7 +1306,7 @@ msgstr "" "amazon-S3 `_." -#: ../../config.rst:400 +#: ../../config.rst:466 msgid "" "Storage has following priority to choose storage engine if multiple was " "provided:" @@ -1143,19 +1314,19 @@ msgstr "" "При выборе движка хранилища используется следующий приоритет, если их " "было предоставлено несколько:" -#: ../../config.rst:402 +#: ../../config.rst:468 msgid "Libcloud store when config contains this section." msgstr "Хранилище Libcloud, когда конфигурация содержит этот раздел." -#: ../../config.rst:404 +#: ../../config.rst:470 msgid "Boto3 store, when you have section and has all required keys." msgstr "Хранилище Boto3, когда у вас есть раздел и имеются все необходимые ключи." -#: ../../config.rst:406 +#: ../../config.rst:472 msgid "FileSystem store otherwise." msgstr "В противном случае хранилище FileSystem." -#: ../../config.rst:408 +#: ../../config.rst:474 msgid "" "Once you have defined your Libcloud providers, you have an option of " "setting one provider as the default provider of Libcloud storage. You can" @@ -1168,7 +1339,7 @@ msgstr "" "``[storages.libcloud.default]`` или же vstutils установит первое " "хранилище, как хранилище по умолчанию." -#: ../../config.rst:413 +#: ../../config.rst:479 msgid "" "If you configure default libcloud provider, vstutils will use it as " "global file storage. To override it set " @@ -1189,7 +1360,7 @@ msgstr "" "``default=storages.backends.apache_libcloud.LibCloudStorage`` в разделе " "``[storages]`` и используйте провайдера Libcloud, как по умолчанию." -#: ../../config.rst:421 +#: ../../config.rst:487 msgid "" "Here is example for boto3 connection to minio cluster with public read " "permissions, external proxy domain and internal connection support:" @@ -1197,15 +1368,15 @@ msgstr "" "Вот пример подключения boto3 к кластеру minio с публичными правами на " "чтение, внешним доменом прокси и поддержкой внутреннего подключения:" -#: ../../config.rst:450 +#: ../../config.rst:516 msgid "Throttle settings" msgstr "Настройки Throttle" -#: ../../config.rst:452 +#: ../../config.rst:518 msgid "Section ``[throttle]``." msgstr "Раздел ``[throttle]``." -#: ../../config.rst:454 +#: ../../config.rst:520 msgid "" "By including this section to your config, you can setup global and per-" "view throttle rates. Global throttle rates are specified under root " @@ -1218,13 +1389,13 @@ msgstr "" "индивидуальные throttle rates для конкретного View, вам нужно добавить " "дочернюю секцию." -#: ../../config.rst:458 +#: ../../config.rst:524 msgid "For example, if you want to apply throttle to ``api/v1/author``:" msgstr "" "Например, если вы хотите применить ограничение количества запросов для " "``api/v1/author``:" -#: ../../config.rst:466 +#: ../../config.rst:532 msgid "" "**rate** - Throttle rate in format number_of_requests/time_period. " "Expected time_periods: second/minute/hour/day." @@ -1233,7 +1404,7 @@ msgstr "" "number_of_requests/time_period. Expected time_periods: " "second/minute/hour/day." -#: ../../config.rst:467 +#: ../../config.rst:533 msgid "" "**actions** - Comma separated list of drf actions. Throttle will be " "applied only on specified here actions. Default: update, partial_update." @@ -1242,7 +1413,7 @@ msgstr "" "количества запросов будет применяться только к указанным здесь действиям." " По умолчанию: update, partial_update." -#: ../../config.rst:469 +#: ../../config.rst:535 msgid "" "More on throttling at `DRF Throttle docs `_." @@ -1250,15 +1421,15 @@ msgstr "" "Подробнее об ограничении количества запросов в `документации DRF Throttle" " `_." -#: ../../config.rst:473 +#: ../../config.rst:539 msgid "Production web settings" msgstr "Настройки для продакшн-сервера" -#: ../../config.rst:475 +#: ../../config.rst:541 msgid "Section ``[uwsgi]``." msgstr "Раздел ``[uwsgi]``." -#: ../../config.rst:477 +#: ../../config.rst:543 msgid "" "Settings related to web-server used by vstutils-based application in " "production (for deb and rpm packages by default). Most of them related to" @@ -1271,7 +1442,7 @@ msgstr "" "т. д.). Дополнительные настройки смотрите в `документации uWSGI. `_." -#: ../../config.rst:483 +#: ../../config.rst:549 msgid "" "But keep in mind that uWSGI is deprecated and may be removed in future " "releases. Use the uvicorn settings to manage your app server." @@ -1280,11 +1451,11 @@ msgstr "" "версиях. Используйте настройки uvicorn для управления сервером вашего " "приложения." -#: ../../config.rst:488 +#: ../../config.rst:554 msgid "Configuration options" msgstr "Параметры конфигурации" -#: ../../config.rst:490 +#: ../../config.rst:556 msgid "" "This section contains additional information for configure additional " "elements." @@ -1292,7 +1463,7 @@ msgstr "" "В этом разделе содержится дополнительная информация для настройки " "дополнительных элементов." -#: ../../config.rst:492 +#: ../../config.rst:558 msgid "" "If you need set ``https`` for your web settings, you can do it using " "HAProxy, Nginx, Traefik or configure it in ``settings.ini``." @@ -1301,7 +1472,7 @@ msgstr "" " сделать это с помощью HAProxy, Nginx, Traefik или настроить в файле " "``settings.ini``." -#: ../../config.rst:504 +#: ../../config.rst:570 msgid "" "We strictly do not recommend running the web server from root. Use HTTP " "proxy to run on privileged ports." @@ -1309,7 +1480,7 @@ msgstr "" "Мы настоятельно не рекомендуем запускать веб-сервер от имени root. " "Используйте HTTP-прокси, чтобы работать на привилегированных портах." -#: ../../config.rst:506 +#: ../../config.rst:572 msgid "" "You can use `{ENV[HOME:-value]}` (where `HOME` is environment variable, " "`value` is default value) in configuration values." @@ -1317,7 +1488,7 @@ msgstr "" "Вы можете использовать `{ENV[HOME:-value]}` (где `HOME` - переменная " "окружения, `value` - значение по умолчанию) в значениях конфигурации." -#: ../../config.rst:509 +#: ../../config.rst:575 msgid "" "You can use environment variables for setup important settings. But " "config variables has more priority then env. Available settings are: " @@ -1330,7 +1501,7 @@ msgstr "" "``DJANGO_LOG_LEVEL``, ``TIMEZONE`` и некоторые настройки с префиксом " "``[ENV_NAME]``." -#: ../../config.rst:512 +#: ../../config.rst:578 msgid "" "For project without special settings and project levels named ``project``" " these variables will start with ``PROJECT_`` prefix. There is a list of " @@ -1354,7 +1525,7 @@ msgstr "" "``{ENV_NAME}_GLOBAL_THROTTLE_RATE``, и " "``{ENV_NAME}_GLOBAL_THROTTLE_ACTIONS``." -#: ../../config.rst:519 +#: ../../config.rst:585 msgid "" "There are also URI-specific variables for connecting to various services " "such as databases and caches. There are ``DATABASE_URL``, ``CACHE_URL``, " @@ -1368,10 +1539,11 @@ msgstr "" "``SESSIONS_CACHE_URL`` и ``ETAG_CACHE_URL``. Как видно из названий, они " "тесно связаны с ключами и именами соответствующих секций конфигурации." -#: ../../config.rst:523 +#: ../../config.rst:589 msgid "" "We recommend to install ``uvloop`` to your environment and setup ``loop =" " uvloop`` in ``[uvicorn]`` section for performance reasons." msgstr "" "Мы рекомендуем установить ``uvloop`` в ваше окружение и настроить ``loop " "= uvloop`` в разделе ``[uvicorn]`` для повышения производительности." + diff --git a/pyproject.toml b/pyproject.toml index 60964d79..fe71aa22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,6 +98,7 @@ omit = [ '*/vstutils/compile.py', '*/vstutils/api/admin.py', '*/vstutils/api/doc_generator.py', + '*/vstutils/drivers/*', ] [tool.coverage.report] @@ -154,5 +155,7 @@ module = [ "ruamel.*", "mysql.*", "configparserc.*", + "kombu.*", + "tarantool.*", ] ignore_missing_imports = true diff --git a/requirements-prod.txt b/requirements-prod.txt index 709970d6..afbd64db 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -3,3 +3,4 @@ # mysql-connector-python==8.0.15; python_version>'3.4' # Advanced cache support redis[hiredis]~=5.0.1 +tarantool~=1.1.2 diff --git a/requirements-stubs.txt b/requirements-stubs.txt index 2305cace..c06820e6 100644 --- a/requirements-stubs.txt +++ b/requirements-stubs.txt @@ -7,4 +7,4 @@ types-PyMySQL==1.1.0.1 types-Markdown==3.5.0.3 types-docutils==0.20.0.3 types-aiofiles~=23.2.0.0 -typing-extensions~=4.8.0 +typing-extensions~=4.9.0 diff --git a/requirements.txt b/requirements.txt index 48cf2526..38306b7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ pyyaml~=6.0.1 # web server uvicorn~=0.24.0.post1 uwsgi==2.0.23 -fastapi~=0.104.1 +fastapi~=0.105.0 aiofiles==23.2.1 # Notifications diff --git a/vstutils/drivers/__init__.py b/vstutils/drivers/__init__.py new file mode 100644 index 00000000..01be5e23 --- /dev/null +++ b/vstutils/drivers/__init__.py @@ -0,0 +1,7 @@ +from kombu.transport import TRANSPORT_ALIASES +from celery.app.backends import BACKEND_ALIASES + +from . import kombu + +TRANSPORT_ALIASES['tarantool'] = 'vstutils.drivers.kombu:TarantoolTransport' +BACKEND_ALIASES['tarantool'] = 'vstutils.drivers.kombu:TarantoolBackend' diff --git a/vstutils/drivers/cache.py b/vstutils/drivers/cache.py new file mode 100644 index 00000000..12483310 --- /dev/null +++ b/vstutils/drivers/cache.py @@ -0,0 +1,203 @@ +import time + +import tarantool +from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT +from django.core.cache.backends.redis import RedisSerializer as Serializer # type: ignore[import-untyped] +from django.utils.functional import cached_property + + +class TarantoolCache(BaseCache): + def __init__(self, servers, params): + super().__init__(params) + self._serializer = Serializer() + self._servers = servers + self._options = params.get("OPTIONS", {}) + self._space_name = self._options.get('space', 'DJANGO_CACHE').upper() + if self._options.get('connect_on_start', True): + self.start_hook() + + def close(self, **kwargs): + if not self.client.is_closed(): + self.client.close() + + def start_hook(self): + self.client.eval( + "box.schema.space.create(" + f"'{self._space_name}', " # noqa: E131 + "{" + "if_not_exists = true, " # noqa: E131 + "temporary = true, " + "format = {" + "{name = 'id', type = 'string'}, " # noqa: E131 + "{name = 'value', type = 'any'}, " + "{name = 'exp', type = 'number'}, " + "} " + "}" + ")" + ) + self.client.eval( + f"box.space.{self._space_name}:create_index('primary', {{ parts = {{ 'id' }}, if_not_exists = true }})" + ) + lower_name = self._space_name.lower() + self.client.eval( + f""" + if {lower_name}_is_expired then return 0 end + + clock = require('clock') + expirationd = require("expirationd") + + function {lower_name}_is_expired(args, tuple) + return tuple[3] > -1 and tuple[3] < clock.realtime() + end + + function {lower_name}_delete_tuple(space, args, tuple) + box.space[space]:delete{{tuple[1]}} + end + + expirationd.start("{lower_name}_clean_cache", '{self._space_name}', {lower_name}_is_expired, {{ + process_expired_tuple = {lower_name}_delete_tuple, + args = nil, + tuples_per_iteration = 50, + full_scan_time = 3600 + }}) + """ + ) + + @cached_property + def client(self): + host, _, port = self._servers.rpartition(':') + user = self._options.get('user', 'guest') + password = self._options.get('password') + client = tarantool.connect(host, int(port), user, password) + return client + + @cached_property + def space(self): + return self.client.space(self._space_name) + + def space_eval(self, evaluation_string: str): + return self.client.eval(f'return box.space.{self._space_name}:{evaluation_string}') + + def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT): + if timeout == DEFAULT_TIMEOUT: + timeout = self.default_timeout + # The key will be made persistent if None used as a timeout. + # Non-positive values will cause the key to be deleted. + return -1 if timeout is None else max(0, int(timeout)) + + def _build_tuple(self, key: str, value, timeout=DEFAULT_TIMEOUT, version=None): + key = self.make_and_validate_key(key, version=version) + value = self._serializer.dumps(value) + timeout = self.get_backend_timeout(timeout=timeout) + if timeout != -1: + timeout = int(time.time() + timeout + 0.5) + + return key, value, timeout + + def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None): + key, value, timeout = self._build_tuple(key, value, timeout, version) + + try: + key, value, _ = self.space.insert((key, value, timeout)).data[0] + except tarantool.error.DatabaseError: + return False + if timeout == 0: + self.space.delete(key) + + return True + + def get(self, key, default=None, version=None): + key = self.make_and_validate_key(key, version=version) + data = self.space.select(key).data + if not data: + return default + if data[0][2] < time.time(): + self.space.delete(key) + return default + return self._serializer.loads(data[0][1]) + + def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None): + self.space.replace(self._build_tuple(key, value, timeout, version)) + + def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None): + key = self.make_and_validate_key(key, version=version) + timeout = self.get_backend_timeout(timeout=timeout) + if timeout != -1: + timeout = int(time.time() + timeout + 0.5) + + try: + self.space.update(key, [('=', 'exp', timeout)]) + return True + except tarantool.error.DatabaseError: + return False + + def delete(self, key, version=None): + key = self.make_and_validate_key(key, version=version) + _, value, _ = self.space.delete(key).data + return self._serializer.loads(value) + + def has_key(self, key, version=None): + key = self.make_and_validate_key(key, version=version) + return bool(self.space.select(key).data) + + def incr(self, key, delta=1, version=None): + key = self.make_and_validate_key(key, version=version) + if delta > 0: + operation = '+' + else: + operation = '-' + delta = -delta + value = self._serializer.dumps(delta) + data = self.space.update(key, [(operation, 'value', value)]).data + if not data: + raise ValueError(f"Key '{key}' not found.") + return data[0][1] + + def get_many(self, keys, version=None): + key_map = { + self.make_and_validate_key(key, version=version): key for key in keys + } + keys_list = [f"'{k}'" for k in key_map.keys()] + data = self.client.execute( + f'SELECT "id", "value" ' # nosec B608 + f'FROM "{self._space_name}" ' # nosec B608 + f'WHERE "id" IN ({", ".join(keys_list)}) AND "exp" >= {int(time.time())}' # nosec B608 + ).data + return { + key_map[k]: self._serializer.loads(v) + for k, v in data + } + + def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None): + # pylint: disable=unidiomatic-typecheck + + if not data: + return [] + + safe_data = {} + for key, value in data.items(): + key = f"'{self.make_and_validate_key(key, version=version)}'" + quote = "'" if not type(value) is int else "" + safe_data[key] = f"{quote}{self._serializer.dumps(value)}{quote}" + + timeout = self.get_backend_timeout(timeout=timeout) + if timeout != -1: + timeout = int(time.time() + timeout + 0.5) + + self.client.eval("\n".join([ + f'box.space.{self._space_name}:put{{ {k}, {v}, {timeout} }}' + for k, v in safe_data.items() + ])) + + return [] + + def delete_many(self, keys, version=None): + if not keys: + return + safe_keys = [f"'{self.make_and_validate_key(key, version=version)}'" for key in keys] + self.client.execute( + f'DELETE FROM "{self._space_name}" WHERE "id" IN ({", ".join(safe_keys)})' # nosec B608 + ) + + def clear(self): + return self.space_eval('truncate()') diff --git a/vstutils/drivers/kombu.py b/vstutils/drivers/kombu.py new file mode 100644 index 00000000..0d8a9d40 --- /dev/null +++ b/vstutils/drivers/kombu.py @@ -0,0 +1,261 @@ +import base64 +import logging +from queue import Empty + +import tarantool +from ormsgpack import packb, unpackb +from kombu.transport import virtual, base +from kombu.utils.url import url_to_parts as parse_url +from celery.backends.base import KeyValueStoreBackend + +logger = logging.getLogger('kombu.transport.tarantool') + + +class TarantoolMessage(virtual.Message): + """ + Represents a message in the Tarantool transport. + """ + + def __init__(self, payload, channel=None, **kwargs): + super().__init__(payload, channel, **kwargs) + self.tarantool_queue_id = payload.pop('tarantool_queue_id') + + +class TarantoolQoS(virtual.QoS): + """ + Provides Quality of Service (QoS) features for Tarantool. + """ + + def get_tarantool_queue_id(self, delivery_tag): + """ + Gets the Tarantool queue name and message ID from the delivery tag. + + :param delivery_tag: The delivery tag. + :return: Tuple with the Tarantool queue name and message ID. + """ + msg = self.get(delivery_tag) + return msg.delivery_info['routing_key'], msg.tarantool_queue_id + + def ack(self, delivery_tag): + """ + Acknowledges a message. + + :param delivery_tag: The delivery tag. + """ + queue, tarantool_id = self.get_tarantool_queue_id(delivery_tag) + self.channel.client_eval(queue, f'ack({tarantool_id})') + super().ack(delivery_tag) + + def reject(self, delivery_tag, requeue=False): + """ + Rejects a message. + + :param delivery_tag: The delivery tag. + :param requeue: Whether to requeue the message. + """ + if requeue: + operation = 'release' + else: + operation = 'delete' + queue, tarantool_id = self.get_tarantool_queue_id(delivery_tag) + self.channel.client_eval(queue, f'{operation}({tarantool_id})') + self._quick_ack(delivery_tag) + + +class TarantoolChannel(virtual.Channel): + # pylint: disable=abstract-method + """ + Represents a channel for communication using the Tarantool transport. + """ + + QoS = TarantoolQoS + Message = TarantoolMessage + + def __init__(self, connection, **kwargs): + super().__init__(connection, **kwargs) + conninfo = connection.client + self.client = tarantool.connect( + host=conninfo.hostname or 'localhost', + port=conninfo.port or self.connection.default_port, + user=conninfo.userid, + password=conninfo.password, + ) + self.client.eval("queue = require 'queue'") + self.prefix = f'{conninfo.virtual_host}_celery_queue_'.replace('/', '_').replace('__', '_') + + def client_eval(self, queue: str, exec_code: str, should_return: bool = True): + """ + Evaluates a Tarantool Lua method of queue. + See `usage diagram `_ for details. + + :param queue: The Tarantool queue name. + :param exec_code: The method of queue to execute. + :param should_return: Whether the method should return a value. + :return: The result of the script execution. + """ + command = f'{"return" if should_return else ""} queue.tube.{self.prefix}{queue}:{exec_code}' + logger.debug(f'Call tarantool command: {command}') + return self.client.eval(command) + + def _get(self, queue, timeout=None): + """ Get next message from `queue`. """ + + result = self.client_eval(queue, f'take({timeout or 0})').data + if not result: + raise Empty() + delivery_tag, _, payload = result[0] + payload = unpackb(base64.b64decode(payload)) + payload['tarantool_queue_id'] = delivery_tag + return payload + + def _put(self, queue, message, **kwargs): + """ Put `message` onto `queue`. """ + + self.client_eval(queue, f"put('{base64.b64encode(packb(message)).decode('utf-8')}')") + + def _purge(self, queue): + """ Remove all messages from `queue`. """ + + self.client_eval(queue, 'truncate()', should_return=False) + + def _size(self, queue): + """ Return the number of messages in `queue` as an :class:`int`. """ + + try: + return self.client.eval(f"queue.statistics({self.prefix}{queue}).tasks.total").data[0] + except tarantool.error.DatabaseError: + return 0 + + def _delete(self, queue, *args, **kwargs): + """ Delete `queue` """ + self.client_eval(queue, 'drop()') + + def _new_queue(self, queue, **kwargs): + """ Create new queue. """ + self.client.eval( + "queue.create_tube('" + self.prefix + str(queue) + "', 'fifottl', {temporary = true, if_not_exists = true})" + ) + + def _has_queue(self, queue, **kwargs): + """ Verify that queue exists. """ + try: + self.client.eval(f"return queue.tube.{self.prefix}{queue}.name") + return True + except tarantool.error.DatabaseError: + return False + + def close(self): + """ + Close channel. + + Cancel all consumers, and requeue unacked messages. + """ + + super().close() + self.client.close() + + +class TarantoolTransport(virtual.Transport): + # pylint: disable=abstract-method + """ + Implements the Tarantool transport for Celery. + + Require `queue module `_ to be installed on the tarantool server. + """ + + Channel = TarantoolChannel + can_parse_url = False + default_port = 3301 + driver_type = 'tarantool' + driver_name = 'tarantool' + connection_errors = (tarantool.error.Error,) + implements = base.Transport.implements.extend( + asynchronous=False, + exchange_type=frozenset(['topic']), + heartbeats=False, + ) + + def driver_version(self): + """ + Gets the version of the Tarantool driver. + + :return: The version of the Tarantool driver. + """ + return tarantool.__version__ + + +class TarantoolBackend(KeyValueStoreBackend): + # pylint: disable=abstract-method + """ + Implements the backend for storing results in Tarantool. + """ + + supports_native_join = True + persistent = False + + def __init__(self, url=None, *args, **kwargs): + super().__init__(*args, **kwargs) + _, host, port, username, password, vhost, _ = parse_url(url) + self.client = tarantool.connect(host, port, user=username, password=password) + self.space_name = f"{vhost + '_' if vhost else ''}celery_backend" + self.client.eval( + "box.schema.space.create(" + f"'{self.space_name}', " # noqa: E131 + "{" + "if_not_exists = true, " # noqa: E131 + "temporary = true, " + "format = {" + "{name = 'id', type = 'string'}, " # noqa: E131 + "{name = 'value', type = 'string'}, " + "} " + "}" + ")" + ) + self.client.eval( + f"box.space.{self.space_name}:create_index('primary', {{ parts = {{ 'id' }}, if_not_exists = true }})" + ) + self.space = self.client.space(self.space_name) + + def get(self, key): + """ + Gets the value associated with the given key. + + :param key: The key for the value. + :return: The value associated with the key, or None if the key is not found. + """ + + data = self.space.select(key.decode('utf-8')).data + if not data: + return + return data[0][1] + + def mget(self, keys): + """ + Gets the values associated with the given key's array. + """ + keys = (f"'{k.decode('utf-8')}'" for k in keys) + + data = self.client.execute( + f'select * from "{self.space_name}" where "id" in ({", ".join(keys)})' # nosec B608 + ).data + if not data: + return [] + return dict(data) + + def set(self, key, value): + """ + Sets the value associated with the given key. + + :param key: The key for the value. + :param value: The value to be stored. + """ + + self.space.replace((key.decode('utf-8'), value)) + + def delete(self, key): + """ + Deletes the value associated with the given key. + + :param key: The key for the value to be deleted. + """ + self.space.delete(key.decode('utf-8')) diff --git a/vstutils/environment.py b/vstutils/environment.py index 742d8a7e..5c2ad137 100644 --- a/vstutils/environment.py +++ b/vstutils/environment.py @@ -47,7 +47,7 @@ def cmd_execution(*args, **kwargs): def get_celery_app(name=None, **kwargs): # nocv - # pylint: disable=import-error + # pylint: disable=import-error,unused-import """ Function to return celery-app. Works only if celery installed. :param name: Application name @@ -57,6 +57,8 @@ def get_celery_app(name=None, **kwargs): # nocv from celery import Celery from kombu.serialization import register as serialize_registrator from .api.renderers import ORJSONRenderer + from . import drivers # noqa: F401 + prepare_environment(**kwargs) name = name or os.getenv("VST_PROJECT") diff --git a/vstutils/settings.py b/vstutils/settings.py index 3955f520..7b2aacd7 100644 --- a/vstutils/settings.py +++ b/vstutils/settings.py @@ -272,6 +272,7 @@ class CacheSection(BackendSection): class CacheOptionsSection(BackendSection): parent: BaseAppendSection types_map = { + 'connect_on_start': ConfigBoolType, 'binary': ConfigBoolType, 'no_delay': ConfigBoolType, 'ignore_exc': ConfigBoolType, From 02733dbdb16521d40d836077c488211101abadf9 Mon Sep 17 00:00:00 2001 From: Dmitriy Ovcharenko Date: Tue, 19 Dec 2023 15:38:29 +1000 Subject: [PATCH 04/20] Feature(frontend): Stepped views --- doc/backend.rst | 2 +- frontend_src/vstutils/AppConfiguration.ts | 3 + .../vstutils/components/page/DefaultView.vue | 48 ++++++ .../vstutils/components/page/OneEntity.vue | 48 +----- .../components/page/tabbed/SteppedView.vue | 29 ++++ .../components/page/tabbed/TabbedDetail.vue | 138 ++++++++++++++++++ .../vstutils/components/page/tabbed/state.ts | 90 ++++++++++++ .../vstutils/fields/base/BaseField.ts | 7 +- frontend_src/vstutils/models/Model.ts | 2 + .../vstutils/models/ModelsResolver.ts | 11 ++ .../__tests__/models-generation.test.ts | 34 +++++ frontend_src/vstutils/models/sandbox.ts | 20 ++- frontend_src/vstutils/views/View.ts | 2 +- package.json | 6 +- test_src/test_proj/models/contented.py | 3 +- test_src/test_proj/tests.py | 1 + vstutils/api/schema/inspectors.py | 4 + vstutils/api/serializers.py | 12 ++ vstutils/management/commands/rpc_worker.py | 4 +- yarn.lock | 34 +++-- 20 files changed, 430 insertions(+), 68 deletions(-) create mode 100644 frontend_src/vstutils/components/page/DefaultView.vue create mode 100644 frontend_src/vstutils/components/page/tabbed/SteppedView.vue create mode 100644 frontend_src/vstutils/components/page/tabbed/TabbedDetail.vue create mode 100644 frontend_src/vstutils/components/page/tabbed/state.ts create mode 100644 frontend_src/vstutils/models/__tests__/models-generation.test.ts diff --git a/doc/backend.rst b/doc/backend.rst index 039067ce..8cd68fdc 100644 --- a/doc/backend.rst +++ b/doc/backend.rst @@ -57,7 +57,7 @@ Serializers ~~~~~~~~~~~ .. automodule:: vstutils.api.serializers - :members: BaseSerializer,VSTSerializer,EmptySerializer,JsonObjectSerializer + :members: DisplayMode,BaseSerializer,VSTSerializer,EmptySerializer,JsonObjectSerializer Views ~~~~~ diff --git a/frontend_src/vstutils/AppConfiguration.ts b/frontend_src/vstutils/AppConfiguration.ts index 3255a7cf..8229b3ec 100644 --- a/frontend_src/vstutils/AppConfiguration.ts +++ b/frontend_src/vstutils/AppConfiguration.ts @@ -45,6 +45,8 @@ export interface AppInfo extends Info { [key: string]: any; } +export const MODEL_MODES = ['DEFAULT', 'STEP'] as const; + export type ModelDefinition = Schema & { properties?: Record; 'x-properties-groups'?: Record; @@ -52,6 +54,7 @@ export type ModelDefinition = Schema & { 'x-non-bulk-methods'?: HttpMethod[]; 'x-translate-model'?: string; 'x-hide-not-required'?: boolean; + 'x-display-mode'?: typeof MODEL_MODES[number]; }; export interface AppSchema extends Spec { diff --git a/frontend_src/vstutils/components/page/DefaultView.vue b/frontend_src/vstutils/components/page/DefaultView.vue new file mode 100644 index 00000000..12e406a1 --- /dev/null +++ b/frontend_src/vstutils/components/page/DefaultView.vue @@ -0,0 +1,48 @@ + + + diff --git a/frontend_src/vstutils/components/page/OneEntity.vue b/frontend_src/vstutils/components/page/OneEntity.vue index 2ad4d972..36ed87e4 100644 --- a/frontend_src/vstutils/components/page/OneEntity.vue +++ b/frontend_src/vstutils/components/page/OneEntity.vue @@ -1,50 +1,16 @@ diff --git a/frontend_src/vstutils/components/page/tabbed/SteppedView.vue b/frontend_src/vstutils/components/page/tabbed/SteppedView.vue new file mode 100644 index 00000000..0634a167 --- /dev/null +++ b/frontend_src/vstutils/components/page/tabbed/SteppedView.vue @@ -0,0 +1,29 @@ + + + diff --git a/frontend_src/vstutils/components/page/tabbed/TabbedDetail.vue b/frontend_src/vstutils/components/page/tabbed/TabbedDetail.vue new file mode 100644 index 00000000..8f8a252d --- /dev/null +++ b/frontend_src/vstutils/components/page/tabbed/TabbedDetail.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/frontend_src/vstutils/components/page/tabbed/state.ts b/frontend_src/vstutils/components/page/tabbed/state.ts new file mode 100644 index 00000000..1ab76bb8 --- /dev/null +++ b/frontend_src/vstutils/components/page/tabbed/state.ts @@ -0,0 +1,90 @@ +import { computed, ref, del, type Ref } from 'vue'; +import { pop_up_msg } from '@/vstutils/popUp'; +import { getModelFieldsInstancesGroups, useHideableFieldsGroups } from '@/vstutils/composables'; +import type { SetFieldValueOptions } from '@/vstutils/fields/base'; +import { ModelValidationError, type Model, type ModelConstructor } from '@/vstutils/models'; +import { getApp } from '@/vstutils/utils'; + +export function useTabbedDetailState(opts: { instance: Ref; requireStepValidation?: boolean }) { + const { instance, requireStepValidation = false } = opts; + + const app = getApp(); + + const currentTabIdx = ref(0); + const fieldsErrors = ref>({}); + + const data = computed(() => instance.value.sandbox.value); + + const { visibleFieldsGroups } = useHideableFieldsGroups( + computed(() => + getModelFieldsInstancesGroups(instance.value.constructor as ModelConstructor, data.value), + ), + { hideReadOnly: true }, + ); + + const currentTabFields = computed(() => visibleFieldsGroups.value[currentTabIdx.value].fields); + + function setFieldValue(options: SetFieldValueOptions) { + del(fieldsErrors.value, options.field); + instance.value.sandbox.set(options); + } + + const tabs = computed(() => + visibleFieldsGroups.value.map((g, idx) => { + return { + active: idx === currentTabIdx.value, + title: g.title, + fields: g.fields, + }; + }), + ); + + function openTab(idx: number) { + if (idx < 0 || idx >= visibleFieldsGroups.value.length) { + return; + } + if (!requireStepValidation || validateCurrentStep()) { + currentTabIdx.value = idx; + } + } + + function goPreviousStep() { + openTab(currentTabIdx.value - 1); + } + + function validateCurrentStep() { + try { + instance.value.sandbox.partialValidate(currentTabFields.value.map((f) => f.name)); + fieldsErrors.value = {}; + return true; + } catch (e) { + if (e instanceof ModelValidationError) { + fieldsErrors.value = e.toFieldsErrors(); + } + app.error_handler.showError( + app.i18n.t(pop_up_msg.instance.error.create, [app.error_handler.errorToString(e)]) as string, + ); + } + return false; + } + + function goNextStep() { + openTab(currentTabIdx.value + 1); + } + + const hasPreviousStep = computed(() => currentTabIdx.value > 0); + const hasNextStep = computed(() => currentTabIdx.value < visibleFieldsGroups.value.length - 1); + + return { + currentTabFields, + data, + fieldsErrors, + goNextStep, + goPreviousStep, + hasNextStep, + hasPreviousStep, + openTab, + setFieldValue, + tabs, + }; +} diff --git a/frontend_src/vstutils/fields/base/BaseField.ts b/frontend_src/vstutils/fields/base/BaseField.ts index 9c2670dc..22468fb9 100644 --- a/frontend_src/vstutils/fields/base/BaseField.ts +++ b/frontend_src/vstutils/fields/base/BaseField.ts @@ -328,10 +328,13 @@ export class BaseField AddKeyField; } + let modeValue = modelSchema['x-display-mode']; + if (modeValue && !MODEL_MODES.includes(modeValue)) { + console.warn( + `Model "${modelName}" has unsupported mode "${modeValue}". ` + + `Supported modes: ${MODEL_MODES.join(', ')}.`, + ); + modeValue = undefined; + } + const model = makeModel( class extends BaseModel { static declaredFields = fields; @@ -117,6 +127,7 @@ export class ModelsResolver { static nonBulkMethods = modelSchema['x-non-bulk-methods'] || null; static translateModel = modelSchema['x-translate-model'] || null; static hideNotRequired = modelSchema['x-hide-not-required']; + static displayMode = modeValue ?? 'DEFAULT'; }, modelName, ); diff --git a/frontend_src/vstutils/models/__tests__/models-generation.test.ts b/frontend_src/vstutils/models/__tests__/models-generation.test.ts new file mode 100644 index 00000000..a2034d90 --- /dev/null +++ b/frontend_src/vstutils/models/__tests__/models-generation.test.ts @@ -0,0 +1,34 @@ +import { describe, beforeAll, test, expect } from '@jest/globals'; +import { createApp, createSchema } from '@/unittests'; +import { getApp } from '@/vstutils/utils'; + +describe('Models generation', () => { + beforeAll(async () => { + await createApp({ schema: createSchema() }); + }); + + test('display mode is set', () => { + const app = getApp(); + + const DefaultModel = app.modelsResolver.bySchemaObject({ + type: 'object', + properties: { name: { type: 'string' } }, + }); + expect(DefaultModel.displayMode).toBe('DEFAULT'); + + const StepModel = app.modelsResolver.bySchemaObject({ + type: 'object', + properties: { name: { type: 'string' } }, + 'x-display-mode': 'STEP', + }); + expect(StepModel.displayMode).toBe('STEP'); + + const InvalidModel = app.modelsResolver.bySchemaObject({ + type: 'object', + properties: { name: { type: 'string' } }, + // @ts-expect-error For test invalid value used + 'x-display-mode': 'invalid', + }); + expect(InvalidModel.displayMode).toBe('DEFAULT'); + }); +}); diff --git a/frontend_src/vstutils/models/sandbox.ts b/frontend_src/vstutils/models/sandbox.ts index 1b6507bf..ac815b13 100644 --- a/frontend_src/vstutils/models/sandbox.ts +++ b/frontend_src/vstutils/models/sandbox.ts @@ -137,13 +137,20 @@ export function createModelSandbox(instance: Model) { _changedFields.value.clear(); } - function validate() { + function partialValidate(fieldsNames: string[]) { + const fields = fieldsNames.map((name) => { + const field = instance._fields.get(name); + if (!field) { + throw new Error(`Field "${name}" not found`); + } + return field; + }); const data = getData(); // Validate represent data const errors: FieldValidationErrorInfo[] = []; const validatedData = emptyRepresentData(); - for (const field of instance._fields.values()) { + for (const field of fields) { try { validatedData[field.name] = field.validateValue(data); } catch (e) { @@ -170,12 +177,12 @@ export function createModelSandbox(instance: Model) { // Create inner data const newData = emptyInnerData(); - for (const field of instance._fields.values()) { + for (const field of fields) { newData[field.name] = field.toInner(validatedData); } // Validate inner data - for (const field of instance._fields.values()) { + for (const field of fields) { try { field.validateInner(newData); } catch (e) { @@ -204,6 +211,10 @@ export function createModelSandbox(instance: Model) { return newData; } + function validate() { + return partialValidate([...instance._fields.keys()]); + } + function markUnchanged() { _changedFields.value.clear(); } @@ -212,6 +223,7 @@ export function createModelSandbox(instance: Model) { set, setPrefetchedValue, reset, + partialValidate, validate, markUnchanged, get changed() { diff --git a/frontend_src/vstutils/views/View.ts b/frontend_src/vstutils/views/View.ts index 5edb08b1..a49c771a 100644 --- a/frontend_src/vstutils/views/View.ts +++ b/frontend_src/vstutils/views/View.ts @@ -297,7 +297,7 @@ export abstract class BaseView< * Method that returns Vue component for view * @return {Object} */ - getComponent(): ComponentOptions | typeof Vue { + getComponent(): ComponentOptions | typeof Vue | Component { // If we provide `this` in `data` directly then `view` will become Vue component // eslint-disable-next-line @typescript-eslint/no-this-alias const thisView = this; diff --git a/package.json b/package.json index 2041fda6..070cc80f 100644 --- a/package.json +++ b/package.json @@ -56,10 +56,10 @@ "select2": "4.1.0-rc.0", "select2-bootstrap-5-theme": "^1.3.0", "visibilityjs": "^2.0.2", - "vue": "^2.7.14", + "vue": "^2.7.15", "vue-i18n": "^8.15.4", "vue-router": "^3.6.5", - "vue-template-compiler": "^2.7.14", + "vue-template-compiler": "^2.7.15", "vue-virtual-table": "^0.2.22" }, "resolutions": { @@ -111,7 +111,7 @@ "typescript": "^5.2.2", "vue-loader": "^15.10.2", "vue-tsc": "^1.8.15", - "webpack": "^5.88.2", + "webpack": "^5.89.0", "webpack-bundle-analyzer": "^4.9.1", "webpack-cli": "^4.10.0" } diff --git a/test_src/test_proj/models/contented.py b/test_src/test_proj/models/contented.py index 10002770..2f6447db 100644 --- a/test_src/test_proj/models/contented.py +++ b/test_src/test_proj/models/contented.py @@ -5,7 +5,7 @@ from rest_framework import fields as drf_fields from vstutils.models import BModel from vstutils.api.filter_backends import VSTFilterBackend -from vstutils.api.serializers import BaseSerializer +from vstutils.api.serializers import BaseSerializer, DisplayMode from vstutils.api.fields import FkModelField, DependFromFkField from vstutils.api.base import ModelViewSet @@ -77,6 +77,7 @@ class Meta: class SubVariablesSerializer(BaseSerializer): _hide_not_required = True + _display_mode = DisplayMode.STEP key = drf_fields.CharField(read_only=True) value = drf_fields.CharField(read_only=True) diff --git a/test_src/test_proj/tests.py b/test_src/test_proj/tests.py index ffe35220..167dd56d 100644 --- a/test_src/test_proj/tests.py +++ b/test_src/test_proj/tests.py @@ -2158,6 +2158,7 @@ def has_deep_parent_filter(params): # Check hide non required fields option self.assertTrue(api['definitions']['SubVariables']['x-hide-not-required']) + self.assertEqual(api['definitions']['SubVariables']['x-display-mode'], 'STEP') # Check public centrifugo address when absolute path is provided self.assertEqual(api['info']['x-centrifugo-address'], 'wss://vstutilstestserver/notify/connection/websocket') diff --git a/vstutils/api/schema/inspectors.py b/vstutils/api/schema/inspectors.py index c9702e8d..8bfdf725 100644 --- a/vstutils/api/schema/inspectors.py +++ b/vstutils/api/schema/inspectors.py @@ -620,6 +620,7 @@ def handle_schema(self, field: Serializer, schema: openapi.SwaggerDict, use_refe view_field_name = getattr(serializer_class, '_view_field_name', None) hide_not_required = getattr(serializer_class, '_hide_not_required', None) + display_mode = getattr(serializer_class, '_display_mode', None) if view_field_name is None and schema_properties: view_field_name = get_first_match_name(schema_properties, schema_properties[0]) @@ -645,6 +646,9 @@ def handle_schema(self, field: Serializer, schema: openapi.SwaggerDict, use_refe if hide_not_required: schema['x-hide-not-required'] = bool(hide_not_required) + if display_mode: + schema['x-display-mode'] = display_mode + schema._handled = True # pylint: disable=protected-access # TODO: return it when swagger become openapi 3.0.1 diff --git a/vstutils/api/serializers.py b/vstutils/api/serializers.py index 27fba586..686b77c8 100644 --- a/vstutils/api/serializers.py +++ b/vstutils/api/serializers.py @@ -45,6 +45,18 @@ def update_declared_fields( return serializer_class +class DisplayMode(utils.BaseEnum): + """ + For any serializer displayed on frontend property `_display_mode` can be set to one of this values. + """ + + DEFAULT = utils.BaseEnum.SAME + """Will be used if no mode provided.""" + + STEP = utils.BaseEnum.SAME + """Each properties group displayed as separate tab. On creation displayed as multiple steps.""" + + class DependFromFkSerializerMixin: def to_internal_value(self, data): if self.instance is not None and self.partial and isinstance(data, _t.Dict): diff --git a/vstutils/management/commands/rpc_worker.py b/vstutils/management/commands/rpc_worker.py index 09eecfd3..3ea5b828 100644 --- a/vstutils/management/commands/rpc_worker.py +++ b/vstutils/management/commands/rpc_worker.py @@ -96,9 +96,9 @@ def handle(self, *args, **options): # nocv proc.kill() proc.wait() raise exc - except KeyboardInterrupt: + except KeyboardInterrupt: # nocv self._print('Exit by user...', 'WARNING') - except BaseException as err: + except BaseException as err: # nocv self._print(traceback.format_exc()) self._print(str(err), 'ERROR') sys.exit(1) diff --git a/yarn.lock b/yarn.lock index 29f56381..ccff30ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2012,10 +2012,10 @@ "@vue/compiler-core" "3.3.4" "@vue/shared" "3.3.4" -"@vue/compiler-sfc@2.7.14": - version "2.7.14" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz#3446fd2fbb670d709277fc3ffa88efc5e10284fd" - integrity sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA== +"@vue/compiler-sfc@2.7.15": + version "2.7.15" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-2.7.15.tgz#62135fb2f69559fc723fd9c56b8e8b0ac7864a0b" + integrity sha512-FCvIEevPmgCgqFBH7wD+3B97y7u7oj/Wr69zADBf403Tui377bThTjBvekaZvlRr4IwUAu3M6hYZeULZFJbdYg== dependencies: "@babel/parser" "^7.18.4" postcss "^8.4.14" @@ -7654,6 +7654,14 @@ vue-template-compiler@^2.7.14: de-indent "^1.0.2" he "^1.2.0" +vue-template-compiler@^2.7.15: + version "2.7.15" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.15.tgz#ec88ba8ceafe0f17a528b89c57e01e02da92b0de" + integrity sha512-yQxjxMptBL7UAog00O8sANud99C6wJF+7kgbcwqkvA38vCGF7HWE66w0ZFnS/kX5gSoJr/PQ4/oS3Ne2pW37Og== + dependencies: + de-indent "^1.0.2" + he "^1.2.0" + vue-template-es2015-compiler@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" @@ -7682,12 +7690,12 @@ vue@^2.6.12: resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235" integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ== -vue@^2.7.14: - version "2.7.14" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.14.tgz#3743dcd248fd3a34d421ae456b864a0246bafb17" - integrity sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ== +vue@^2.7.15: + version "2.7.15" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.7.15.tgz#94cd34e6e9f22cd2d35a02143f96a5beac1c1f54" + integrity sha512-a29fsXd2G0KMRqIFTpRgpSbWaNBK3lpCTOLuGLEDnlHWdjB8fwl6zyYZ8xCrqkJdatwZb4mGHiEfJjnw0Q6AwQ== dependencies: - "@vue/compiler-sfc" "2.7.14" + "@vue/compiler-sfc" "2.7.15" csstype "^3.1.0" w3c-keyname@^2.2.0: @@ -7786,10 +7794,10 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.88.2: - version "5.88.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.2.tgz#f62b4b842f1c6ff580f3fcb2ed4f0b579f4c210e" - integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ== +webpack@^5.89.0: + version "5.89.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc" + integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.0" From 5f5f35eb99edc09975591cfbc89ea8880c35ae82 Mon Sep 17 00:00:00 2001 From: Dmitriy Ovcharenko Date: Thu, 21 Dec 2023 15:45:53 +1000 Subject: [PATCH 05/20] Fix(backend): Fields validation --- frontend_src/__mocks__/testSchema.json | 24 +++++++++---------- .../components/page/tabbed/TabbedDetail.vue | 2 +- .../fields/numbers/__tests__/rating.test.js | 9 ++----- .../vstutils/fields/numbers/rating/index.js | 4 ++-- test_src/test_proj/tests.py | 14 +++++++---- vstutils/api/schema/inspectors.py | 23 +++++++++++++++--- vstutils/asgi.py | 2 +- vstutils/settings.py | 6 ++--- 8 files changed, 51 insertions(+), 33 deletions(-) diff --git a/frontend_src/__mocks__/testSchema.json b/frontend_src/__mocks__/testSchema.json index 0c072357..14c243f2 100644 --- a/frontend_src/__mocks__/testSchema.json +++ b/frontend_src/__mocks__/testSchema.json @@ -7291,9 +7291,9 @@ "title": "Rating stars", "type": "number", "format": "rating", + "minimum": 0, + "maximum": 5, "x-options": { - "min_value": 0, - "max_value": 5, "step": 1, "style": "stars", "color": "green", @@ -7305,9 +7305,9 @@ "title": "Rating slider", "type": "number", "format": "rating", + "minimum": 0, + "maximum": 5, "x-options": { - "min_value": 0, - "max_value": 5, "step": 0.3, "style": "slider", "color": "orange", @@ -7318,9 +7318,9 @@ "title": "Rating icon", "type": "number", "format": "rating", + "minimum": 0, + "maximum": 5, "x-options": { - "min_value": 0, - "max_value": 5, "step": 1, "style": "fa_icon", "color": "DeepPink", @@ -8013,9 +8013,9 @@ "title": "Rating stars", "type": "number", "format": "rating", + "minimum": 0, + "maximum": 5, "x-options": { - "min_value": 0, - "max_value": 5, "step": 1, "style": "stars", "color": "green", @@ -8027,9 +8027,9 @@ "title": "Rating slider", "type": "number", "format": "rating", + "minimum": 0, + "maximum": 5, "x-options": { - "min_value": 0, - "max_value": 5, "step": 0.3, "style": "slider", "color": "orange", @@ -8040,9 +8040,9 @@ "title": "Rating icon", "type": "number", "format": "rating", + "minimum": 0, + "maximum": 5, "x-options": { - "min_value": 0, - "max_value": 5, "step": 1, "style": "fa_icon", "color": "DeepPink", diff --git a/frontend_src/vstutils/components/page/tabbed/TabbedDetail.vue b/frontend_src/vstutils/components/page/tabbed/TabbedDetail.vue index 8f8a252d..83c4019b 100644 --- a/frontend_src/vstutils/components/page/tabbed/TabbedDetail.vue +++ b/frontend_src/vstutils/components/page/tabbed/TabbedDetail.vue @@ -20,7 +20,7 @@ { test('check rating empty null values', () => { @@ -8,9 +7,7 @@ describe('RatingField', () => { name: 'rating', required: true, 'x-nullable': true, - [X_OPTIONS]: { - min_value: 1, - }, + minimum: 1, }); expect(ratingField.getInitialValue()).toBeNull(); }); @@ -20,9 +17,7 @@ describe('RatingField', () => { name: 'rating', required: true, 'x-nullable': false, - [X_OPTIONS]: { - min_value: 1, - }, + minimum: 1, }); expect(ratingField.getInitialValue()).toBe(1); }); diff --git a/frontend_src/vstutils/fields/numbers/rating/index.js b/frontend_src/vstutils/fields/numbers/rating/index.js index 10ab28a5..e24f7563 100644 --- a/frontend_src/vstutils/fields/numbers/rating/index.js +++ b/frontend_src/vstutils/fields/numbers/rating/index.js @@ -58,8 +58,8 @@ export class RatingField extends FloatField { constructor(options) { super(options); - this.min = this.props.min_value; - this.max = this.props.max_value; + this.min = options.minimum ?? 0; + this.max = options.maximum ?? 5; this.step = this.props.step; this.style = this.props.style; this.color = this.props.color || '#ffb100'; diff --git a/test_src/test_proj/tests.py b/test_src/test_proj/tests.py index 167dd56d..f544cf80 100644 --- a/test_src/test_proj/tests.py +++ b/test_src/test_proj/tests.py @@ -1743,6 +1743,8 @@ def test_openapi_schema_content(self): self.assertEqual(api['definitions']['OneAuthor']['properties']['posts']['items']['type'], 'object') self.assertEqual(api['definitions']['OneAuthor']['properties']['posts']['items']['properties']['title']['description'], 'Some description') + # Check that minLength is set if field allow_null is False + self.assertEqual(api['definitions']['OneAuthor']['properties']['decimal']['minLength'], 1) # Check properly format for RatingField self.assertEqual( @@ -1751,9 +1753,9 @@ def test_openapi_schema_content(self): 'title': 'Rating', 'type': 'number', 'format': 'rating', + 'minimum': 0, + 'maximum': 10, X_OPTIONS: { - 'min_value': 0, - 'max_value': 10, 'step': 1, 'style': 'slider', 'color': 'red', @@ -1767,9 +1769,9 @@ def test_openapi_schema_content(self): 'title': 'Fa icon rating', 'type': 'number', 'format': 'rating', + 'minimum': 0, + 'maximum': 5, X_OPTIONS: { - 'min_value': 0, - 'max_value': 5, 'step': 1, 'style': 'fa_icon', 'color': None, @@ -2063,6 +2065,9 @@ def has_deep_parent_filter(params): } ) + # Check field without allow_blank will have minLength + self.assertEqual(api['definitions']['OnePost']['properties']['text']['minLength'], 1) + # Check dynamic field with complex types nested_model = { 'type': 'object', @@ -2203,6 +2208,7 @@ def has_deep_parent_filter(params): 'type': 'string', 'format': 'wysiwyg', 'title': 'Text', + 'minLength': 1, } ) diff --git a/vstutils/api/schema/inspectors.py b/vstutils/api/schema/inspectors.py index 8bfdf725..1549fcda 100644 --- a/vstutils/api/schema/inspectors.py +++ b/vstutils/api/schema/inspectors.py @@ -4,7 +4,7 @@ from django.http import FileResponse from drf_yasg.inspectors.base import FieldInspector, FilterInspector, NotHandled -from drf_yasg.inspectors.field import ReferencingSerializerInspector, decimal_field_type +from drf_yasg.inspectors.field import ReferencingSerializerInspector, decimal_field_type, find_limits from drf_yasg.inspectors.query import DrfAPICompatInspector, force_real_str # type: ignore from drf_yasg import openapi from rest_framework.fields import Field, JSONField, DecimalField, ListField, empty @@ -130,10 +130,26 @@ def field_have_redirect(field, **kwargs): return kwargs +# Some fields must be ignored from limits because actual data they represent doesn't +# match field type they extend. For example when field extends CharField but actually +# represent object or array. +IGNORED_LIMITS = ( + # Actually object or array + fields.NamedBinaryFileInJsonField, + # Can be any type + fields.DynamicJsonTypeField, +) + + def field_extra_handler(field, **kwargs): kwargs = field_have_redirect(field, **kwargs) if kwargs['type'] in (openapi.TYPE_ARRAY, openapi.TYPE_OBJECT): kwargs['title'] = force_real_str(field.label) if field.label else None + + if not isinstance(field, IGNORED_LIMITS): + # Get limits from field e.g min_length, minimum + kwargs.update(find_limits(field)) + return kwargs @@ -352,8 +368,6 @@ def field_to_swagger_object(self, field, swagger_object_type, use_references, ** 'type': openapi.TYPE_NUMBER, 'format': FORMAT_RATING, X_OPTIONS: { - 'min_value': field.min_value, - 'max_value': field.max_value, 'step': field.step, 'style': field.front_style, 'color': field.color, @@ -550,6 +564,9 @@ def field_to_swagger_object(self, field, swagger_object_type, use_references, ** if field.default != empty: kwargs['default'] = str(field.default) + if not field.allow_null and kwargs['type'] == 'string': + kwargs['minLength'] = 1 + return SwaggerType(**field_extra_handler(field, **kwargs)) diff --git a/vstutils/asgi.py b/vstutils/asgi.py index 7a062daf..7615fd32 100644 --- a/vstutils/asgi.py +++ b/vstutils/asgi.py @@ -31,7 +31,7 @@ CORSMiddleware, allow_origins=settings.CORS_ALLOWED_ORIGINS if not settings.CORS_ORIGIN_ALLOW_ALL else ['*'], expose_headers=settings.CORS_EXPOSE_HEADERS, - allow_origin_regex=settings.CORS_ALLOWED_ORIGIN_REGEXES if not settings.CORS_ORIGIN_ALLOW_ALL else None, + allow_origin_regex=settings.CORS_ALLOWED_ORIGIN_REGEX if not settings.CORS_ORIGIN_ALLOW_ALL else None, allow_methods=getattr(settings, 'CORS_ALLOW_METHODS', ("GET",)) if not settings.CORS_ORIGIN_ALLOW_ALL else ['*'], allow_headers=getattr(settings, 'CORS_ALLOW_HEADERS', ()) if not settings.CORS_ORIGIN_ALLOW_ALL else ['*'], allow_credentials=getattr(settings, 'CORS_ALLOWED_CREDENTIALS', settings.CORS_ORIGIN_ALLOW_ALL), diff --git a/vstutils/settings.py b/vstutils/settings.py index 7b2aacd7..2334bc10 100644 --- a/vstutils/settings.py +++ b/vstutils/settings.py @@ -172,7 +172,7 @@ class WebSection(BaseAppendSection): 'cors_allow_all_origins': ConfigBoolType, 'cors_allowed_credentials': ConfigBoolType, 'cors_allowed_origins': ConfigListType, - 'cors_allowed_origins_regexes': ConfigListType, + 'cors_allowed_origins_regex': ConfigStringType, 'cors_expose_headers': ConfigListType, 'cors_allow_methods': ConfigListType, 'cors_allow_headers': ConfigListType, @@ -455,7 +455,7 @@ class DjangoEnv(environ.Env): 'allow_cors': env.bool(f'{ENV_NAME}_WEB_ALLOW_CORS', default=False), 'cors_allowed_credentials': env.bool(f'{ENV_NAME}_WEB_CORS_ALLOWED_CREDENTIALS', default=False), 'cors_allowed_origins': env.list(f'{ENV_NAME}_WEB_CORS_ALLOWED_ORIGINS', default=[]), - 'cors_allowed_origins_regexes': env.list(f'{ENV_NAME}_WEB_CORS_ALLOWED_ORIGINS_REGEXES', default=[]), + 'cors_allowed_origins_regex': env.str(f'{ENV_NAME}_WEB_CORS_ALLOWED_ORIGINS_REGEX', default=''), 'cors_expose_headers': env.list(f'{ENV_NAME}_WEB_CORS_EXPOSE_HEADERS', default=[]), 'cors_preflight_max_age': env.str(f'{ENV_NAME}_WEB_CORS_PREFLIGHT_MAX_AGE', default='1d'), 'session_timeout': env.str(f"{ENV_NAME}_SESSION_TIMEOUT", default='2w'), @@ -734,7 +734,7 @@ def secret_key(secret_file, default='*sg17)9wa_e+4$n%7n7r_(kqwlsc^^xdoc3&px$hs)s CORS_ORIGIN_ALLOW_ALL: bool = web['allow_cors'] CORS_ALLOWED_ORIGINS: _t.Sequence[str] = web['cors_allowed_origins'] CORS_ALLOWED_CREDENTIALS: bool = web['cors_allowed_credentials'] -CORS_ALLOWED_ORIGIN_REGEXES: _t.Sequence[_t.Union[str, _t.Pattern[str]]] = web['cors_allowed_origins_regexes'] or None +CORS_ALLOWED_ORIGIN_REGEX: _t.Union[str, _t.Pattern[str]] = web['cors_allowed_origins_regex'] or None CORS_EXPOSE_HEADERS: _t.Sequence = web['cors_expose_headers'] CORS_PREFLIGHT_MAX_AGE: int = web['cors_preflight_max_age'] if 'cors_allow_methods' in web: From 43856581ae23eb78b19141a1629039a416fa6845 Mon Sep 17 00:00:00 2001 From: Alexander Vyaznikov Date: Thu, 21 Dec 2023 17:02:49 +1000 Subject: [PATCH 06/20] Fix(backend): Fixed permissions in schema generations for nested views. --- ...duct_option_manufacturer_store_and_more.py | 74 +++++++++ test_src/test_proj/models/__init__.py | 1 + test_src/test_proj/models/deep.py | 2 +- test_src/test_proj/models/nested_models.py | 84 ++++++++++ test_src/test_proj/settings.py | 3 + test_src/test_proj/tests.py | 73 +++++++++ vstutils/__init__.py | 2 +- vstutils/api/permissions.py | 18 +-- vstutils/api/schema/generators.py | 8 + vstutils/api/schema/schema.py | 149 +++++++++--------- 10 files changed, 330 insertions(+), 84 deletions(-) create mode 100644 test_src/test_proj/migrations/0043_manufacturer_store_product_option_manufacturer_store_and_more.py create mode 100644 test_src/test_proj/models/nested_models.py diff --git a/test_src/test_proj/migrations/0043_manufacturer_store_product_option_manufacturer_store_and_more.py b/test_src/test_proj/migrations/0043_manufacturer_store_product_option_manufacturer_store_and_more.py new file mode 100644 index 00000000..b8907db8 --- /dev/null +++ b/test_src/test_proj/migrations/0043_manufacturer_store_product_option_manufacturer_store_and_more.py @@ -0,0 +1,74 @@ +# Generated by Django 4.2.8 on 2023-12-20 06:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('test_proj', '0042_testexternalcustommodel'), + ] + + operations = [ + migrations.CreateModel( + name='Manufacturer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ], + options={ + 'default_related_name': 'manufacturers', + }, + ), + migrations.CreateModel( + name='Store', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ], + options={ + 'default_related_name': 'stores', + }, + ), + migrations.CreateModel( + name='Product', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('price', models.DecimalField(decimal_places=2, max_digits=10)), + ('manufacturer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='test_proj.manufacturer')), + ('store', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='test_proj.store')), + ], + options={ + 'default_related_name': 'products', + }, + ), + migrations.CreateModel( + name='Option', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='test_proj.product')), + ], + options={ + 'default_related_name': 'options', + }, + ), + migrations.AddField( + model_name='manufacturer', + name='store', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='test_proj.store'), + ), + migrations.CreateModel( + name='Attribute', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='test_proj.product')), + ], + options={ + 'default_related_name': 'attributes', + }, + ), + ] diff --git a/test_src/test_proj/models/__init__.py b/test_src/test_proj/models/__init__.py index 96fba894..54fa6086 100644 --- a/test_src/test_proj/models/__init__.py +++ b/test_src/test_proj/models/__init__.py @@ -14,3 +14,4 @@ from .fields_testing import Post, ExtraPost, Author, ModelWithChangedFk, ModelWithCrontabField, ModelWithUuidFK, ModelWithUuidPk from .cacheable import CachableModel, CachableProxyModel from .deep import Group, ModelWithNestedModels, GroupWithFK, AnotherDeepNested, ProtectedBySignal +from .nested_models import Option, Attribute, Store, Product, Manufacturer diff --git a/test_src/test_proj/models/deep.py b/test_src/test_proj/models/deep.py index 9cf39c2f..176e51b2 100644 --- a/test_src/test_proj/models/deep.py +++ b/test_src/test_proj/models/deep.py @@ -66,5 +66,5 @@ class Meta: 'protected': { 'allow_append': True, 'model': ProtectedBySignal, - } + }, } diff --git a/test_src/test_proj/models/nested_models.py b/test_src/test_proj/models/nested_models.py new file mode 100644 index 00000000..c49b1550 --- /dev/null +++ b/test_src/test_proj/models/nested_models.py @@ -0,0 +1,84 @@ +from django.db import models +from django.dispatch import receiver +from django.db.models.signals import pre_delete +from django.core.validators import ValidationError +from rest_framework.permissions import BasePermission +from rest_framework.request import Request +from rest_framework.views import APIView + +from vstutils.models import BaseModel + + +class DisallowStaffPermission(BasePermission): + def has_permission(self, request, view): + if not request.user.is_superuser and request.user.is_staff: + return False + return super().has_permission(request, view) + + +class Option(BaseModel): + name = models.CharField(max_length=255) + product = models.ForeignKey('Product', on_delete=models.CASCADE) + + class Meta: + default_related_name = 'options' + + +class Attribute(BaseModel): + name = models.CharField(max_length=255) + product = models.ForeignKey('Product', on_delete=models.CASCADE) + + class Meta: + default_related_name = 'attributes' + _permission_classes = [DisallowStaffPermission] + + +class Product(BaseModel): + name = models.CharField(max_length=255) + price = models.DecimalField(max_digits=10, decimal_places=2) + store = models.ForeignKey('Store', on_delete=models.CASCADE) + manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE) + + class Meta: + default_related_name = 'products' + _nested = { + 'options': { + 'allow_append': True, + 'model': Option, + }, + 'attributes': { + 'allow_append': True, + 'model': Attribute, + } + } + + +class Manufacturer(BaseModel): + name = models.CharField(max_length=255) + store = models.ForeignKey('Store', on_delete=models.CASCADE) + + class Meta: + default_related_name = 'manufacturers' + _nested = { + 'products': { + 'allow_append': False, + 'model': Product, + } + } + + +class Store(BaseModel): + name = models.CharField(max_length=255) + + class Meta: + default_related_name = 'stores' + _nested = { + 'products': { + 'allow_append': True, + 'model': Product, + }, + 'manufacturers': { + 'allow_append': False, + 'model': Manufacturer, + } + } diff --git a/test_src/test_proj/settings.py b/test_src/test_proj/settings.py index 2c10a048..71c3187a 100644 --- a/test_src/test_proj/settings.py +++ b/test_src/test_proj/settings.py @@ -68,6 +68,9 @@ API[VST_API_VERSION][r'modelwithnested'] = dict( model='test_proj.models.ModelWithNestedModels' ) +API[VST_API_VERSION][r'stores'] = dict( + model='test_proj.models.Store' +) API[VST_API_VERSION][r'modelwithcrontab'] = dict( model='test_proj.models.ModelWithCrontabField' ) diff --git a/test_src/test_proj/tests.py b/test_src/test_proj/tests.py index f544cf80..b5f27f2c 100644 --- a/test_src/test_proj/tests.py +++ b/test_src/test_proj/tests.py @@ -86,6 +86,11 @@ ModelWithNestedModels, ProtectedBySignal, ModelWithUuidPk, + Store, + Manufacturer, + Option, + Attribute, + Product ) from rest_framework.exceptions import ValidationError from base64 import b64encode @@ -2328,6 +2333,23 @@ def has_deep_parent_filter(params): schema = self.endpoint_schema() self.assertTrue(schema['definitions']['User']['properties']['is_staff']['readOnly']) + # check that nested endponit's permissions took into account + user = self._create_user(is_super_user=False, is_staff=True) + with self.user_as(self, user): + schema = self.endpoint_schema() + schemas_differance = set(api['paths'].keys()) - set(schema['paths'].keys()) + expected_differance = { + '/stores/{id}/products/{products_id}/attributes/', + '/stores/{id}/products/{products_id}/attributes/{attributes_id}/', + '/stores/{id}/manufacturers/{manufacturers_id}/products/{products_id}/attributes/{attributes_id}/', + '/stores/{id}/manufacturers/{manufacturers_id}/products/{products_id}/attributes/', + } + # Check that only expected endpoints were banned. + self.assertEqual( + schemas_differance, + expected_differance + ) + def test_search_fields(self): self.assertEqual( self.get_model_class('test_proj.Variable').generated_view.search_fields, @@ -5073,6 +5095,57 @@ def serializer_test(serializer): generated_serializer = ModelWithBinaryFiles.generated_view.serializer_class() serializer_test(generated_serializer) + def test_nested_views_permissions(self): + # Test nested model viewsets permissions. + store = Store.objects.create( + name='test' + ) + manufacturer = Manufacturer.objects.create( + name='test man', + store=store + ) + product = Product.objects.create( + name='test prod', + store=store, + price = 100, + manufacturer=manufacturer + ) + attr = Attribute.objects.create( + name='test attr', + product=product + ) + option = Option.objects.create( + name='test option', + product=product, + ) + + endpoints_to_test = [ + {'method': 'get', 'path': f'/stores/{store.id}/products/{product.id}/attributes/'}, + {'method': 'get', 'path': f'/stores/{store.id}/products/{product.id}/attributes/{attr.id}/'}, + {'method': 'get', 'path': f'/stores/{store.id}/manufacturers/{manufacturer.id}/products/{product.id}/attributes/{attr.id}/'}, + {'method': 'get', 'path': f'/stores/{store.id}/manufacturers/{manufacturer.id}/products/{product.id}/attributes/'}, + ] + + always_available = [ + {'method': 'get', 'path': f'/stores/{store.id}/products/{product.id}/options/{option.id}/'}, + {'method': 'get', 'path': f'/stores/{store.id}/products/{product.id}/options/'}, + ] + + results = self.bulk(endpoints_to_test + always_available) + + for result in results: + self.assertEqual(result['status'], 200, result['path']) + + user = self._create_user(is_super_user=False, is_staff=True) + with self.user_as(self, user): + results = self.bulk(endpoints_to_test) + for result in results: + self.assertEqual(result['status'], 403, result['path']) + + results = self.bulk(always_available) + for result in results: + self.assertEqual(result['status'], 200, result['path']) + def test_deep_nested(self): results = self.bulk([ # [0-2] Create 3 nested objects diff --git a/vstutils/__init__.py b/vstutils/__init__.py index 4826670b..14ada44f 100644 --- a/vstutils/__init__.py +++ b/vstutils/__init__.py @@ -1,2 +1,2 @@ # pylint: disable=django-not-available -__version__: str = '5.8.18' +__version__: str = '5.8.19' diff --git a/vstutils/api/permissions.py b/vstutils/api/permissions.py index 4301f919..c638db3a 100644 --- a/vstutils/api/permissions.py +++ b/vstutils/api/permissions.py @@ -5,17 +5,17 @@ from ..utils import raise_context -class IsAuthenticatedOpenApiRequest(permissions.IsAuthenticated): +def is_openapi_request(request): + return ( + request.path.startswith(f'/{settings.API_URL}/openapi/') or + request.path.startswith(f'/{settings.API_URL}/endpoint/') or + request.path == f'/{settings.API_URL}/{request.version}/_openapi/' + ) - def is_openapi(self, request): - return ( - request.path.startswith(f'/{settings.API_URL}/openapi/') or - request.path.startswith(f'/{settings.API_URL}/endpoint/') or - request.path == f'/{settings.API_URL}/{request.version}/_openapi/' - ) +class IsAuthenticatedOpenApiRequest(permissions.IsAuthenticated): def has_permission(self, request, view): - return self.is_openapi(request) or super().has_permission(request, view) + return is_openapi_request(request) or super().has_permission(request, view) class SuperUserPermission(IsAuthenticatedOpenApiRequest): @@ -29,7 +29,7 @@ def has_permission(self, request, view): issubclass(view.get_queryset().model, AbstractUser) and str(view.kwargs['pk']) in (str(request.user.pk), 'profile') ) - return self.is_openapi(request) + return is_openapi_request(request) def has_object_permission(self, request, view, obj): if request.user.is_superuser: diff --git a/vstutils/api/schema/generators.py b/vstutils/api/schema/generators.py index 66f1171f..47e99934 100644 --- a/vstutils/api/schema/generators.py +++ b/vstutils/api/schema/generators.py @@ -10,6 +10,8 @@ from drf_yasg.inspectors import field as field_insp from vstutils.utils import raise_context_decorator_with_default +from .schema import get_nested_view_obj, _get_nested_view_and_subaction + def get_centrifugo_public_address(request: drf_request.Request): address = settings.CENTRIFUGO_PUBLIC_HOST @@ -103,6 +105,12 @@ def get_path_parameters(self, path, view_cls): continue # nocv return parameters + def should_include_endpoint(self, path, method, view, public): + nested_view, sub_action = _get_nested_view_and_subaction(view) + if nested_view and sub_action: + view = get_nested_view_obj(view, nested_view, sub_action, method) + return super().should_include_endpoint(path, method, view, public) + def get_operation_keys(self, subpath, method, view): keys = super().get_operation_keys(subpath, method, view) subpath_keys = list(filter(bool, subpath.split('/'))) diff --git a/vstutils/api/schema/schema.py b/vstutils/api/schema/schema.py index f5e36c37..ec6aefc8 100644 --- a/vstutils/api/schema/schema.py +++ b/vstutils/api/schema/schema.py @@ -19,6 +19,79 @@ View = _t.Type[views.APIView] +def _get_nested_view_and_subaction(view, default=None): + sub_action = getattr(view, view.action, None) + return getattr(sub_action, '_nested_view', default), sub_action + + +def _get_nested_view_class(nested_view, view_action_func): + # pylint: disable=protected-access + if not hasattr(view_action_func, '_nested_name'): + return nested_view + + nested_action_name = '_'.join(view_action_func._nested_name.split('_')[1:]) + + if nested_view is None: + return nested_view # nocv + + if hasattr(view_action_func, '_nested_view'): + nested_view_class = view_action_func._nested_view + view_action_func = getattr(nested_view_class, nested_action_name, None) + else: # nocv + nested_view_class = None + view_action_func = None + + if view_action_func is None: + return nested_view + + return _get_nested_view_class(nested_view_class, view_action_func) + + +def get_nested_view_obj(view, nested_view: 'View', view_action_func, method): + # pylint: disable=protected-access + # Get nested view recursively + nested_view: 'View' = utils.get_if_lazy(_get_nested_view_class(nested_view, view_action_func)) + # Get action suffix + replace_pattern = view_action_func._nested_subname + '_' + replace_index = view.action.index(replace_pattern) + len(replace_pattern) + action_suffix = view.action[replace_index:] + # Check detail or list action + is_detail = action_suffix.endswith('detail') + is_list = action_suffix.endswith('list') + # Create view object + method = method.lower() + nested_view_obj = nested_view() + nested_view_obj.request = view.request + nested_view_obj.kwargs = view.kwargs + nested_view_obj.lookup_field = view.lookup_field + nested_view_obj.lookup_url_kwarg = view.lookup_url_kwarg + nested_view_obj.format_kwarg = None + nested_view_obj.format_kwarg = None + nested_view_obj._nested_wrapped_view = getattr(view_action_func, '_nested_wrapped_view', None) + # Check operation action + if method == 'post' and is_list: + nested_view_obj.action = 'create' + elif method == 'get' and is_list: + nested_view_obj.action = 'list' + elif method == 'get' and is_detail: + nested_view_obj.action = 'retrieve' + elif method == 'put' and is_detail: + nested_view_obj.action = 'update' + elif method == 'patch' and is_detail: + nested_view_obj.action = 'partial_update' + elif method == 'delete' and is_detail: + nested_view_obj.action = 'destroy' + else: + nested_view_obj.action = action_suffix + new_view = getattr(nested_view_obj, action_suffix, None) + if new_view is not None: + serializer_class = new_view.kwargs.get('serializer_class', None) + if serializer_class: + nested_view_obj.serializer_class = serializer_class + + return nested_view_obj + + class ExtendedSwaggerAutoSchema(SwaggerAutoSchema): def get_query_parameters(self): result = super().get_query_parameters() @@ -107,72 +180,6 @@ def __init__(self, *args, **kwargs): self._sch.view = args[0] self.request._schema = self - def _get_nested_view_class(self, nested_view: 'View', view_action_func): - # pylint: disable=protected-access - if not hasattr(view_action_func, '_nested_name'): - return nested_view - - nested_action_name = '_'.join(view_action_func._nested_name.split('_')[1:]) - - if nested_view is None: - return nested_view # nocv - - if hasattr(view_action_func, '_nested_view'): - nested_view_class = view_action_func._nested_view - view_action_func = getattr(nested_view_class, nested_action_name, None) - else: # nocv - nested_view_class = None - view_action_func = None - - if view_action_func is None: - return nested_view - - return self._get_nested_view_class(nested_view_class, view_action_func) - - def __get_nested_view_obj(self, nested_view: 'View', view_action_func): - # pylint: disable=protected-access - # Get nested view recursively - nested_view: 'View' = utils.get_if_lazy(self._get_nested_view_class(nested_view, view_action_func)) - # Get action suffix - replace_pattern = view_action_func._nested_subname + '_' - replace_index = self.view.action.index(replace_pattern) + len(replace_pattern) - action_suffix = self.view.action[replace_index:] - # Check detail or list action - is_detail = action_suffix.endswith('detail') - is_list = action_suffix.endswith('list') - # Create view object - method = self.method.lower() - nested_view_obj = nested_view() - nested_view_obj.request = self.view.request - nested_view_obj.kwargs = self.view.kwargs - nested_view_obj.lookup_field = self.view.lookup_field - nested_view_obj.lookup_url_kwarg = self.view.lookup_url_kwarg - nested_view_obj.format_kwarg = None - nested_view_obj.format_kwarg = None - nested_view_obj._nested_wrapped_view = getattr(view_action_func, '_nested_wrapped_view', None) - # Check operation action - if method == 'post' and is_list: - nested_view_obj.action = 'create' - elif method == 'get' and is_list: - nested_view_obj.action = 'list' - elif method == 'get' and is_detail: - nested_view_obj.action = 'retrieve' - elif method == 'put' and is_detail: - nested_view_obj.action = 'update' - elif method == 'patch' and is_detail: - nested_view_obj.action = 'partial_update' - elif method == 'delete' and is_detail: - nested_view_obj.action = 'destroy' - else: - nested_view_obj.action = action_suffix - view = getattr(nested_view_obj, action_suffix, None) - if view is not None: - serializer_class = view.kwargs.get('serializer_class', None) - if serializer_class: - nested_view_obj.serializer_class = serializer_class - - return nested_view_obj - def get_operation_id(self, operation_keys=None): new_operation_keys: _t.List[str] = [] append_new_operation_keys = new_operation_keys.append @@ -189,17 +196,13 @@ def get_response_schemas(self, response_serializers): response.description = self.default_status_messages.get(response_code, 'Action accepted.') return responses - def __get_nested_view_and_subaction(self, default=None): - sub_action = getattr(self.view, self.view.action, None) - return getattr(sub_action, '_nested_view', default), sub_action - def __perform_with_nested(self, func_name, *args, **kwargs): # pylint: disable=protected-access - nested_view, sub_action = self.__get_nested_view_and_subaction() + nested_view, sub_action = _get_nested_view_and_subaction(self.view) if nested_view and sub_action: schema = copy(self) try: - schema.view = self.__get_nested_view_obj(nested_view, sub_action) + schema.view = get_nested_view_obj(self.view, nested_view, sub_action, self.method) result = getattr(schema, func_name)(*args, **kwargs) if result: return result @@ -260,7 +263,7 @@ def get_operation(self, operation_keys=None): params_to_override = ('x-title', 'x-icons') if self.method.lower() == 'get': - subscribe_view = self.__get_nested_view_and_subaction(self.view)[0] + subscribe_view = _get_nested_view_and_subaction(self.view, self.view)[0] queryset = getattr(subscribe_view, 'queryset', None) if queryset is not None: # pylint: disable=protected-access From ad21a683e5449f015ba4a0b301a1c6f1f9e456d3 Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Thu, 21 Dec 2023 12:27:17 -0800 Subject: [PATCH 07/20] Fix(docs): Improved russian translation. --- doc/locale/ru/LC_MESSAGES/backend.po | 57 ++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/doc/locale/ru/LC_MESSAGES/backend.po b/doc/locale/ru/LC_MESSAGES/backend.po index ef15ca1b..2262c6d4 100644 --- a/doc/locale/ru/LC_MESSAGES/backend.po +++ b/doc/locale/ru/LC_MESSAGES/backend.po @@ -7,14 +7,14 @@ msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-11-25 04:56+0000\n" +"POT-Creation-Date: 2023-12-20 23:56+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.13.1\n" +"Generated-By: Babel 2.14.0\n" #: ../../backend.rst:2 msgid "Backend API manual" @@ -315,9 +315,10 @@ msgstr "" "вложенного view, kwargs для декоратора " ":class:`vstutils.api.decorators.nested_view`, но поддерживает атрибут " "``model`` в качестве вложенного). ``model`` может быть строкой для " -"импорта. Используйте параметр ``override_params`` в тех случаях, когда необходимо перегрузить" -"параметры генерируемого представления в качестве вложенного (работает только когда задан ``model`` " -"как вложенное представление)." +"импорта. Используйте параметр ``override_params`` в тех случаях, когда " +"необходимо перегрузить параметры генерируемого представления в качестве " +"вложенного (работает только когда задан ``model`` как вложенное " +"представление)." #: of vstutils.models.BModel:123 msgid "" @@ -404,10 +405,11 @@ msgid "" "Django Rest Framework (DRF) Generic ViewSets. It allows developers to " "define and customize various aspects of the associated DRF view class." msgstr "" -"Метод ``get_view_class()`` — это служебный метод в ORM Django " -"моделях, предназначенный для облегчения настройки и создания экземпляров" -"представлений Django Rest Framework (DRF). Это позволяет разработчикам " -"определить и настроить различные аспекты класса представления DRF." +"Метод ``get_view_class()`` — это служебный метод в ORM Django моделях, " +"предназначенный для облегчения настройки и создания " +"экземпляров представлений Django Rest Framework (DRF). Это позволяет " +"разработчикам определить и настроить различные аспекты класса " +"представления DRF." #: of vstutils.models.BModel:189 msgid "" @@ -416,10 +418,11 @@ msgid "" " backends, permission classes, etc. It uses attributes declared in meta " "attributes, but allows individual parts to be overriden." msgstr "" -"Разработчики могут использовать этот метод для изменения различных аспектов " -"получаемого представления, таких как классы сериализаторов, конфигурацию полей, фильтры," -"классы разрешений и т.п. Этот метод использует такие же атрибуты, которые были объявлены" -"в мета-атрибутах, но позволяет перегружать отдельные части." +"Разработчики могут использовать этот метод для изменения различных " +"аспектов получаемого представления, таких как классы сериализаторов, " +"конфигурацию полей, фильтры,классы разрешений и т.п. Этот метод " +"использует такие же атрибуты, которые были объявлены в мета-атрибутах, но " +"позволяет перегружать отдельные части." #: ../../docstring of vstutils.models.BModel.hidden:1 msgid "If hidden is set to True, entry will be excluded from query in BQuerySet." @@ -1223,6 +1226,7 @@ msgstr "" "значения." #: of vstutils.api.fields.DependEnumField:6 +#: vstutils.api.fields.DependFromFkField:13 #: vstutils.api.fields.DynamicJsonTypeField:6 msgid "" "key-value mapping where key is value of subscribed field and value is " @@ -1289,7 +1293,7 @@ msgstr "" msgid "attribute of related model instance with name of type." msgstr "атрибут связанного экземпляра модели с именем типа." -#: of vstutils.api.fields.DependFromFkField:15 +#: of vstutils.api.fields.DependFromFkField:18 msgid "" "``field_attribute`` in related model must be " ":class:`rest_framework.fields.ChoicesField` or GUI will show field as " @@ -2145,6 +2149,26 @@ msgstr "" "Настройте атрибут ``generated_field_factory`` чтобы изменить фабричный " "метод по умолчанию." +#: of vstutils.api.serializers.DisplayMode:1 +msgid "" +"For any serializer displayed on frontend property `_display_mode` can be " +"set to one of this values." +msgstr "" +"Для любого сериализатора, показанного на фронтенде, аттрибут `_display_mode` может принимать " +"одно из следующих значений." + +#: ../../docstring of vstutils.api.serializers.DisplayMode.DEFAULT:1 +msgid "Will be used if no mode provided." +msgstr "Используется, когда не задано никакого режима." + +#: ../../docstring of vstutils.api.serializers.DisplayMode.STEP:1 +msgid "" +"Each properties group displayed as separate tab. On creation displayed as" +" multiple steps." +msgstr "" +"Каждая группа параметров отображается на раздельных вкладках. " +"При создании выглядит как пошаговый мастер." + #: of vstutils.api.serializers.EmptySerializer:1 msgid "" "Default serializer for empty responses. In generated GUI this means that " @@ -4376,8 +4400,9 @@ msgid "" "This function is oldstyle and will be deprecated in future versions. Use " "native call of method :method:`vstutils.models.BModel.get_view_class`." msgstr "" -"Эта функция олдскульная и будет объявлена устаревшей в будущих версиях. Используйте " -"встроенный вызов метода :method:`vstutils.models.BModel.get_view_class`." +"Эта функция олдскульная и будет объявлена устаревшей в будущих версиях. " +"Используйте встроенный вызов метода " +":method:`vstutils.models.BModel.get_view_class`." #: of vstutils.utils.create_view:28 msgid "" From 1f080a595516c8cb9d7b3d7577fc27013e44c5af Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Thu, 21 Dec 2023 13:06:21 -0800 Subject: [PATCH 08/20] Feature(backend): Provide support for msgpack in celery messages via ormsgpack. --- requirements.txt | 2 +- test_src/test_proj/settings.py | 1 + vstutils/api/renderers.py | 2 +- vstutils/environment.py | 16 +++++++++++++--- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 38306b7d..107020af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ ormsgpack~=1.4.1 pyyaml~=6.0.1 # web server -uvicorn~=0.24.0.post1 +uvicorn~=0.25.0 uwsgi==2.0.23 fastapi~=0.105.0 aiofiles==23.2.1 diff --git a/test_src/test_proj/settings.py b/test_src/test_proj/settings.py index 71c3187a..c164e3f4 100644 --- a/test_src/test_proj/settings.py +++ b/test_src/test_proj/settings.py @@ -168,3 +168,4 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media') REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] += ('rest_framework.authentication.BasicAuthentication',) +CELERY_TASK_SERIALIZER = 'msgpack' diff --git a/vstutils/api/renderers.py b/vstutils/api/renderers.py index 878930cf..989a6736 100644 --- a/vstutils/api/renderers.py +++ b/vstutils/api/renderers.py @@ -46,5 +46,5 @@ def render(self, data, accepted_media_type=None, renderer_context=None): Renders *obj* into serialized MessagePack. """ if data is None: - return '' # nocv + return b'' # nocv return ormsgpack.packb(data, default=ORJSONRenderer.default, option=self.options) diff --git a/vstutils/environment.py b/vstutils/environment.py index 5c2ad137..586c289c 100644 --- a/vstutils/environment.py +++ b/vstutils/environment.py @@ -4,6 +4,7 @@ import functools import orjson +import ormsgpack _default_settings = { @@ -56,7 +57,7 @@ def get_celery_app(name=None, **kwargs): # nocv """ from celery import Celery from kombu.serialization import register as serialize_registrator - from .api.renderers import ORJSONRenderer + from .api.renderers import ORJSONRenderer, MsgpackRenderer from . import drivers # noqa: F401 prepare_environment(**kwargs) @@ -64,11 +65,20 @@ def get_celery_app(name=None, **kwargs): # nocv # Override json encoder/decoder for tasks json_encoder = functools.partial(ORJSONRenderer().render, media_type=ORJSONRenderer.media_type) + msgpack_encoder = functools.partial(MsgpackRenderer().render, accepted_media_type=MsgpackRenderer.media_type) serialize_registrator( 'json', - encoder=lambda x: json_encoder(x).decode('utf-8'), + encoder=json_encoder, decoder=orjson.loads, - content_type=ORJSONRenderer.media_type + content_type=ORJSONRenderer.media_type, + content_encoding='binary', + ) + serialize_registrator( + 'msgpack', + encoder=msgpack_encoder, + decoder=ormsgpack.unpackb, + content_type='application/x-msgpack', + content_encoding='binary', ) celery_app = Celery(name, task_cls='vstutils.tasks:TaskClass') From a5340206c485a99f7396be7bfcd128bb5e2d320c Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Thu, 21 Dec 2023 22:41:26 -0800 Subject: [PATCH 09/20] Fix(backend): Improved tarantool support with delays and expiration celery results. --- test_src/test_proj/settings.py | 1 - test_src/test_proj/tests.py | 2 +- vstutils/drivers/cache.py | 6 ++-- vstutils/drivers/kombu.py | 54 +++++++++++++++++++++++++++++++--- vstutils/settings.py | 10 +++---- 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/test_src/test_proj/settings.py b/test_src/test_proj/settings.py index c164e3f4..71c3187a 100644 --- a/test_src/test_proj/settings.py +++ b/test_src/test_proj/settings.py @@ -168,4 +168,3 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media') REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] += ('rest_framework.authentication.BasicAuthentication',) -CELERY_TASK_SERIALIZER = 'msgpack' diff --git a/test_src/test_proj/tests.py b/test_src/test_proj/tests.py index b5f27f2c..15c74145 100644 --- a/test_src/test_proj/tests.py +++ b/test_src/test_proj/tests.py @@ -5721,7 +5721,7 @@ def test_file_reader(self): self.assertEqual(settings.CELERYD_PREFETCH_MULTIPLIER, 1) self.assertEqual(settings.CELERYD_MAX_TASKS_PER_CHILD, 1) self.assertEqual(settings.CELERY_BROKER_HEARTBEAT, 10) - self.assertEqual(settings.CELERY_RESULT_EXPIRES, 1) + self.assertEqual(settings.CELERY_RESULT_EXPIRES, 86400) self.assertEqual(settings.CREATE_INSTANCE_ATTEMPTS, 10) self.assertEqual(settings.CONCURRENCY, 4) diff --git a/vstutils/drivers/cache.py b/vstutils/drivers/cache.py index 12483310..13359c89 100644 --- a/vstutils/drivers/cache.py +++ b/vstutils/drivers/cache.py @@ -133,8 +133,10 @@ def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None): def delete(self, key, version=None): key = self.make_and_validate_key(key, version=version) - _, value, _ = self.space.delete(key).data - return self._serializer.loads(value) + data = self.space.delete(key).data + if not data: + return + return self._serializer.loads(data[0][1]) def has_key(self, key, version=None): key = self.make_and_validate_key(key, version=version) diff --git a/vstutils/drivers/kombu.py b/vstutils/drivers/kombu.py index 0d8a9d40..92937117 100644 --- a/vstutils/drivers/kombu.py +++ b/vstutils/drivers/kombu.py @@ -1,5 +1,7 @@ +import time import base64 import logging +from datetime import datetime from queue import Empty import tarantool @@ -7,6 +9,7 @@ from kombu.transport import virtual, base from kombu.utils.url import url_to_parts as parse_url from celery.backends.base import KeyValueStoreBackend +from celery.utils.time import maybe_make_aware logger = logging.getLogger('kombu.transport.tarantool') @@ -111,7 +114,23 @@ def _get(self, queue, timeout=None): def _put(self, queue, message, **kwargs): """ Put `message` onto `queue`. """ - self.client_eval(queue, f"put('{base64.b64encode(packb(message)).decode('utf-8')}')") + headers = message.get('headers', {}) + properties = message.get('properties', {}) + options = {'pri': properties.get('priority', 0)} + + now = maybe_make_aware(datetime.now()) + if (eta := headers.get('eta')) and (eta_date := datetime.fromisoformat(eta)) > now: + options['delay'] = max(round((eta_date - now).total_seconds() - 0.5), 0) + ops = [ + f'{k} = {v}' + for k, v in options.items() + if v + ] + + self.client_eval( + queue, + f"put('{base64.b64encode(packb(message)).decode('utf-8')}', {{ {','.join(ops)} }})" + ) def _purge(self, queue): """ Remove all messages from `queue`. """ @@ -193,9 +212,10 @@ class TarantoolBackend(KeyValueStoreBackend): supports_native_join = True persistent = False - def __init__(self, url=None, *args, **kwargs): + def __init__(self, url=None, expires=None, *args, **kwargs): super().__init__(*args, **kwargs) _, host, port, username, password, vhost, _ = parse_url(url) + self.expires = self.prepare_expires(expires, type=int) self.client = tarantool.connect(host, port, user=username, password=password) self.space_name = f"{vhost + '_' if vhost else ''}celery_backend" self.client.eval( @@ -207,6 +227,7 @@ def __init__(self, url=None, *args, **kwargs): "format = {" "{name = 'id', type = 'string'}, " # noqa: E131 "{name = 'value', type = 'string'}, " + "{name = 'exp', type = 'number'}, " "} " "}" ")" @@ -214,6 +235,30 @@ def __init__(self, url=None, *args, **kwargs): self.client.eval( f"box.space.{self.space_name}:create_index('primary', {{ parts = {{ 'id' }}, if_not_exists = true }})" ) + lower_name = self.space_name.lower() + self.client.eval( + f""" + if {lower_name}_is_expired then return 0 end + + clock = require('clock') + expirationd = require("expirationd") + + function {lower_name}_is_expired(args, tuple) + return tuple[3] > -1 and tuple[3] < clock.realtime() + end + + function {lower_name}_delete_tuple(space, args, tuple) + box.space[space]:delete{{tuple[1]}} + end + + expirationd.start("{lower_name}_clean_results", '{self.space_name}', {lower_name}_is_expired, {{ + process_expired_tuple = {lower_name}_delete_tuple, + args = nil, + tuples_per_iteration = 50, + full_scan_time = 3600 + }}) + """ + ) self.space = self.client.space(self.space_name) def get(self, key): @@ -236,7 +281,7 @@ def mget(self, keys): keys = (f"'{k.decode('utf-8')}'" for k in keys) data = self.client.execute( - f'select * from "{self.space_name}" where "id" in ({", ".join(keys)})' # nosec B608 + f'select "id", "value" from "{self.space_name}" where "id" in ({", ".join(keys)})' # nosec B608 ).data if not data: return [] @@ -250,7 +295,8 @@ def set(self, key, value): :param value: The value to be stored. """ - self.space.replace((key.decode('utf-8'), value)) + exp = int(time.time() + self.expires + 0.5) if self.expires else -1 + self.space.replace((key.decode('utf-8'), value, exp)) def delete(self, key): """ diff --git a/vstutils/settings.py b/vstutils/settings.py index 2334bc10..964c8d6b 100644 --- a/vstutils/settings.py +++ b/vstutils/settings.py @@ -351,7 +351,7 @@ class RPCSection(BaseAppendSection): 'prefetch_multiplier': ConfigIntType, 'max_tasks_per_child': ConfigIntType, 'heartbeat': ConfigIntType, - 'results_expiry_days': ConfigIntType, + 'results_expiry': ConfigIntSecondsType, 'create_instance_attempts': ConfigIntType, 'enable_worker': ConfigBoolType, 'task_send_sent_event': ConfigBoolType, @@ -538,7 +538,7 @@ class DjangoEnv(environ.Env): 'prefetch_multiplier': env.int(f'{ENV_NAME}_RPC_PREFETCH_MULTIPLIER', default=1), 'max_tasks_per_child': env.int(f'{ENV_NAME}_RPC_MAX_TASKS_PER_CHILD', default=1), 'heartbeat': env.int(f'{ENV_NAME}_RPC_HEARTBEAT', default=10), - 'results_expiry_days': env.int(f'{ENV_NAME}_RPC_RESULTS_EXPIRY_DAYS', default=1), + 'results_expiry': env.int(f'{ENV_NAME}_RPC_RESULTS_EXPIRY', default=24 * 60 * 60), 'create_instance_attempts': env.int(f'{ENV_NAME}_RPC_CREATE_INSTANCE_ATTEMPTS', default=10), 'default_delivery_mode': "persistent", 'broker_transport_options': {}, @@ -1237,9 +1237,9 @@ def parse_db(params): CELERYD_PREFETCH_MULTIPLIER = rpc["prefetch_multiplier"] CELERYD_MAX_TASKS_PER_CHILD = rpc["max_tasks_per_child"] CELERY_BROKER_HEARTBEAT = rpc["heartbeat"] - CELERY_ACCEPT_CONTENT = ['pickle', 'json'] - CELERY_TASK_SERIALIZER = 'pickle' - CELERY_RESULT_EXPIRES = rpc["results_expiry_days"] + CELERY_ACCEPT_CONTENT = ['pickle', 'json', 'msgpack'] + CELERY_TASK_SERIALIZER = 'msgpack' + CELERY_RESULT_EXPIRES = rpc["results_expiry"] CELERY_SEND_SENT_EVENT = rpc["task_send_sent_event"] CELERY_SEND_EVENTS = rpc["worker_send_task_events"] CELERY_DEFAULT_DELIVERY_MODE = rpc["default_delivery_mode"] From 994478308f1d0f73dac56354231a198a93a9093b Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Fri, 22 Dec 2023 13:10:11 -0800 Subject: [PATCH 10/20] Fix(backend): Update new project generation templates. --- .github/workflows/review.yml | 2 +- doc/config.rst | 2 +- doc/locale/ru/LC_MESSAGES/config.po | 4 +- doc/quickstart.rst | 6 +- test_src/test_proj/tests.py | 2 +- tox.ini | 9 +- vstutils/management/commands/newproject.py | 4 +- .../templates/newproject/.coveragerc.template | 18 - vstutils/templates/newproject/.pep8.template | 4 - .../templates/newproject/MANIFEST.in.template | 2 +- .../project_name/__init__.py.template | 1 - .../newproject/pyproject.toml.template | 115 ++++++ .../newproject/requirements.txt.template | 9 + .../templates/newproject/setup.cfg.template | 35 -- .../templates/newproject/setup.py.template | 383 +----------------- .../templates/newproject/tox.ini.template | 83 ++-- .../newproject/tox_build.ini.template | 45 +- 17 files changed, 211 insertions(+), 513 deletions(-) delete mode 100644 vstutils/templates/newproject/.coveragerc.template delete mode 100644 vstutils/templates/newproject/.pep8.template create mode 100644 vstutils/templates/newproject/pyproject.toml.template delete mode 100644 vstutils/templates/newproject/setup.cfg.template diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index a218bcae..62ca4789 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -40,7 +40,7 @@ jobs: runs-on: "ubuntu-22.04" strategy: matrix: - python: [3.8, "3.11"] + python: [3.8, "3.12"] steps: - name: Checkout the source code diff --git a/doc/config.rst b/doc/config.rst index ceac81db..a8e54826 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -282,7 +282,7 @@ are also supported (with the corresponding types): * **worker_send_task_events** - :celery_docs:`CELERY_DEFAULT_DELIVERY_MODE ` VST Utils provides seamless support for using Tarantool as a transport for Celery, allowing efficient and reliable message passing between distributed components. -To enable this feature, ensure that the Tarantool server has the `queue` module installed. +To enable this feature, ensure that the Tarantool server has the `queue` and `expirationd` modules installed. To configure the connection, use the following example URL: ``tarantool://guest@localhost:3301/rpc`` diff --git a/doc/locale/ru/LC_MESSAGES/config.po b/doc/locale/ru/LC_MESSAGES/config.po index 5ca305e4..0add414d 100644 --- a/doc/locale/ru/LC_MESSAGES/config.po +++ b/doc/locale/ru/LC_MESSAGES/config.po @@ -708,11 +708,11 @@ msgid "" "VST Utils provides seamless support for using Tarantool as a transport " "for Celery, allowing efficient and reliable message passing between " "distributed components. To enable this feature, ensure that the Tarantool" -" server has the `queue` module installed." +" server has the `queue` and `expirationd` modules installed." msgstr "" "VST Utils так же предоставляет поддержку для использования Tarantool сервера как транспорта " "в Celery, обеспечивая эффективную и надежную передачу сообщений между распределёнными компонентами. " -"Для использования этой возможности на сервере Tarantool должен быть установлен модуль `queue`." +"Для использования этой возможности на сервере Tarantool должны быть установлены модули `queue` и `expirationd`." #: ../../config.rst:287 msgid "" diff --git a/doc/quickstart.rst b/doc/quickstart.rst index f1cb30af..cef4004d 100644 --- a/doc/quickstart.rst +++ b/doc/quickstart.rst @@ -83,7 +83,6 @@ Throughout this tutorial, we’ll go through a creation of a basic poll applicat .. sourcecode:: /{{app_dir}}/{{app_name}} - ├── .coveragerc ├── frontend_src │   ├── app │   │   └── index @@ -92,11 +91,10 @@ Throughout this tutorial, we’ll go through a creation of a basic poll applicat │   └── .prettierrc ├── MANIFEST.in ├── package.json - ├── .pep8 ├── README.rst ├── requirements-test.txt ├── requirements.txt - ├── setup.cfg + ├── pyproject.toml ├── setup.py ├── {{app_name}} │   ├── __init__.py @@ -120,7 +118,7 @@ Throughout this tutorial, we’ll go through a creation of a basic poll applicat * **README.rst** - default README file for your application (this file includes base commands for starting/stopping your application); * **requirements-test.txt** - file with list of requirements for test environment; * **requirements.txt** - file with list of requirements for your application; - * **setup.cfg** - this file is used for building installation package; + * **pyproject.toml** - this file is used for building installation package; * **setup.py** - this file is used for building installation package; * **test.py** - this file is used for tests creation; * **tox.ini** - this file is used for tests execution; diff --git a/test_src/test_proj/tests.py b/test_src/test_proj/tests.py index 15c74145..18bee3f7 100644 --- a/test_src/test_proj/tests.py +++ b/test_src/test_proj/tests.py @@ -218,7 +218,7 @@ def test_startproject(self): self.assertTrue(os.path.exists(self.project_place + '/test_project/settings.py')) self.assertTrue(os.path.isfile(self.project_place + '/test_project/settings.py')) self.assertTrue(os.path.isfile(self.project_place + '/setup.py')) - self.assertTrue(os.path.isfile(self.project_place + '/setup.cfg')) + self.assertTrue(os.path.isfile(self.project_place + '/pyproject.toml')) self.assertTrue(os.path.isfile(self.project_place + '/requirements.txt')) self.assertTrue(os.path.isfile(self.project_place + '/README.rst')) self.assertTrue(os.path.isfile(self.project_place + '/MANIFEST.in')) diff --git a/tox.ini b/tox.ini index 507a7d61..3939c66f 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ allowlist_externals = [gh-actions] python = 3.8: flake,mypy,pylint,bandit,py38-django42-install - 3.11: py311-django42-coverage,builddoc + 3.12: py312-django42-coverage,builddoc [testenv] passenv = * @@ -39,15 +39,14 @@ allowlist_externals = rm ls bash + head pwd yarn commands = pip uninstall vstutils -y install: rm -rfv {envdir}/dist/ - install: pip install vstcompile - install: python -m build --no-isolation --sdist --outdir {envdir}/dist/ {toxinidir} - install: pip uninstall vstcompile -y - install: bash -ec "pip install -U {envdir}/dist/$(ls {envdir}/dist)[all]" + install: pip wheel {toxinidir} -w {envdir}/dist/ --no-deps + install: bash -ec "pip install -U $(ls {envdir}/dist/*.whl | head -1)[all]" pwd install: python -m {env:EXECUTE_ARGS} {posargs} coverage: pip install -U -e ..[all] diff --git a/vstutils/management/commands/newproject.py b/vstutils/management/commands/newproject.py index 0efbd2d6..b4cc78d7 100644 --- a/vstutils/management/commands/newproject.py +++ b/vstutils/management/commands/newproject.py @@ -38,15 +38,13 @@ class Command(BaseCommand): 'web.ini': Path('web.ini'), 'wsgi.py': Path('wsgi.py'), }, - '.coveragerc': Path('.coveragerc'), '.gitignore': Path('.gitignore'), - '.pep8': Path('.pep8'), 'MANIFEST.in': Path('MANIFEST.in'), 'package.json': Path('package.json'), 'README.rst': Path('README.rst'), 'requirements.txt': Path('requirements.txt'), 'requirements-test.txt': Path('requirements-test.txt'), - 'setup.cfg': Path('setup.cfg'), + 'pyproject.toml': Path('pyproject.toml'), 'setup.py': Path('setup.py'), 'test.py': Path('test.py'), 'tox.ini': Path('tox.ini'), diff --git a/vstutils/templates/newproject/.coveragerc.template b/vstutils/templates/newproject/.coveragerc.template deleted file mode 100644 index e620ffa3..00000000 --- a/vstutils/templates/newproject/.coveragerc.template +++ /dev/null @@ -1,18 +0,0 @@ -[run] -source = {{project_name}} -parallel = True -concurrency = - thread - multiprocessing -omit = - *.tox/* - *setup.py - test.py - */{{project_name}}/__main__.py - */{{project_name}}/wsgi.py - -[report] -show_missing = True -exclude_lines = - pragma: no cover - nocv diff --git a/vstutils/templates/newproject/.pep8.template b/vstutils/templates/newproject/.pep8.template deleted file mode 100644 index a3e0f11f..00000000 --- a/vstutils/templates/newproject/.pep8.template +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -ignore = E221,E222,E121,E123,E126,E226,E24,E704,E116,E731,E722,E741,W504,B001,B008,C812,C815 -exclude = ./{{project_name}}/*/migrations/*,./{{project_name}}/settings*.py,.tox/*,{{project_name}}/__main__.py, -max-line-length=120 diff --git a/vstutils/templates/newproject/MANIFEST.in.template b/vstutils/templates/newproject/MANIFEST.in.template index 275425c3..d72b850c 100644 --- a/vstutils/templates/newproject/MANIFEST.in.template +++ b/vstutils/templates/newproject/MANIFEST.in.template @@ -1,5 +1,5 @@ include README.rst LICENSE -include setup.cfg +include pyproject.toml include requirements.txt recursive-include {{project_name}}/*/templates * recursive-include {{project_name}}/templates * diff --git a/vstutils/templates/newproject/project_name/__init__.py.template b/vstutils/templates/newproject/project_name/__init__.py.template index 663b5e0f..02fdd6d0 100644 --- a/vstutils/templates/newproject/project_name/__init__.py.template +++ b/vstutils/templates/newproject/project_name/__init__.py.template @@ -9,7 +9,6 @@ __version__ = '1.0.0' settings = { "VST_PROJECT": '{{ project_name }}', "VST_ROOT_URLCONF": 'vstutils.urls', - "VST_WSGI": 'vstutils.wsgi', "VST_PROJECT_GUI_NAME": "{{ project_gui_name }}", "DJANGO_SETTINGS_MODULE": '{{ project_name }}.settings', } diff --git a/vstutils/templates/newproject/pyproject.toml.template b/vstutils/templates/newproject/pyproject.toml.template new file mode 100644 index 00000000..3b522cf7 --- /dev/null +++ b/vstutils/templates/newproject/pyproject.toml.template @@ -0,0 +1,115 @@ +[build-system] +requires = ["setuptools>=61.2", "vstcompile~=2.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "{{project_name}}" +keywords = ["spa", "vue", "pwa", "web", "app"] +# license = {text = "Apache License 2.0" } +classifiers = [ + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 4.2", + "Operating System :: POSIX", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Software Development :: User Interfaces", + "Topic :: Utilities", +] +requires-python = ">=3.8" +dynamic = ["version", "dependencies", "optional-dependencies"] + +[project.urls] +"Issue Tracker" = "https://gitlab.com/vstconsulting/vstutils/issues" +Source = "https://gitlab.com/vstconsulting/vstutils" +Releases = "https://pypi.org/project/vstutils/#history" +Documentation = "https://vstutils.vstconsulting.net/" + +[project.readme] +file = "README.rst" +content-type = "text/x-rst" + +[project.scripts] +vstutilsctl = "vstutils.__main__:cmd_execution" + +[tool.setuptools] +zip-safe = false +include-package-data = true + +[tool.setuptools.dynamic] +version = { attr = "{{project_name}}.__version__" } + +[tool.setuptools.packages.find] +include = ['{{project_name}}', '{{project_name}}.*'] +namespaces = false + +[tool.flake8] +ignore = "E221,E222,E121,E123,E126,E226,E24,E704,E116,E731,E722,E741,W504,B001,B008,B023,C812,C815,CFQ004,B019,I100" +exclude = "./{{project_name}}/migrations/*,./{{project_name}}/settings*.py,.tox/*,{{project_name}}/__main__.py" +max-line-length = 120 +import-order-style = 'pep8' + +[tool.coverage.run] +# branch = true +source = [ + '{{project_name}}', +] +parallel = true +concurrency = [ + 'thread', + 'multiprocessing', +] +omit = [ + '*.tox/*', + '*setup.py', + '*/{{project_name}}/__main__.py', +] + +[tool.coverage.report] +fail_under = 100 +show_missing = true +exclude_lines = [ + 'pragma: no cover', + 'nocv', + 'raise NotImplementedError', + 'if _t.TYPE_CHECKING:', +] + +[tool.bandit] +skips = [] + +[tool.mypy] +python_version = 3.8 +#strict = true +allow_redefinition = true +check_untyped_defs = true +warn_unused_ignores = true +warn_redundant_casts = true +warn_unused_configs = true +disallow_untyped_decorators = true +implicit_reexport = true +show_error_codes = true +disable_error_code = "attr-defined,assignment,index,operator,arg-type,misc,call-overload,union-attr,valid-type,func-returns-value" +plugins = [ + "mypy_django_plugin.main", + "mypy_drf_plugin.main", +] + +[tool.django-stubs] +django_settings_module = "{{project_name}}.settings" + +[[tool.mypy.overrides]] +module = [ + "PIL.*", + "configparserc.*", +] +ignore_missing_imports = true diff --git a/vstutils/templates/newproject/requirements.txt.template b/vstutils/templates/newproject/requirements.txt.template index d587af02..6062ba96 100644 --- a/vstutils/templates/newproject/requirements.txt.template +++ b/vstutils/templates/newproject/requirements.txt.template @@ -1,3 +1,12 @@ # {{project_name}} requirements ############################### vstutils[prod]=={{vstutils_version}} + +# Add extra for features: +# rpc - for celery ecosystem +# sqs - for celery capability with AWS SQS +# ldap - for support ldap authorization +# pil - for auto resizing images via validators +# boto3 - for s3 media storages (AWS S3, Minio, etc) +# stubs - for mypy checks +# doc - for standard sphinx requirements to build documentation diff --git a/vstutils/templates/newproject/setup.cfg.template b/vstutils/templates/newproject/setup.cfg.template deleted file mode 100644 index caf0b620..00000000 --- a/vstutils/templates/newproject/setup.cfg.template +++ /dev/null @@ -1,35 +0,0 @@ -[metadata] -name = {{project_name}} -version = attr: {{project_name}}.__version__ -# description = -long_description = file: README.rst -long_description_content_type = text/x-rst -# license = Apache Software License -keywords = web, app -classifiers = - Environment :: Web Environment - Framework :: Django - Framework :: Django :: 3.2 - Operating System :: POSIX - Programming Language :: Cython - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Topic :: Software Development :: Libraries - Topic :: Internet :: WWW/HTTP - Topic :: Internet :: WWW/HTTP :: Dynamic Content - Topic :: Internet :: WWW/HTTP :: WSGI - Topic :: Utilities - -[options] -zip_safe = False -include_package_data = True -python_requires = >=3.6, <4.0 - -[aliases] diff --git a/vstutils/templates/newproject/setup.py.template b/vstutils/templates/newproject/setup.py.template index 272c38e2..d8135626 100644 --- a/vstutils/templates/newproject/setup.py.template +++ b/vstutils/templates/newproject/setup.py.template @@ -1,389 +1,10 @@ -# Compilation block -######################################################################################## -import re import os import sys -import subprocess -import fnmatch -import codecs -import gzip -import shutil +from vstcompile import make_setup, load_requirements # allow setup.py to be run from any path os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) -from setuptools import find_packages, setup, errors, Command -from setuptools.extension import Extension -from setuptools.command.sdist import sdist as _sdist -from setuptools.command.build_py import build_py as build_py_orig -from setuptools.command.install_lib import install_lib as _install_lib -try: - from Cython.Build import cythonize, build_ext as _build_ext -except ImportError: - has_cython = False -else: - has_cython = True - -try: - from sphinx.setup_command import BuildDoc - import sphinx # noqa: F401 - has_sphinx = True -except ImportError: - has_sphinx = False - - -ignored_keys = ['-h', '--help', '--version'] -is_help = any([a for a in ignored_keys if a in sys.argv]) -is_develop = 'develop' in sys.argv -is_build = (any([a for a in ['compile', 'bdist_wheel', 'bdist'] if a in sys.argv]) or is_develop) and not is_help - - -def get_discription(file_path='README.rst', folder=os.getcwd()): - with codecs.open("{}/{}".format(folder, file_path), 'r', encoding='utf-8') as readme: - return readme.read() - - -def load_requirements(file_name, folder=os.getcwd()): - with codecs.open(os.path.join(folder, file_name), 'r', encoding='utf-8')as req_file: - return req_file.read().strip().split('\n') - - -def get_file_ext(ext): - file_types = [".py", ".pyx", ".c", '.cpp'] if has_cython else [".c", '.cpp', ".py"] - for ftype in file_types: - fname = ext.replace(".", "/") + ftype - if os.path.exists(fname): - return fname - return None - - -def listfiles(folder): - if not isinstance(folder, (list, tuple)): - folder = [folder] - folder = filter(lambda p: os.path.isdir(p), folder) - for one_folder in folder: - for root, folders, files in os.walk(one_folder): - for filename in folders + files: - yield os.path.join(root, filename) - - -def clear_old_extentions(extensions_list, packages): - for filename in listfiles(packages): - _filename, _f_ext = os.path.splitext(filename) - if os.path.isdir(_filename) or _f_ext not in ['.c', '.cpp']: - continue - has_py = ( - os.path.exists('{}.py'.format(_filename)) or - os.path.exists('{}.pyx'.format(_filename)) - ) - - if has_py and filename.replace('/', '.').replace(_f_ext, '') in extensions_list: - print('Removing old extention [{}].'.format(filename)) - os.remove(filename) - - -def make_extention(module_name, files, extra_compile_args, main_include_dir=os.path.join(os.getcwd(), 'include')): - include_dirs = list(filter( - lambda f: bool(f) and os.path.exists(f) and os.path.isdir(f), - [os.path.join(module_name.split('.')[0], 'include'), main_include_dir] - )) - - return Extension( - module_name, files, - extra_compile_args=extra_compile_args, - include_dirs=include_dirs - ) - - -def make_extensions(extensions_list, packages): - if not isinstance(extensions_list, list): - raise Exception("Extension list should be `list`.") - - if not is_help: - clear_old_extentions(extensions_list, packages) - - extensions_dict = {} - for ext in extensions_list: - files = [] - module_name = ext - if isinstance(ext, (list, tuple)): - module_name = ext[0] - for file_module in ext[1]: - file_name = get_file_ext(file_module) - files += [file_name] if file_name else [] - else: - file_name = get_file_ext(ext) - files += [file_name] if file_name else [] - if files: - extensions_dict[module_name] = files - - extra_compile_args = [ - '-g0', '-ggdb1', - "-fno-strict-aliasing", - "-fno-var-tracking-assignments", - "-pipe", "-std=c99", '-Werror=sign-compare', - ] - if 'compile' in sys.argv: - extra_compile_args.append("-DBUILD_FROM_SOURCE") - ext_modules = list( - make_extention(m, f, extra_compile_args) - for m, f in extensions_dict.items() - ) - ext_count = len(ext_modules) - nthreads = ext_count if ext_count < 10 else 10 - - language_level = 3 - if is_help: - pass - elif has_cython and ('compile' in sys.argv or 'bdist_wheel' in sys.argv or 'build_ext' in sys.argv): - cy_kwargs = dict( - nthreads=nthreads, - force=True, - language_level=language_level, - compiler_directives=dict( - linetrace='CYTHON_TRACE_NOGIL' in sys.argv, - profile=True, - c_string_type='str', - c_string_encoding='utf8' - ), - ) - return cythonize(ext_modules, **cy_kwargs), extensions_dict - return ext_modules, extensions_dict - - -def minify_js_file(js_file, jsmin_func): - return jsmin_func(js_file, quote_chars="'\"`") - - -def minify_css_file(css_file, cssmin_func): - return cssmin_func(css_file) - - -def minify_static_files(base_dir, files, exclude=None): - exclude = exclude or [] - patterns = dict() - try: - from jsmin import jsmin as jsmin_func - patterns['*.js'] = (minify_js_file, jsmin_func) - except: - pass - try: - from csscompressor import compress as csscompressor_func - patterns['*.css'] = (minify_css_file, csscompressor_func) - except: - pass - - regex_exclude = [re.compile(r, re.MULTILINE) for r in exclude] - - for fnext, funcs in patterns.items(): - for fext_file in filter(lambda f: fnmatch.fnmatch(f, fnext), files): - if fnmatch.fnmatch(fext_file, '*.min.*'): - continue - fext_file = os.path.join(base_dir, fext_file) - if os.path.exists(fext_file): - if not any(filter(lambda fp: bool(fp.search(fext_file)), regex_exclude)): - func, subfunc = funcs - with codecs.open(fext_file, 'r', encoding='utf-8') as static_file_fd: - minified = func(static_file_fd.read(), subfunc) - with codecs.open(fext_file, 'w', encoding='utf-8') as static_file_fd: - static_file_fd.write(minified) - print('Minfied file {fext_file}.'.format(fext_file=fext_file)) - with open(fext_file, 'rb') as f_in: - with gzip.open("{}.gz".format(fext_file), 'wb') as f_out: - shutil.copyfileobj(f_in, f_out) - print('Compressed file {fext_file}.'.format(fext_file=fext_file)) - - -def compile_py_func(fullname, compile_file_func): - if compile_file_func(fullname, ddir=os.path.dirname(fullname), legacy=True, optimize=0): - os.remove(fullname) - - -def compile_python_sources(base_dir, files, exclude=None): - exclude = exclude or [] - patterns = dict() - try: - from compileall import compile_file - patterns['*.py'] = (compile_py_func, compile_file) - except: - pass - - regex_exclude = [re.compile(r, re.MULTILINE) for r in exclude] - - for fnext, funcs in patterns.items(): - for fext_file in filter(lambda f: fnmatch.fnmatch(f, fnext), files): - fext_file = os.path.join(base_dir, fext_file) - if os.path.exists(fext_file): - if not any(filter(lambda fp: bool(fp.search(fext_file)), regex_exclude)): - func, subfunc = funcs - funcs[0](fext_file, funcs[1]) - print('Compiled {fext_file}.'.format(fext_file=fext_file)) - - -class _Compile(_sdist): - extensions_dict = dict() - static_exclude = [] - - def __filter_files(self, files): - for _files in self.extensions_dict.values(): - for file in _files: - if file in files: - files.remove(file) - return files - - def make_release_tree(self, base_dir, files): - if has_cython: - files = self.__filter_files(files) - _sdist.make_release_tree(self, base_dir, files) - minify_static_files(base_dir, files, self.static_exclude) - - def run(self): - return _sdist.run(self) - - -class GithubRelease(Command): - ''' - Make release on github via githubrelease - ''' - description = 'Make release on github via githubrelease' - - user_options = [ - ('body=', 'b', 'Body message.'), - ('assets=', 'a', 'Release assets patterns.'), - ('repo=', 'r', 'Repository for release.'), - ('release=', 'R', 'Release version.'), - ('dry-run=', 'd', 'Dry run.'), - ('publish=', 'p', 'Publish release or just create draft.'), - ] - - def initialize_options(self): - self.body = None or os.getenv('CI_COMMIT_DESCRIPTION', None) - self.assets = None - self.repo = None - self.dry_run = False - self.publish = False - self.release = None or self.distribution.metadata.version - - def finalize_options(self): - if self.repo is None: - raise Exception("Parameter --repo is missing") - if self.release is None: - raise Exception("Parameter --release is missing") - self._gh_args = (self.repo, self.release) - self._gh_kwargs = dict( - publish=self.publish, name=self.release, dry_run=self.dry_run - ) - if self.assets: - assets = self.assets.format(release=self.release) - assets = list(filter(bool, assets.split('\n'))) - self._gh_kwargs['asset_pattern'] = assets - if self.body: - self._gh_kwargs['body'] = self.body - - def run(self): - from github_release import gh_release_create - gh_release_create(*self._gh_args, **self._gh_kwargs) - - -class build_py(build_py_orig): - exclude = [] - compile_extentions_types = ['.py', '.pyx'] - wheel_extentions_types = ['.c', '.cpp'] + compile_extentions_types - - def _filter_modules(self, module_tuple): - pkg, mod, file = module_tuple - try: - file_name, file_ext = os.path.splitext(file) - module_name = file_name.replace('/', '.') - except: - return True - if 'bdist_wheel' in sys.argv: - exclude_list = self.wheel_extentions_types - elif 'compile' in sys.argv: - exclude_list = self.compile_extentions_types - else: - return True - if module_name in self.exclude and file_ext in exclude_list: - return False - return True - - def find_package_modules(self, package, package_dir): - modules = build_py_orig.find_package_modules(self, package, package_dir) - return list(filter(self._filter_modules, modules)) - - -class install_lib(_install_lib): - exclude = [] - static_exclude = [] - compile_exclude = [] - - def _filter_files_with_ext(self, filename): - _filename, _fext = os.path.splitext(filename) - if _fext in build_py.wheel_extentions_types: - return True - return False - - def install(self): - result = _install_lib.install(self) - files = list(listfiles(self.install_dir)) - so_extentions = list(filter(lambda f: fnmatch.fnmatch(f, '*.so'), files)) - for source in filter(self._filter_files_with_ext, files): - _source_name, _source_ext = os.path.splitext(source) - if any(filter(lambda f: fnmatch.fnmatch(f, _source_name+"*.so"), so_extentions)): - print('Removing extention sources [{}].'.format(source)) - os.remove(source) - minify_static_files('', files, self.static_exclude) - if os.getenv('BUILD_COMPILE', '') == 'true': - compile_python_sources('', files, self.compile_exclude) - return result - - -def get_compile_command(extensions_dict=None): - extensions_dict = extensions_dict or dict() - compile_class = _Compile - compile_class.extensions_dict = extensions_dict - return compile_class - - -def make_setup(**opts): - if 'packages' not in opts: - opts['packages'] = find_packages() - ext_modules_list = opts.pop('ext_modules_list', []) - ext_mod, ext_mod_dict = make_extensions(ext_modules_list, opts['packages']) - opts['ext_modules'] = opts.get('ext_modules', []) + ext_mod - cmdclass = opts.get('cmdclass', dict()) - static_exclude = opts.pop('static_exclude_min', []) - if 'compile' not in cmdclass: - compile_class = get_compile_command(ext_mod_dict) - compile_class.static_exclude = static_exclude - cmdclass.update({"compile": get_compile_command(ext_mod_dict)}) - if has_cython: - build_py.exclude = ext_modules_list - install_lib.static_exclude = static_exclude - install_lib.compile_exclude = opts.pop('compile_modules_exclude', []) - cmdclass.update({ - 'build_ext': _build_ext, - 'build_py': build_py, - 'install_lib': install_lib - }) - if has_sphinx and 'build_sphinx' not in cmdclass: - cmdclass['build_sphinx'] = BuildDoc - cmdclass['githubrelease'] = GithubRelease - opts['cmdclass'] = cmdclass - - webpack_path = os.path.join(os.getcwd(), 'webpack.config.js') - if os.path.exists(webpack_path) and is_build and os.environ.get('DONT_YARN', "") != 'true': - try: - subprocess.check_call(['yarn', 'install', '--pure-lockfile', '--mutex network'], stdout=sys.stdout, stderr=sys.stderr) - subprocess.check_call(['yarn', 'devBuild' if is_develop else 'build'], stdout=sys.stdout, stderr=sys.stderr) - except Exception as err: - raise errors.CompileError(str(err)) - - setup(**opts) - -######################################################################################## -# end block - ext_list = [ # Modules that should be compiled to C extentions # Allowed '*.py', '*.pyx' and '*.c' files. @@ -406,8 +27,6 @@ kwargs = dict( }, dependency_links=[ ], - project_urls={ - }, ) make_setup(**kwargs) diff --git a/vstutils/templates/newproject/tox.ini.template b/vstutils/templates/newproject/tox.ini.template index c42fcb36..ae448c48 100644 --- a/vstutils/templates/newproject/tox.ini.template +++ b/vstutils/templates/newproject/tox.ini.template @@ -1,7 +1,7 @@ [tox] -envlist = flake,py36-coverage,py38-install +envlist = flake,py312-coverage,py38-install skipsdist = True -whitelist_externals = +allowlist_externals = rm bash @@ -13,78 +13,87 @@ passenv = DJANGO_LOG_LEVEL PYTHONPATH CC -whitelist_externals = +allowlist_externals = rm ls ln bash + head mkdir + yarn commands = - pip install -U pip - pip uninstall {{project_name}} -y - install: rm -rfv {envdir}/dist/ - install: python setup.py compile --dist-dir {envdir}/dist/ - install: bash -c "pip install {envdir}/dist/{{project_name}}-$(python -c 'import {{project_name}}; print({{project_name}}.__version__)').tar.gz" - install: rm -f {envdir}/test.py {envdir}/test.pyc - install: bash -c "cd {envdir} && ln -s {toxinidir}/test.py && python -m {{project_name}} test -v 2 --failfast --parallel 8" - coverage: python setup.py install_egg_info - coverage: pip install -U -e .[test] - coverage: coverage debug sys - coverage: coverage erase - coverage: coverage run -m {{project_name}} test -v 2 --failfast --parallel 8 {posargs} - coverage: coverage combine - coverage: coverage report - rm -rf .eggs build {{project_name}}.egg-info {envdir}/dist - pip uninstall {{project_name}} -y + pip install -U pip + pip uninstall {{project_name}} -y + install: rm -rfv {envdir}/dist/ + install: pip wheel {toxinidir} -w {envdir}/dist/ --no-deps + install: bash -ec "pip install -U {envdir}/dist/$(ls {envdir}/dist/*.whl | head -1)[all]" + install: rm -f {envdir}/test.py {envdir}/test.pyc + install: bash -c "cd {envdir} && ln -s {toxinidir}/test.py && python -m {{project_name}} test -v 2 --failfast --parallel 8" + # Uncomment this strings if you're using custom frontend + # coverage: yarn install --pure-lockfile + # coverage: yarn devBuild + coverage: pip install -U -e .[test] + coverage: python -m {{project_name}} makemigrations {{project_name}} --check + coverage: coverage debug sys + coverage: coverage erase + coverage: coverage run -m {{project_name}} test -v 2 --failfast --parallel 8 {posargs} + coverage: coverage combine + coverage: coverage report + rm -rf .eggs build {{project_name}}.egg-info {envdir}/dist + pip uninstall {{project_name}} -y deps = -rrequirements.txt -rrequirements-test.txt [testenv:flake] -basepython = python3.6 +basepython = python3.8 deps = - flake8 - flake8-bugbear - flake8-import-order - flake8-functions==0.0.4 - flake8-executable - flake8-django~=1.1.2 - flake8-comprehensions - flake8-commas + flake8==6.1.0 + flake8-bugbear==23.9.16 + flake8-comprehensions==3.14.0 + flake8-executable==2.1.3 + flake8-functions==0.0.8 + flake8-import-order==0.18.2 + Flake8-pyproject==1.2.3 commands = - flake8 --config=.pep8 {{project_name}} + flake8 {{project_name}} [testenv:contrib] -basepython = python3.6 +basepython = python3.8 skipsdist = True envdir = {toxinidir}/env setenv = CCACHE_DIR = {envdir}/.ccache passenv = * -whitelist_externals = * +allowlist_externals = * commands = pip install -U -r requirements-test.txt -r requirements.txt - python setup.py install_egg_info pip install -U -e .[test] deps = - tox==3.24.4 + tox>=4 [testenv:build_for_docker] basepython = python3.8 skipsdist = True setenv = CCACHE_DIR = {envdir}/.ccache + BUILD_OPTIMIZATION = true BUILD_COMPILE = true + UWSGI_PROFILE = default passenv = * -whitelist_externals = * +allowlist_externals = * commands = - python setup.py bdist_wheel -v + rm -frv {envdir}/dist + bash -c "pip wheel .[prod] -w wheels" deps = - pip>=20.2 + pip>=23.3.1 + cython>=3.0.5 + wheel==0.41.3 + setuptools>=61.2.0 [testenv:build] passenv = * changedir = . -whitelist_externals = +allowlist_externals = tox rm commands = diff --git a/vstutils/templates/newproject/tox_build.ini.template b/vstutils/templates/newproject/tox_build.ini.template index a7d7d153..8368fee5 100644 --- a/vstutils/templates/newproject/tox_build.ini.template +++ b/vstutils/templates/newproject/tox_build.ini.template @@ -1,38 +1,47 @@ [tox] -envlist = py36-build,py{38}-wheel +envlist = py38-build,py31{1,2}-wheel skipsdist = True [testenv] passenv = * setenv = - CCACHE_DIR = {envdir}/.ccache - DONT_YARN = false - BUILD_COMPILE = true -changedir = . -whitelist_externals = + CCACHE_DIR = {envdir}/.ccache + DONT_YARN = false + build: BUILD_OPTIMIZATION=false + build: DONT_YARN=true + NOT_COMPRESS = 1 + wheel: BUILD_OPTIMIZATION=true +changedir = {envdir} +allowlist_externals = rm ls grep bash + yarn commands = - rm -rf build - build: python setup.py compile -v - wheel: python setup.py bdist_wheel -v + rm -rf {toxinidir}/build + # Uncomment this strings if you're using custom frontend + # build: bash -ec 'cd {toxinidir} && yarn install --pure-lockfile && yarn build' + build: python -m build --sdist --wheel --no-isolation --skip-dependency-check --outdir {toxinidir}/dist {toxinidir} + wheel: python -m build --wheel --no-isolation --skip-dependency-check --outdir {toxinidir}/dist {toxinidir} deps = - cython>=0.29.2 - wheel==0.31.1 - setuptools>=40.6.3 - jsmin~=3.0.0 - csscompressor==0.9.5 + cython>=3.0.5 + build~=1.0.3 + wheel==0.41.3 + setuptools>=61.2.0 + -rrequirements-doc.txt + jsmin~=3.0.0 + csscompressor==0.9.5 + vstcompile~=2.0 [testenv:auditwheel] -basepython = python3.6 -whitelist_externals = +basepython = python3.11 +allowlist_externals = bash grep rm ls commands = - bash -c "for whl in `ls dist/*.whl | grep -v manylinux`; do auditwheel repair --plat manylinux2014_x86_64 $whl -w dist/; rm $whl; done" + bash -c "for whl in `ls {toxinidir}/dist/*.whl | grep -v manylinux | grep linux`; do auditwheel repair --plat manylinux2014_x86_64 $whl -w {toxinidir}/dist/; rm $whl; done" deps = - auditwheel + auditwheel~=5.4.0 From d346b07bf0f85517fa80df2a7d30112710ec3595 Mon Sep 17 00:00:00 2001 From: Dmitriy Ovcharenko Date: Wed, 27 Dec 2023 01:24:10 +1000 Subject: [PATCH 11/20] Chore(frontend): Update dependencies in project template --- test_src/test_proj/tests.py | 40 +++++++++---------- .../newproject/package.json.template | 40 +++++++++---------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/test_src/test_proj/tests.py b/test_src/test_proj/tests.py index 18bee3f7..d6450efc 100644 --- a/test_src/test_proj/tests.py +++ b/test_src/test_proj/tests.py @@ -125,26 +125,26 @@ }, "dependencies": {}, "devDependencies": { - "@babel/core": "^7.16.7", - "@babel/eslint-parser": "^7.16.5", - "@babel/plugin-transform-runtime": "^7.16.8", - "@babel/preset-env": "^7.16.8", - "babel-loader": "^8.2.3", - "css-loader": "^6.5.1", - "dotenv": "^11.0.0", - "eslint": "^8.6.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-vue": "^8.2.0", - "prettier": "^2.5.1", - "sass": "^1.48.0", - "sass-loader": "^12.4.0", - "style-loader": "^3.3.1", - "vue-loader": "^15.9.8", - "vue-template-compiler": "^2.6.14", - "webpack": "^5.66.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.1" + "@babel/core": "^7.23.6", + "@babel/eslint-parser": "^7.23.3", + "@babel/plugin-transform-runtime": "^7.23.6", + "@babel/preset-env": "^7.23.6", + "babel-loader": "^9.1.3", + "css-loader": "^6.8.1", + "dotenv": "^16.3.1", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.2", + "eslint-plugin-vue": "^9.19.2", + "prettier": "^3.1.1", + "sass": "^1.69.5", + "sass-loader": "^13.3.3", + "style-loader": "^3.3.3", + "vue": "^2.7.16", + "vue-loader": "^15.11.1", + "webpack": "^5.89.0", + "webpack-bundle-analyzer": "^4.10.1", + "webpack-cli": "^5.1.4" } } diff --git a/vstutils/templates/newproject/package.json.template b/vstutils/templates/newproject/package.json.template index b3c7ddb9..b38594d7 100644 --- a/vstutils/templates/newproject/package.json.template +++ b/vstutils/templates/newproject/package.json.template @@ -12,25 +12,25 @@ }, "dependencies": {}, "devDependencies": { - "@babel/core": "^7.16.7", - "@babel/eslint-parser": "^7.16.5", - "@babel/plugin-transform-runtime": "^7.16.8", - "@babel/preset-env": "^7.16.8", - "babel-loader": "^8.2.3", - "css-loader": "^6.5.1", - "dotenv": "^11.0.0", - "eslint": "^8.6.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-vue": "^8.2.0", - "prettier": "^2.5.1", - "sass": "^1.48.0", - "sass-loader": "^12.4.0", - "style-loader": "^3.3.1", - "vue-loader": "^15.9.8", - "vue-template-compiler": "^2.6.14", - "webpack": "^5.66.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.1" + "@babel/core": "^7.23.6", + "@babel/eslint-parser": "^7.23.3", + "@babel/plugin-transform-runtime": "^7.23.6", + "@babel/preset-env": "^7.23.6", + "babel-loader": "^9.1.3", + "css-loader": "^6.8.1", + "dotenv": "^16.3.1", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.2", + "eslint-plugin-vue": "^9.19.2", + "prettier": "^3.1.1", + "sass": "^1.69.5", + "sass-loader": "^13.3.3", + "style-loader": "^3.3.3", + "vue": "^2.7.16", + "vue-loader": "^15.11.1", + "webpack": "^5.89.0", + "webpack-bundle-analyzer": "^4.10.1", + "webpack-cli": "^5.1.4" } } From f670024d610d813ff3b2e7ca43714ce923ec316e Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Thu, 11 Jan 2024 18:59:17 +1000 Subject: [PATCH 12/20] Docs(backend): Improve backend api documentation for better comprehension. --- NOTICE | 2 +- README.rst | 2 +- doc/backend.rst | 9 +- doc/config.rst | 271 ++++++- doc/locale/ru/LC_MESSAGES/backend.po | 796 ++++++++++++-------- doc/locale/ru/LC_MESSAGES/config.po | 938 +++++++++++++++++------- doc/locale/ru/LC_MESSAGES/quickstart.po | 172 ++--- requirements-rtd.txt | 4 +- requirements-stubs.txt | 2 +- requirements-test.txt | 4 +- requirements.txt | 8 +- setup.py | 4 +- vstutils/api/fields.py | 36 +- vstutils/models/custom_model.py | 94 ++- vstutils/models/decorators.py | 42 +- vstutils/settings.py | 6 +- 16 files changed, 1669 insertions(+), 721 deletions(-) diff --git a/NOTICE b/NOTICE index 0c04b550..067df667 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ The VSTUtils Framework project. -Copyright 2018-2023 VST Consulting +Copyright 2018-2024 VST Consulting Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.rst b/README.rst index c654203c..fa4de28f 100644 --- a/README.rst +++ b/README.rst @@ -67,4 +67,4 @@ License VSTUtils is licensed under the terms of the Apache License 2.0. See the file "LICENSE" for more information. -Copyright 2018-2023 VST Consulting +Copyright 2018-2024 VST Consulting diff --git a/doc/backend.rst b/doc/backend.rst index 8cd68fdc..bbce1d2e 100644 --- a/doc/backend.rst +++ b/doc/backend.rst @@ -20,7 +20,14 @@ as BModel provides plenty of Meta attributes to autogenerate serializers and vie :members: register_view_action -You can also use custom models without using database: +Vstutils supports models that don't necessitate direct database interaction or aren't inherently tied to database tables. +These models exhibit diverse behaviors, such as fetching data directly from class attributes, loading data from files, +or implementing custom data retrieval mechanisms. +Remarkably, there are models that, in a sense, implement the mechanism of SQL views with pre-defined queries. +This flexibility allows developers to define a wide range of models that cater to specific data needs, +from in-memory models to those seamlessly integrating external data sources. +Vstutils' model system is not confined to traditional database-backed structures, +providing a versatile foundation for crafting various data representations. .. automodule:: vstutils.models.custom_model :members: ListModel,FileModel,ExternalCustomModel,ViewCustomModel diff --git a/doc/config.rst b/doc/config.rst index a8e54826..d7bc8645 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -58,11 +58,13 @@ In the example above authorization logic will be the following: * **debug** - Enable debug mode. Default: false. * **allowed_hosts** - Comma separated list of domains, which allowed to serve. Default: ``*``. * **first_day_of_week** - Integer value with first day of week. Default: ``0``. -* **ldap-server** - LDAP server connection. +* **ldap-server** - LDAP server connection. For example: ``ldap://your_ldap_server:389`` * **ldap-default-domain** - Default domain for auth. * **ldap-auth_format** - Default search request format for auth. Default: ``cn=,``. * **timezone** - Timezone for web-application. Default: UTC. -* **log_level** - Logging level. Default: WARNING. +* **log_level** - Logging level. The verbosity level, configurable in Django and Celery, dictates the extent of log information, + with higher levels providing detailed debugging insights for development and lower levels streamlining + logs for production environments. Default: WARNING. * **enable_django_logs** - Enable or disable Django logger to output. Useful for debugging. Default: false. * **enable_admin_panel** - Enable or disable Django Admin panel. Default: false. @@ -91,6 +93,12 @@ These settings are for all databases and are vendor-independent, with the exception of tablespace management. * **default_tablespace** - Default tablespace to use for models that don’t specify one, if the backend supports it. + A tablespace is a storage location on a database server where the physical data files corresponding to database tables are stored. + It allows you to organize and manage the storage of your database tables, specifying the location on disk where the table data is stored. + Configuring tablespaces can be beneficial for various reasons, such as optimizing performance by placing specific tables or indexes (with ``default_index_tablespace``) + on faster storage devices, managing disk space efficiently, or segregating data for administrative purposes. + It provides a level of control over the physical organization of data within the database, + allowing developers to tailor storage strategies based on the requirements and characteristics of their application. Read more at :django_topics:`Declaring tablespaces for tables `. * **default_index_tablespace** - Default tablespace to use for indexes on fields that don’t specify one, if the backend supports it. @@ -232,8 +240,8 @@ another bunch of same settings as cache_. And why there is another section for them, you may ask. Because cache backend is used for locking must provide some guarantees, which do not required to usual cache: it MUST be shared for all vstutils-based application threads and nodes. So, for example, in-memory backend is not suitable. In case of clusterization we strongly recommend -to use Redis or Memcached as backend for that purpose. Cache and locks backend -can be the same, but don't forget about requirement we said above. +to use Tarantool, Redis or Memcached as backend because they have enough speed for this purposes. +Cache and locks backend can be the same, but don't forget about requirement we said above. .. _session: @@ -256,10 +264,21 @@ Rpc settings Section ``[rpc]``. +Celery is a distributed task queue system for handling asynchronous tasks in web applications. +Its primary role is to facilitate the execution of background or time-consuming tasks independently from the main application logic. +Celery is particularly useful for offloading tasks that don't need to be processed immediately, improving the overall responsiveness and performance of an application. + +Key features and roles of Celery in an application with asynchronous tasks include: + +#. Asynchronous Task Execution: Celery allows developers to define tasks as functions or methods and execute them asynchronously. This is beneficial for tasks that might take a considerable amount of time, such as sending emails, processing data, or generating reports. +#. Distributed Architecture: Celery operates in a distributed manner, making it suitable for large-scale applications. It can distribute tasks across multiple worker processes or even multiple servers, enhancing scalability and performance. +#. Message Queue Integration: Celery relies on message brokers (such as RabbitMQ, Redis, Tarantool, SQS or others) to manage the communication between the main application and the worker processes. This decoupling ensures reliable task execution and allows for the efficient handling of task queues. +#. Periodic Tasks: Celery includes a scheduler that enables the execution of periodic or recurring tasks. This is useful for automating tasks that need to run at specific intervals, like updating data or performing maintenance operations. +#. Error Handling and Retry Mechanism: Celery provides mechanisms for handling errors in tasks and supports automatic retries. This ensures robustness in task execution, allowing the system to recover from transient failures. +#. Task Result Storage: Celery supports storing the results of completed tasks, which can be useful for tracking task progress or retrieving results later. This feature is especially valuable for long-running tasks. + vstutils-based application uses Celery for long-running async tasks. -Celery is based on message queue concept, -so between web-service and workers running under Celery must be some kind of -message broker (RabbitMQ or something). Those settings relate to this broker +Those settings relate to this broker and Celery itself. Those kinds of settings: broker backend, number of worker-processes per node and some settings used for troubleshoot server-broker-worker interaction problems. @@ -335,6 +354,10 @@ Django comes with several email sending backends. With the exception of the SMTP (default when ``host`` is set), these backends are useful only in testing and development. Applications based on vstutils uses only ``smtp`` and ``console`` backends. +These two backends serve distinct purposes in different environments. +The SMTP backend ensures the reliable delivery of emails in a production setting, +while the console backend provides a convenient way to inspect emails during development without the risk of unintentional communication with external mail servers. +Developers often switch between these backends based on the context of their work, choosing the appropriate one for the stage of development or testing. * **host** - IP or domain for smtp-server. If it not set vstutils uses ``console`` backends. Default: ``None``. * **port** - Port for smtp-server connection. Default: ``25``. @@ -414,9 +437,23 @@ Centrifugo client settings Section ``[centrifugo]``. +Centrifugo is employed to optimize real-time data updates within a Django application by orchestrating seamless communication among its various components. +The operational paradigm involves the orchestrated generation of Django signals, specifically ``post_save`` and ``post_delete`` signals, +dynamically triggered during HTTP requests or the execution of Celery tasks. +These signals, when invoked on user or BaseModel-derived models within the vstutils framework, +initiate the creation of messages destined for all subscribers keen on the activities related to these models. +Subsequent to the completion of the HTTP request or Celery task, +the notification mechanism dispatches tailored messages to all relevant subscribers. +In effect, each active browser tab with a pertinent subscription promptly receives a notification, +prompting an immediate data update request. +Centrifugo's pivotal role lies in obviating the necessity for applications to engage in periodic REST API polling at fixed intervals (e.g., every 5 seconds). +This strategic elimination of redundant requests significantly alleviates the REST API's operational load, +rendering it more scalable to accommodate a larger user base. +Importantly, this real-time communication model ensures prompt and synchronized data updates, fostering a highly responsive user experience. + To install app with centrifugo client, ``[centrifugo]`` section must be set. Centrifugo is used by application to auto-update page data. -When user change some data, other clients get notification on ``subscriptions_update`` channel +When user change some data, other clients get notification on channel with model label and primary key. Without the service all GUI-clients get page data every 5 seconds (by default). @@ -550,6 +587,216 @@ But keep in mind that uWSGI is deprecated and may be removed in future releases. Use the uvicorn settings to manage your app server. +Working behind the proxy server with TLS +---------------------------------------- + +Nginx +~~~~~ + +To configure vstutils for operation behind Nginx with TLS, follow these steps: + +1. **Install Nginx:** + +Ensure that Nginx is installed on your server. You can install it using the package manager specific to your operating system. + +2. **Configure Nginx:** + +Create an Nginx configuration file for your vstutils application. +Below is a basic example of an Nginx configuration. Adjust the values based on your specific setup. + +.. sourcecode:: nginx + + server { + listen 80; + server_name your_domain.com; + + return 301 https://$host$request_uri; + } + + server { + listen 443 ssl; + server_name your_domain.com; + + ssl_certificate /path/to/your/certificate.crt; + ssl_certificate_key /path/to/your/private.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384'; + + gzip on; + gzip_types text/plain application/xml application/json application/openapi+json text/css application/javascript; + gzip_min_length 1000; + + charset utf-8; + + location / { + proxy_pass http://127.0.0.1:8080; # Assuming application is running on the default port + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; # Set to 'https' since it's a secure connection + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + } + } + + +Replace ``your_domain.com`` with your actual domain, and update the paths for SSL certificates. + +3. **Update vstutils settings:** + +Ensure that your vstutils settings have the correct configurations for HTTPS. In your ``/etc/vstutils/settings.ini`` (or project ``settings.ini``): + +.. sourcecode:: ini + + [web] + secure_proxy_ssl_header_name = HTTP_X_FORWARDED_PROTO + secure_proxy_ssl_header_value = https + +This ensures that vstutils recognizes the HTTPS connection. + +4. **Restart Nginx:** + +After making these changes, restart Nginx to apply the new configurations: + +.. sourcecode:: bash + + sudo systemctl restart nginx + +Now, your vstutils application should be accessible via HTTPS through Nginx. Adjust these instructions based on your specific environment and security considerations. + + +Traefik +~~~~~~~ + +To configure vstutils for operation behind Traefik with TLS, follow these steps: + +1. **Install Traefik:** + +Ensure that Traefik is installed on your server. You can download the binary from the official website or use a package manager specific to your operating system. + +2. **Configure Traefik:** + +Create a Traefik configuration file ``/path/to/traefik.toml``. Here's a basic example: + +.. sourcecode:: toml + [experimental] + http3 = true + + [entryPoints] + [entryPoints.web] + address = ":80" + [entryPoints.web.http.redirections] + [entryPoints.web.http.redirections.entryPoint] + to = "websecure" + + [entryPoints.websecure] + address = ":443" + http3: {} + + [api] + + [providers.file] + filename = "/path/to/traefik_config.toml" + +3. **Create Traefik Toml Configuration:** + +Create the ``/path/to/traefik_config.toml`` file with the following content: + +.. sourcecode:: toml + + [http.routers] + [http.routers.vstutils] + rule = "Host(`your_domain.com`)" + entryPoints = ["websecure"] + service = "vstutils" + middlewares = ["customheaders", "compress"] + + [http.middlewares] + [http.middlewares.customheaders.headers.customRequestHeaders] + X-Forwarded-Proto = "https" + + [http.middlewares.compress.compress] + compress = true + + [http.services] + [http.services.vstutils.loadBalancer] + [[http.services.vstutils.loadBalancer.servers]] + url = "http://127.0.0.1:8080" # Assuming application is running on the default port + +Make sure to replace ``your_domain.com`` with your actual domain. + +4. **Update vstutils settings:** + +Ensure that your vstutils settings have the correct configurations for HTTPS. In your ``/etc/vstutils/settings.ini`` (or project ``settings.ini``): + +.. sourcecode:: ini + [web] + secure_proxy_ssl_header_name = HTTP_X_FORWARDED_PROTO + secure_proxy_ssl_header_value = https + +5. **Start Traefik:** + +Start Traefik with the following command: + +.. sourcecode:: bash + + traefik --configfile /path/to/traefik.toml + +Now, your vstutils application should be accessible via HTTPS through Traefik. Adjust these instructions based on your specific environment and requirements. + + +HAProxy +~~~~~~~ + +1. **Install HAProxy:** + +Ensure that HAProxy is installed on your server. You can install it using the package manager specific to your operating system. + +2. **Configure HAProxy:** + +Create an HAProxy configuration file for your vstutils application. Below is a basic example of an HAProxy configuration. Adjust the values based on your specific setup. + +.. sourcecode:: haproxy + + frontend http-in + bind *:80 + mode http + redirect scheme https code 301 if !{ ssl_fc } + + frontend https-in + bind *:443 ssl crt /path/to/your/certificate.pem + mode http + option forwardfor + http-request set-header X-Forwarded-Proto https + + default_backend vstutils_backend + + backend vstutils_backend + mode http + server vstutils-server 127.0.0.1:8080 check + +Replace ``your_domain.com`` with your actual domain and update the paths for SSL certificates. + +3. **Update vstutils settings:** + +Ensure that your vstutils settings have the correct configurations for HTTPS. In your ``/etc/vstutils/settings.ini`` (or project ``settings.ini``): + +.. sourcecode:: ini + [web] + secure_proxy_ssl_header_name = HTTP_X_FORWARDED_PROTO + secure_proxy_ssl_header_value = https + +4. **Restart HAProxy:** + +After making these changes, restart HAProxy to apply the new configurations: + +.. sourcecode:: bash + + sudo systemctl restart haproxy + +Now, your vstutils application should be accessible via HTTPS through HAProxy. Adjust these instructions based on your specific environment and security considerations. + + Configuration options ----------------------------- @@ -587,3 +834,11 @@ This section contains additional information for configure additional elements. As you can see from the names, they are closely related to the keys and names of the corresponding config sections. #. We recommend to install ``uvloop`` to your environment and setup ``loop = uvloop`` in ``[uvicorn]`` section for performance reasons. + +In the context of vstutils, the adoption of ``uvloop`` is paramount for optimizing the performance of the application, especially because utilizing ``uvicorn`` as the ASGI server. +``uvloop`` is an ultra-fast, drop-in replacement for the default event loop provided by Python. +It is built on top of ``libuv``, a high-performance event loop library, and is specifically designed to optimize the execution speed of asynchronous code. + +By leveraging ``uvloop``, developers can achieve substantial performance improvements in terms of reduced latency and increased throughput. +This is especially critical in scenarios where applications handle a large number of concurrent connections. +The improved efficiency of event loop handling directly translates to faster response times and better overall responsiveness of the application. diff --git a/doc/locale/ru/LC_MESSAGES/backend.po b/doc/locale/ru/LC_MESSAGES/backend.po index 2262c6d4..fd7719ce 100644 --- a/doc/locale/ru/LC_MESSAGES/backend.po +++ b/doc/locale/ru/LC_MESSAGES/backend.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-12-20 23:56+0000\n" +"POT-Creation-Date: 2024-01-11 05:57+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -63,7 +63,7 @@ msgstr "" #: of vstutils.api.actions.Action:5 vstutils.api.actions.EmptyAction:9 #: vstutils.api.actions.SimpleAction:20 vstutils.api.base.ModelViewSet:31 #: vstutils.api.fields.FkField:44 vstutils.models.BModel:162 -#: vstutils.models.BModel:187 vstutils.models.custom_model.FileModel:22 +#: vstutils.models.BModel:187 vstutils.models.custom_model.FileModel:20 #: vstutils.models.custom_model.ListModel:16 #: vstutils.models.custom_model.ListModel:35 msgid "Examples:" @@ -406,10 +406,9 @@ msgid "" "define and customize various aspects of the associated DRF view class." msgstr "" "Метод ``get_view_class()`` — это служебный метод в ORM Django моделях, " -"предназначенный для облегчения настройки и создания " -"экземпляров представлений Django Rest Framework (DRF). Это позволяет " -"разработчикам определить и настроить различные аспекты класса " -"представления DRF." +"предназначенный для облегчения настройки и создания экземпляров " +"представлений Django Rest Framework (DRF). Это позволяет разработчикам " +"определить и настроить различные аспекты класса представления DRF." #: of vstutils.models.BModel:189 msgid "" @@ -421,8 +420,8 @@ msgstr "" "Разработчики могут использовать этот метод для изменения различных " "аспектов получаемого представления, таких как классы сериализаторов, " "конфигурацию полей, фильтры,классы разрешений и т.п. Этот метод " -"использует такие же атрибуты, которые были объявлены в мета-атрибутах, но " -"позволяет перегружать отдельные части." +"использует такие же атрибуты, которые были объявлены в мета-атрибутах, но" +" позволяет перегружать отдельные части." #: ../../docstring of vstutils.models.BModel.hidden:1 msgid "If hidden is set to True, entry will be excluded from query in BQuerySet." @@ -488,91 +487,350 @@ msgstr "" msgid "" "Decorator for turning model methods to generated view `actions " "`_. The decorated method becomes a method of " -"generated view and `self` is an view object. See supported args in " -":func:`vstutils.api.decorators.subaction`." +"actions-for-routing>`_. When a method is decorated, it becomes a part of " +"the generated view and the `self` reference within the method points to " +"the view object. This allows you to extend the functionality of generated" +" views with custom actions." msgstr "" -"Декоратор, позволяющий обратить методы модели в сгенерированные `экшены " +"Декоратор для превращения методов модели в сгенерированные view `экшены " "`_ view. Декорированный метод становится методом " -"сгенерированного view, где `self` - объект view. Смотрите поддерживаемые " -"аргументы в :func:`vstutils.api.decorators.subaction`." +"actions-for-routing>`_. Когда метод декорируется, он становится частью " +"сгенерированного view, и ссылка `self` внутри метода указывает на объект " +"view. Это позволяет расширять функциональность сгенерированных view с " +"помощью пользовательских экшенов." #: of vstutils.models.decorators.register_view_action:7 msgid "" -"Sometimes you may need to use proxy models with a common set of actions. " -"To receive the action by the proxy model, pass the named argument " -"``inherit`` with ``True`` value." -msgstr "" -"Возможно вам потребуется использовать прокси-модели со стандартным " -"набором экшенов. Чтобы получить экшен прокси-модели, передайте " -"именованный аргумент ``inherit`` со значением ``True``." +"The `register_view_action` decorator supports various arguments, and you " +"can refer to the documentation for " +":func:`vstutils.api.decorators.subaction` to explore the complete list of" +" supported arguments. These arguments provide flexibility in defining the" +" behavior and characteristics of the generated view actions." +msgstr "" +"Декоратор `register_view_action` поддерживает различные аргументы, и вы " +"можете обратиться к документации для " +":func:`vstutils.api.decorators.subaction`, чтобы изучить полный список " +"поддерживаемых аргументов. Эти аргументы предоставляют гибкость в " +"определении поведения и характеристик сгенерированных экшенов view." + +#: of vstutils.models.decorators.register_view_action:12 +msgid "" +"In scenarios where you're working with proxy models that share a common " +"set of actions, you can use the `inherit` named argument with a value of " +"`True`. This allows the proxy model to inherit actions defined in the " +"base model, reducing redundancy and promoting code reuse." +msgstr "" +"В сценариях, где вы работаете с прокси-моделями, использующими общий " +"набор действий, вы можете использовать именованный аргумент `inherit` со " +"значением `True`. Это позволяет прокси-модели наследовать действия, " +"определенные в базовой модели, сокращая избыточность и способствуя " +"повторному использованию кода." + +#: of vstutils.models.decorators.register_view_action:18 +msgid "" +"In many cases, an action may not require any parameters and can be " +"executed by sending an empty query. To streamline development and enhance" +" efficiency, the `register_view_action` decorator sets the default " +"serializer to :class:`vstutils.api.serializers.EmptySerializer`. This " +"means that the action expects no input data, making it convenient for " +"actions that operate without additional parameters." +msgstr "" +"Во многих случаях действие может не требовать параметров и может быть " +"выполнено, отправив пустой запрос. Для упрощения разработки и повышения " +"эффективности декоратор `register_view_action` устанавливает сериализатор" +" по умолчанию на :class:`vstutils.api.serializers.EmptySerializer`. Это " +"означает, что действие не ожидает входных данных, что удобно для " +"действий, которые работают без дополнительных параметров." -#: of vstutils.models.decorators.register_view_action:11 +#: of vstutils.api.base.FileResponseRetrieveMixin:3 +#: vstutils.api.decorators.nested_view:37 +#: vstutils.api.filter_backends.DeepViewFilterBackend:25 +#: vstutils.api.filter_backends.VSTFilterBackend:22 +#: vstutils.api.filters.FkFilterHandler:15 +#: vstutils.middleware.BaseMiddleware:38 +#: vstutils.models.decorators.register_view_action:24 +#: vstutils.tasks.TaskClass:4 vstutils.utils.BaseEnum:4 vstutils.utils.Lock:35 +#: vstutils.utils.ObjectHandlers:31 vstutils.utils.SecurePickling:22 +#: vstutils.utils.URLHandlers:10 vstutils.utils.apply_decorators:18 +#: vstutils.utils.classproperty:20 vstutils.utils.create_view:17 +#: vstutils.utils.raise_context_decorator_with_default:19 +msgid "Example:" +msgstr "Пример:" + +#: of vstutils.models.decorators.register_view_action:26 msgid "" -"Often, an action does not transfer any parameters and requires only " -"sending an empty query. To speed up development, we set the default " -"serializer to :class:`vstutils.api.serializers.EmptySerializer`." +"This example demonstrates how to use the decorator to create a custom " +"action within a model view. The ``empty_action`` method becomes part of " +"the generated view and expects no input parameters." msgstr "" -"Часто экшен не должен передавать никаких параметров, отправляя вместо " -"этого пустой запрос. Чтобы ускорить разработку, мы установили " -"сериализатор :class:`vstutils.api.serializers.EmptySerializer`. по " -"умолчанию." +"В этом примере показано, как использовать декоратор для создания " +"пользовательского действия в представлении модели. Метод ``empty_action``" +" становится частью сгенерированного view и не ожидает входных параметров." #: ../../backend.rst:23 -msgid "You can also use custom models without using database:" -msgstr "Вы также можете использовать модели, не нуждающиеся в базе данных:" +msgid "" +"Vstutils supports models that don't necessitate direct database " +"interaction or aren't inherently tied to database tables. These models " +"exhibit diverse behaviors, such as fetching data directly from class " +"attributes, loading data from files, or implementing custom data " +"retrieval mechanisms. Remarkably, there are models that, in a sense, " +"implement the mechanism of SQL views with pre-defined queries. This " +"flexibility allows developers to define a wide range of models that cater" +" to specific data needs, from in-memory models to those seamlessly " +"integrating external data sources. Vstutils' model system is not confined" +" to traditional database-backed structures, providing a versatile " +"foundation for crafting various data representations." +msgstr "" +"Vstutils поддерживает модели, которые не требуют прямого взаимодействия с" +" базой данных или не являются непосредственно таблицами в базе. Эти " +"модели проявляют разнообразные поведения, такие как извлечение данных " +"непосредственно из атрибутов класса, загрузка данных из файлов или " +"реализация собственных механизмов получения данных. Замечательно, что " +"существуют модели, которые, в каком-то смысле, реализуют механизм SQL " +"представлений с предопределенными запросами. Эта гибкость позволяет " +"разработчикам определять широкий спектр моделей, от моделей, существующих" +" только в памяти, до тех, которые без проблем интегрируют внешние " +"источники данных. Система моделей Vstutils не ограничивается " +"традиционными структурами, поддерживаемыми базой данных, предоставляя " +"гибкое основание для создания различных представлений данных." #: of vstutils.models.custom_model.ExternalCustomModel:1 msgid "" -"This custom model is intended for self-implementation of requests to " -"external services. The model allows you to pass filtering, limiting and " -"sorting parameters to an external request, receiving already limited " -"data." +"Represents a custom model designed for the self-implementation of " +"requests to external services." +msgstr "" +"Представляет собой кастомную модель, предназначенную для самостоятельной " +"реализации запросов ко внешним сервисам." + +#: of vstutils.models.custom_model.ExternalCustomModel:3 +msgid "" +"This model facilitates the seamless interaction with external services by" +" allowing the passing of filtering, limiting, and sorting parameters to " +"an external request. It is designed to receive data that is already " +"filtered and limited." +msgstr "" +"Данная кастомная модель облегчает взаимодействие с внешними сервисами, " +"позволяя передавать параметры фильтрации, лимитирования и сортировки во " +"внешний запрос. Она предназначена для получения данных, которые уже " +"отфильтрованы и ограничены." + +#: of vstutils.models.custom_model.ExternalCustomModel:7 +msgid "" +"To utilize this model effectively, developers need to implement the " +"``get_data_generator()`` class method. This method receives a query " +"object containing the necessary parameters, enabling developers to " +"customize interactions with external services." msgstr "" -"Данная кастомная модель предназначена для самостоятельной реализации " -"запросов к внешним сервисам. Модель позволяет вам передавать параметры " -"фильтрации, лимитирования и сортировки к внешним запросам, получая уже " -"ограниченные данные." +"Для эффективного использования этой модели разработчики должны " +"реализовать метод класса ``get_data_generator()``. Этот метод получает " +"объект запроса с необходимыми параметрами, позволяя разработчикам " +"настраивать взаимодействие с внешними сервисами." -#: of vstutils.models.custom_model.ExternalCustomModel:5 +#: of vstutils.models.custom_model.ExternalCustomModel:11 +msgid "**Example:**" +msgstr "**Пример:**" + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:1 msgid "" -"To start using this model, it is enough to implement the " -"``get_data_generator()`` class method, which receives the query object " -"with the necessary parameters as an argument." +"This class method must be implemented by derived classes to define custom" +" logic for fetching data from an external service based on the provided " +"query parameters." msgstr "" -"Чтобы начать использовать эту модель, достаточно реализовать метод класса" -" ``get_data_generator()``, который получает объект запроса с необходимыми" -" параметрами, как аргумент." +"Этот метод класса должен быть реализован в производных классах для " +"определения пользовательской логики извлечения данных из внешнего сервиса" +" на основе предоставленных параметров запроса." + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:4 +msgid "Query object might contain the following parameters:" +msgstr "Объект запроса может содержать следующие параметры:" + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:6 +msgid "filter (dict): A dictionary specifying the filtering criteria." +msgstr "filter (dict): Словарь, задающий критерии фильтрации." + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:7 +msgid "exclude (dict): A dictionary specifying the exclusion criteria." +msgstr "exclude (dict): Словарь, задающий критерии исключения." + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:8 +msgid "order_by (list): A list specifying the sorting order." +msgstr "order_by (list): Список, задающий порядок сортировки." + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:9 +msgid "low_mark (int): The low index for slicing (if sliced)." +msgstr "low_mark (int): Нижний индекс для среза (если задан срез)." + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:10 +msgid "high_mark (int): The high index for slicing (if sliced)." +msgstr "high_mark (int): Верхний индекс для среза (если задан срез)." + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:11 +msgid "is_sliced (bool): A boolean indicating whether the query is sliced." +msgstr "is_sliced (bool): Булево значение, указывающее, является ли запрос срезом." + +#: of vstutils.api.actions.Action +#: vstutils.api.base.GenericViewSet.create_action_serializer +#: vstutils.api.base.GenericViewSet.get_query_serialized_data +#: vstutils.api.decorators.nested_view vstutils.api.decorators.subaction +#: vstutils.api.endpoint.EndpointViewSet.get +#: vstutils.api.endpoint.EndpointViewSet.get_client +#: vstutils.api.endpoint.EndpointViewSet.operate +#: vstutils.api.endpoint.EndpointViewSet.post +#: vstutils.api.endpoint.EndpointViewSet.put +#: vstutils.api.fields.AutoCompletionField vstutils.api.fields.Barcode128Field +#: vstutils.api.fields.BinFileInStringField vstutils.api.fields.CSVFileField +#: vstutils.api.fields.CommaMultiSelect vstutils.api.fields.DeepFkField +#: vstutils.api.fields.DependEnumField vstutils.api.fields.DependFromFkField +#: vstutils.api.fields.DynamicJsonTypeField +#: vstutils.api.fields.FileInStringField vstutils.api.fields.FkField +#: vstutils.api.fields.FkModelField vstutils.api.fields.MaskedField +#: vstutils.api.fields.MultipleNamedBinaryImageInJsonField +#: vstutils.api.fields.NamedBinaryImageInJsonField +#: vstutils.api.fields.QrCodeField vstutils.api.fields.RatingField +#: vstutils.api.fields.RedirectFieldMixin vstutils.api.fields.RelatedListField +#: vstutils.api.fields.SecretFileInString vstutils.api.fields.WYSIWYGField +#: vstutils.api.filters.FkFilterHandler vstutils.api.filters.extra_filter +#: vstutils.api.filters.name_filter vstutils.api.responses.BaseResponseClass +#: vstutils.api.validators.FileMediaTypeValidator +#: vstutils.api.validators.ImageBaseSizeValidator +#: vstutils.api.validators.ImageHeightValidator +#: vstutils.api.validators.ImageOpenValidator +#: vstutils.api.validators.ImageResolutionValidator +#: vstutils.api.validators.ImageValidator +#: vstutils.api.validators.ImageWidthValidator +#: vstutils.api.validators.RegularExpressionValidator +#: vstutils.api.validators.UrlQueryStringValidator +#: vstutils.api.validators.resize_image +#: vstutils.api.validators.resize_image_from_to +#: vstutils.middleware.BaseMiddleware.get_response_handler +#: vstutils.middleware.BaseMiddleware.handler +#: vstutils.middleware.BaseMiddleware.request_handler +#: vstutils.models.custom_model.ExternalCustomModel.get_data_generator +#: vstutils.tests.BaseTestCase.assertCheckDict +#: vstutils.tests.BaseTestCase.assertCount +#: vstutils.tests.BaseTestCase.assertRCode vstutils.tests.BaseTestCase.bulk +#: vstutils.tests.BaseTestCase.bulk_transactional +#: vstutils.tests.BaseTestCase.call_registration +#: vstutils.tests.BaseTestCase.details_test +#: vstutils.tests.BaseTestCase.endpoint_call +#: vstutils.tests.BaseTestCase.endpoint_schema +#: vstutils.tests.BaseTestCase.get_count +#: vstutils.tests.BaseTestCase.get_model_class +#: vstutils.tests.BaseTestCase.get_model_filter +#: vstutils.tests.BaseTestCase.get_result vstutils.tests.BaseTestCase.list_test +#: vstutils.tests.BaseTestCase.patch_field_default +#: vstutils.tests.BaseTestCase.user_as +#: vstutils.utils.BaseVstObject.get_django_settings vstutils.utils.Executor +#: vstutils.utils.Executor.aexecute vstutils.utils.Executor.execute +#: vstutils.utils.Executor.post_execute vstutils.utils.Executor.pre_execute +#: vstutils.utils.Executor.working_handler vstutils.utils.Executor.write_output +#: vstutils.utils.Lock vstutils.utils.ModelHandlers +#: vstutils.utils.ModelHandlers.get_object vstutils.utils.ObjectHandlers +#: vstutils.utils.ObjectHandlers.backend vstutils.utils.URLHandlers +#: vstutils.utils.URLHandlers.get_object vstutils.utils.UnhandledExecutor +#: vstutils.utils.classproperty vstutils.utils.create_view +#: vstutils.utils.decode vstutils.utils.deprecated vstutils.utils.encode +#: vstutils.utils.get_render vstutils.utils.lazy_translate +#: vstutils.utils.list_to_choices vstutils.utils.send_template_email +#: vstutils.utils.send_template_email_handler vstutils.utils.tmp_file +#: vstutils.utils.tmp_file.write vstutils.utils.translate +msgid "Parameters" +msgstr "Параметры" + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:14 +msgid "An object containing filtering, limiting, and sorting parameters." +msgstr "Объект, содержащий параметры фильтрации, лимитирования и сортировки." + +#: of vstutils.api.base.GenericViewSet.create_action_serializer +#: vstutils.api.filters.extra_filter vstutils.api.filters.name_filter +#: vstutils.api.validators.resize_image +#: vstutils.api.validators.resize_image_from_to +#: vstutils.middleware.BaseMiddleware.handler +#: vstutils.middleware.BaseMiddleware.request_handler +#: vstutils.models.custom_model.ExternalCustomModel.get_data_generator +#: vstutils.models.custom_model.ViewCustomModel.get_view_queryset +#: vstutils.tests.BaseTestCase.bulk +#: vstutils.tests.BaseTestCase.bulk_transactional +#: vstutils.tests.BaseTestCase.endpoint_call +#: vstutils.tests.BaseTestCase.get_count +#: vstutils.tests.BaseTestCase.get_model_class +#: vstutils.tests.BaseTestCase.get_result vstutils.tests.BaseTestCase.get_url +#: vstutils.utils.BaseVstObject.get_django_settings +#: vstutils.utils.Executor.aexecute vstutils.utils.Executor.execute +#: vstutils.utils.Executor.write_output vstutils.utils.ModelHandlers.get_object +#: vstutils.utils.ObjectHandlers.backend vstutils.utils.URLHandlers.get_object +#: vstutils.utils.decode vstutils.utils.encode vstutils.utils.get_render +#: vstutils.utils.list_to_choices vstutils.utils.send_template_email_handler +#: vstutils.utils.tmp_file.write +msgid "Returns" +msgstr "Возвращает" + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:17 +msgid "A generator that yields the requested data." +msgstr "Генератор, возвращающий запрошенные данные." + +#: of vstutils.api.base.GenericViewSet.create_action_serializer +#: vstutils.api.endpoint.EndpointViewSet.get +#: vstutils.api.endpoint.EndpointViewSet.get_client +#: vstutils.api.endpoint.EndpointViewSet.get_serializer +#: vstutils.api.endpoint.EndpointViewSet.get_serializer_context +#: vstutils.api.endpoint.EndpointViewSet.operate +#: vstutils.api.endpoint.EndpointViewSet.post +#: vstutils.api.endpoint.EndpointViewSet.put vstutils.api.filters.extra_filter +#: vstutils.api.filters.name_filter vstutils.api.validators.resize_image +#: vstutils.api.validators.resize_image_from_to +#: vstutils.middleware.BaseMiddleware.get_response_handler +#: vstutils.middleware.BaseMiddleware.handler +#: vstutils.middleware.BaseMiddleware.request_handler +#: vstutils.models.custom_model.ExternalCustomModel.get_data_generator +#: vstutils.models.custom_model.ViewCustomModel.get_view_queryset +#: vstutils.tasks.TaskClass.do vstutils.tests.BaseTestCase.bulk +#: vstutils.tests.BaseTestCase.bulk_transactional +#: vstutils.tests.BaseTestCase.endpoint_call +#: vstutils.tests.BaseTestCase.get_count +#: vstutils.tests.BaseTestCase.get_model_class +#: vstutils.tests.BaseTestCase.get_model_filter +#: vstutils.tests.BaseTestCase.get_result vstutils.tests.BaseTestCase.get_url +#: vstutils.tests.BaseTestCase.patch +#: vstutils.tests.BaseTestCase.patch_field_default +#: vstutils.tests.BaseTestCase.random_name vstutils.utils.Executor.write_output +#: vstutils.utils.ModelHandlers.get_object +#: vstutils.utils.ObjectHandlers.backend vstutils.utils.URLHandlers.get_object +#: vstutils.utils.create_view vstutils.utils.decode vstutils.utils.encode +#: vstutils.utils.get_render vstutils.utils.tmp_file.write +msgid "Return type" +msgstr "Тип возвращаемого значения" + +#: of vstutils.api.validators.RegularExpressionValidator +#: vstutils.models.custom_model.ExternalCustomModel.get_data_generator +#: vstutils.models.custom_model.ViewCustomModel.get_view_queryset +msgid "Raises" +msgstr "Выбрасывает" + +#: of vstutils.models.custom_model.ExternalCustomModel.get_data_generator:20 +#: vstutils.models.custom_model.ViewCustomModel.get_view_queryset:7 +msgid "If the method is not implemented by the derived class." +msgstr "Если метод не реализован в производном классе." #: of vstutils.models.custom_model.FileModel:1 msgid "" -"Custom model which loads data from YAML-file instead of database. Path to" -" the file stored in `FileModel.file_path` attribute." +"Custom model that loads data from a YAML file instead of a database. The " +"path to the file is specified in the `FileModel.file_path` attribute." msgstr "" -"Модель, загружающая данные из YAML-файла вместо базы данных. Путь до " -"файла хранится в атрибуте `FileModel.file_path`." +"Кастомная модель, загружающая данные из YAML-файла вместо базы данных. " +"Путь к файлу указывается в атрибуте `FileModel.file_path`." -#: of vstutils.models.custom_model.FileModel:6 -msgid "Source file stored in `/etc/authors.yaml` with content:" +#: of vstutils.models.custom_model.FileModel:5 +msgid "" +"Suppose the source file is stored at `/etc/authors.yaml` with the " +"following content:" msgstr "" -"Исходный файл хранится в `/etc/authors.yaml` вместе со следующим " -"содержимым:" +"Предположим, что исходный файл хранится в `/etc/authors.yaml` со " +"следующим содержимым:" -#: of vstutils.api.base.FileResponseRetrieveMixin:3 -#: vstutils.api.decorators.nested_view:37 -#: vstutils.api.filter_backends.DeepViewFilterBackend:25 -#: vstutils.api.filter_backends.VSTFilterBackend:22 -#: vstutils.api.filters.FkFilterHandler:15 -#: vstutils.middleware.BaseMiddleware:38 -#: vstutils.models.custom_model.FileModel:13 vstutils.tasks.TaskClass:4 -#: vstutils.utils.BaseEnum:4 vstutils.utils.Lock:35 -#: vstutils.utils.ObjectHandlers:31 vstutils.utils.SecurePickling:22 -#: vstutils.utils.URLHandlers:10 vstutils.utils.apply_decorators:18 -#: vstutils.utils.classproperty:20 vstutils.utils.create_view:17 -#: vstutils.utils.raise_context_decorator_with_default:19 -msgid "Example:" -msgstr "Пример:" +#: of vstutils.models.custom_model.FileModel:12 +msgid "You can create a custom model using this file:" +msgstr "Вы можете создать кастомную модель, используя этот файл:" #: of vstutils.models.custom_model.ListModel:1 msgid "" @@ -607,21 +865,48 @@ msgstr "" "`setup_custom_queryset_kwargs`, и каждый последующий вызов в цепочке " "методов будет работать с этими данными." -#: ../../docstring of vstutils.models.custom_model.ListModel.data:1 +#: of vstutils.api.base.ModelViewSet vstutils.api.responses.BaseResponseClass +#: vstutils.models.custom_model.ListModel +msgid "Variables" +msgstr "Переменные" + +#: of vstutils.models.custom_model.ListModel:40 msgid "List with data dicts. Empty by default." msgstr "Список кортежей данных. Пустой по умолчанию." #: of vstutils.models.custom_model.ViewCustomModel:1 +msgid "Implements the SQL View programming mechanism over other models." +msgstr "Реализует механизм программирования SQL View над другими моделями." + +#: of vstutils.models.custom_model.ViewCustomModel:3 msgid "" -"This model implements the SQL View programming mechanism over other " -"models. In the ``get_view_queryset()`` method, a query is prepared, and " -"all further actions are implemented on top of it." +"This model provides a mechanism for implementing SQL View-like behavior " +"over other models. In the ``get_view_queryset()`` method, a base query is" +" prepared, and all further actions are implemented on top of it." msgstr "" -"Эта модель реализует механизм программирования SQL View над другими " -"моделями. В методе ``get_view_queryset()`` подготавливается запрос, и все" -" остальные действия осуществляются поверх него." +"Эта модель предоставляет механизм для реализации поведения, аналогичного " +"SQL View, над другими моделями. В методе ``get_view_queryset()`` " +"подготавливается базовый запрос, и все последующие действия реализуются " +"поверх него." + +#: of vstutils.models.custom_model.ViewCustomModel:7 +msgid "**Example Usage:**" +msgstr "**Примеры:**" -#: ../../backend.rst:29 +#: of vstutils.models.custom_model.ViewCustomModel.get_view_queryset:1 +msgid "" +"This class method must be implemented by derived classes to define custom" +" logic for generating the base queryset for the SQL View." +msgstr "" +"Этот метод класса должен быть реализован в производных классах для " +"определения пользовательской логики создания базового queryset для SQL " +"View." + +#: of vstutils.models.custom_model.ViewCustomModel.get_view_queryset:4 +msgid "The base queryset for the SQL View." +msgstr "Базовый queryset для SQL View." + +#: ../../backend.rst:36 msgid "Model Fields" msgstr "Поля Модели" @@ -785,11 +1070,11 @@ msgstr "" ":class:`django.db.models.TextField`, поэтому не поддерживает индексацию и" " не рекомендовано для использования в фильтрах." -#: ../../backend.rst:36 +#: ../../backend.rst:43 msgid "Web API" msgstr "Веб-API" -#: ../../backend.rst:38 +#: ../../backend.rst:45 msgid "" "Web API is based on Django Rest Framework with additional nested " "functions." @@ -797,11 +1082,11 @@ msgstr "" "Веб-API основано на Django Rest Framework. Предоставляет дополнительные " "вложенные функции." -#: ../../backend.rst:41 +#: ../../backend.rst:48 msgid "Fields" msgstr "Поля" -#: ../../backend.rst:43 +#: ../../backend.rst:50 msgid "" "The Framework includes a list of convenient serializer fields. Some of " "them take effect only in generated admin interface." @@ -824,74 +1109,6 @@ msgstr "" "Поле, предоставляющее автодополнение на фронтенде. Использует указанный " "список объектов." -#: of vstutils.api.actions.Action -#: vstutils.api.base.GenericViewSet.create_action_serializer -#: vstutils.api.base.GenericViewSet.get_query_serialized_data -#: vstutils.api.decorators.nested_view vstutils.api.decorators.subaction -#: vstutils.api.endpoint.EndpointViewSet.get -#: vstutils.api.endpoint.EndpointViewSet.get_client -#: vstutils.api.endpoint.EndpointViewSet.operate -#: vstutils.api.endpoint.EndpointViewSet.post -#: vstutils.api.endpoint.EndpointViewSet.put -#: vstutils.api.fields.AutoCompletionField vstutils.api.fields.Barcode128Field -#: vstutils.api.fields.BinFileInStringField vstutils.api.fields.CSVFileField -#: vstutils.api.fields.CommaMultiSelect vstutils.api.fields.DeepFkField -#: vstutils.api.fields.DependEnumField vstutils.api.fields.DependFromFkField -#: vstutils.api.fields.DynamicJsonTypeField -#: vstutils.api.fields.FileInStringField vstutils.api.fields.FkField -#: vstutils.api.fields.FkModelField vstutils.api.fields.MaskedField -#: vstutils.api.fields.MultipleNamedBinaryImageInJsonField -#: vstutils.api.fields.NamedBinaryImageInJsonField -#: vstutils.api.fields.QrCodeField vstutils.api.fields.RatingField -#: vstutils.api.fields.RedirectFieldMixin vstutils.api.fields.RelatedListField -#: vstutils.api.fields.SecretFileInString vstutils.api.fields.WYSIWYGField -#: vstutils.api.filters.FkFilterHandler vstutils.api.filters.extra_filter -#: vstutils.api.filters.name_filter vstutils.api.responses.BaseResponseClass -#: vstutils.api.validators.FileMediaTypeValidator -#: vstutils.api.validators.ImageBaseSizeValidator -#: vstutils.api.validators.ImageHeightValidator -#: vstutils.api.validators.ImageOpenValidator -#: vstutils.api.validators.ImageResolutionValidator -#: vstutils.api.validators.ImageValidator -#: vstutils.api.validators.ImageWidthValidator -#: vstutils.api.validators.RegularExpressionValidator -#: vstutils.api.validators.UrlQueryStringValidator -#: vstutils.api.validators.resize_image -#: vstutils.api.validators.resize_image_from_to -#: vstutils.middleware.BaseMiddleware.get_response_handler -#: vstutils.middleware.BaseMiddleware.handler -#: vstutils.middleware.BaseMiddleware.request_handler -#: vstutils.tests.BaseTestCase.assertCheckDict -#: vstutils.tests.BaseTestCase.assertCount -#: vstutils.tests.BaseTestCase.assertRCode vstutils.tests.BaseTestCase.bulk -#: vstutils.tests.BaseTestCase.bulk_transactional -#: vstutils.tests.BaseTestCase.call_registration -#: vstutils.tests.BaseTestCase.details_test -#: vstutils.tests.BaseTestCase.endpoint_call -#: vstutils.tests.BaseTestCase.endpoint_schema -#: vstutils.tests.BaseTestCase.get_count -#: vstutils.tests.BaseTestCase.get_model_class -#: vstutils.tests.BaseTestCase.get_model_filter -#: vstutils.tests.BaseTestCase.get_result vstutils.tests.BaseTestCase.list_test -#: vstutils.tests.BaseTestCase.patch_field_default -#: vstutils.tests.BaseTestCase.user_as -#: vstutils.utils.BaseVstObject.get_django_settings vstutils.utils.Executor -#: vstutils.utils.Executor.aexecute vstutils.utils.Executor.execute -#: vstutils.utils.Executor.post_execute vstutils.utils.Executor.pre_execute -#: vstutils.utils.Executor.working_handler vstutils.utils.Executor.write_output -#: vstutils.utils.Lock vstutils.utils.ModelHandlers -#: vstutils.utils.ModelHandlers.get_object vstutils.utils.ObjectHandlers -#: vstutils.utils.ObjectHandlers.backend vstutils.utils.URLHandlers -#: vstutils.utils.URLHandlers.get_object vstutils.utils.UnhandledExecutor -#: vstutils.utils.classproperty vstutils.utils.create_view -#: vstutils.utils.decode vstutils.utils.deprecated vstutils.utils.encode -#: vstutils.utils.get_render vstutils.utils.lazy_translate -#: vstutils.utils.list_to_choices vstutils.utils.send_template_email -#: vstutils.utils.send_template_email_handler vstutils.utils.tmp_file -#: vstutils.utils.tmp_file.write vstutils.utils.translate -msgid "Parameters" -msgstr "Параметры" - #: of vstutils.api.fields.AutoCompletionField:3 msgid "" "Autocompletion reference. You can set list/tuple with values or set " @@ -928,7 +1145,6 @@ msgstr "" "умолчанию." #: of vstutils.api.fields.AutoCompletionField:18 -#: vstutils.api.fields.BinFileInStringField:8 #: vstutils.api.fields.CommaMultiSelect:22 vstutils.api.fields.HtmlField:7 #: vstutils.api.fields.NamedBinaryFileInJsonField:18 #: vstutils.api.fields.TextareaField:4 @@ -956,19 +1172,33 @@ msgstr "" #: of vstutils.api.fields.BinFileInStringField:1 msgid "" -"Field extends :class:`.FileInStringField`, but works with binary (base64)" -" files." +"This field extends :class:`.FileInStringField` and is specifically " +"designed to handle binary files. In the GUI, it functions as a file input" +" field, accepting binary files from the user, which are then converted to" +" base64-encoded strings and stored in this field." msgstr "" -"Поле, расширяющее :class:`.FileInStringField`, но работающее также с " -"бинарными (base64) файлами." +"Это поле расширяет функциональность :class:`.FileInStringField` и " +"специально предназначено для обработки бинарных файлов. В интерфейсе " +"пользователя оно выступает в качестве поля для загрузки файлов, принимая " +"бинарные файлы от пользователя, которые затем конвертируются в строку в " +"формате base64 и сохраняются в данном поле." + -#: of vstutils.api.fields.BinFileInStringField:3 +#: of vstutils.api.fields.BinFileInStringField:5 msgid "" -"List of MIME types to select on the user's side. Supported syntax using " -"``*``. Default: `['*/*']`" +"A list of MIME types to filter on the user's side. Supports the use of " +"``*`` as a wildcard. Default: `['*/*']`" msgstr "" "Список MIME-типов, доступных для выбора пользователем. Поддерживается " -"синтаксис с использованием ``*``. По умолчанию `['*/*']`" +"синтаксис с использованием ``*``. По умолчанию ``['*/*']``" + +#: of vstutils.api.fields.BinFileInStringField:10 +msgid "" +"This functionality is effective only in the GUI. In the API, it behaves " +"similarly to :class:`.VSTCharField`." +msgstr "" +"Действует только в графическом интерфейсе. В API ведет себя так же, как и" +" :class:`.VSTCharField`." #: of vstutils.api.fields.CSVFileField:1 msgid "" @@ -1337,28 +1567,32 @@ msgstr "" ":class:`.VSTCharField` без изменения значения." #: of vstutils.api.fields.FileInStringField:1 -msgid "Field extends :class:`.VSTCharField` and saves file's content as string." +msgid "" +"This field extends :class:`.VSTCharField` and stores the content of a " +"file as a string." msgstr "" "Поле, расширяющее :class:`.VSTCharField`. Сохраняет содержимое файла в " "виде строки." #: of vstutils.api.fields.FileInStringField:3 #: vstutils.api.fields.SecretFileInString:3 -msgid "Value must be text (not binary) and saves in model as is." +msgid "The value must be text (not binary) and is saved in the model as is." msgstr "Поле должно быть текстовым (не бинарным). Сохраняется в модель как есть." #: of vstutils.api.fields.FileInStringField:5 #: vstutils.api.fields.SecretFileInString:5 msgid "" -"List of MIME types to select on the user's side. Supported syntax using " -"``*``. Default: ``['*/*']``" +"A list of MIME types to filter on the user's side. Supports the use of " +"``*`` as a wildcard. Default: ``['*/*']``" msgstr "" "Список MIME-типов, доступных для выбора пользователем. Поддерживается " "синтаксис с использованием ``*``. По умолчанию ``['*/*']``" #: of vstutils.api.fields.FileInStringField:10 #: vstutils.api.fields.SecretFileInString:10 -msgid "Take effect only in GUI. In API it would behave as :class:`.VSTCharField`." +msgid "" +"This setting only takes effect in the GUI. In the API, it behaves like " +":class:`.VSTCharField`." msgstr "" "Действует только в графическом интерфейсе. В API ведет себя так же, как и" " :class:`.VSTCharField`." @@ -1859,8 +2093,8 @@ msgstr "" #: of vstutils.api.fields.SecretFileInString:1 msgid "" -"Field extends :class:`.FileInStringField`, but hides it's value in admin " -"interface." +"This field extends :class:`.FileInStringField` but hides its value in the" +" admin interface." msgstr "" "Поле, расширяющее :class:`.FileInStringField`, но скрывающее свое " "значение в интерфейсе администратора." @@ -1895,11 +2129,11 @@ msgstr "" msgid "html-escape input. Enabled by default." msgstr "экранирование входящих html-символов. Включено по умолчанию." -#: ../../backend.rst:49 +#: ../../backend.rst:56 msgid "Validators" msgstr "Валидаторы" -#: ../../backend.rst:51 +#: ../../backend.rst:58 msgid "There are validation classes for fields." msgstr "Классы для валидации полей." @@ -2005,10 +2239,6 @@ msgstr "Обертка для ImageBaseSizeValidator, проверяющая т msgid "Class for regular expression based validation" msgstr "Класс для валидации на основе регулярного выражения" -#: of vstutils.api.validators.RegularExpressionValidator -msgid "Raises" -msgstr "Выбрасывает" - #: of vstutils.api.validators.RegularExpressionValidator:3 msgid "in case value does not match regular expression" msgstr "в случае, если значение не соответствует регулярному выражению" @@ -2041,58 +2271,6 @@ msgstr "Необходимая ширина" msgid "Required height" msgstr "Необходимая высота" -#: of vstutils.api.base.GenericViewSet.create_action_serializer -#: vstutils.api.filters.extra_filter vstutils.api.filters.name_filter -#: vstutils.api.validators.resize_image -#: vstutils.api.validators.resize_image_from_to -#: vstutils.middleware.BaseMiddleware.handler -#: vstutils.middleware.BaseMiddleware.request_handler -#: vstutils.tests.BaseTestCase.bulk -#: vstutils.tests.BaseTestCase.bulk_transactional -#: vstutils.tests.BaseTestCase.endpoint_call -#: vstutils.tests.BaseTestCase.get_count -#: vstutils.tests.BaseTestCase.get_model_class -#: vstutils.tests.BaseTestCase.get_result vstutils.tests.BaseTestCase.get_url -#: vstutils.utils.BaseVstObject.get_django_settings -#: vstutils.utils.Executor.aexecute vstutils.utils.Executor.execute -#: vstutils.utils.Executor.write_output vstutils.utils.ModelHandlers.get_object -#: vstutils.utils.ObjectHandlers.backend vstutils.utils.URLHandlers.get_object -#: vstutils.utils.decode vstutils.utils.encode vstutils.utils.get_render -#: vstutils.utils.list_to_choices vstutils.utils.send_template_email_handler -#: vstutils.utils.tmp_file.write -msgid "Returns" -msgstr "Возвращает" - -#: of vstutils.api.base.GenericViewSet.create_action_serializer -#: vstutils.api.endpoint.EndpointViewSet.get -#: vstutils.api.endpoint.EndpointViewSet.get_client -#: vstutils.api.endpoint.EndpointViewSet.get_serializer -#: vstutils.api.endpoint.EndpointViewSet.get_serializer_context -#: vstutils.api.endpoint.EndpointViewSet.operate -#: vstutils.api.endpoint.EndpointViewSet.post -#: vstutils.api.endpoint.EndpointViewSet.put vstutils.api.filters.extra_filter -#: vstutils.api.filters.name_filter vstutils.api.validators.resize_image -#: vstutils.api.validators.resize_image_from_to -#: vstutils.middleware.BaseMiddleware.get_response_handler -#: vstutils.middleware.BaseMiddleware.handler -#: vstutils.middleware.BaseMiddleware.request_handler -#: vstutils.tasks.TaskClass.do vstutils.tests.BaseTestCase.bulk -#: vstutils.tests.BaseTestCase.bulk_transactional -#: vstutils.tests.BaseTestCase.endpoint_call -#: vstutils.tests.BaseTestCase.get_count -#: vstutils.tests.BaseTestCase.get_model_class -#: vstutils.tests.BaseTestCase.get_model_filter -#: vstutils.tests.BaseTestCase.get_result vstutils.tests.BaseTestCase.get_url -#: vstutils.tests.BaseTestCase.patch -#: vstutils.tests.BaseTestCase.patch_field_default -#: vstutils.tests.BaseTestCase.random_name vstutils.utils.Executor.write_output -#: vstutils.utils.ModelHandlers.get_object -#: vstutils.utils.ObjectHandlers.backend vstutils.utils.URLHandlers.get_object -#: vstutils.utils.create_view vstutils.utils.decode vstutils.utils.encode -#: vstutils.utils.get_render vstutils.utils.tmp_file.write -msgid "Return type" -msgstr "Тип возвращаемого значения" - #: of vstutils.api.validators.resize_image_from_to:1 msgid "" "Utility function to resize image proportional to values between min and " @@ -2112,7 +2290,7 @@ msgstr "" "Словарь с максимальным/минимальным ограничениями, например ``{'width': " "{'min': 300, 'max: 600'}, 'height': {'min': 400, 'max: 800'}}``" -#: ../../backend.rst:57 +#: ../../backend.rst:64 msgid "Serializers" msgstr "Сериализаторы" @@ -2154,8 +2332,8 @@ msgid "" "For any serializer displayed on frontend property `_display_mode` can be " "set to one of this values." msgstr "" -"Для любого сериализатора, показанного на фронтенде, аттрибут `_display_mode` может принимать " -"одно из следующих значений." +"Для любого сериализатора, показанного на фронтенде, аттрибут " +"`_display_mode` может принимать одно из следующих значений." #: ../../docstring of vstutils.api.serializers.DisplayMode.DEFAULT:1 msgid "Will be used if no mode provided." @@ -2166,8 +2344,8 @@ msgid "" "Each properties group displayed as separate tab. On creation displayed as" " multiple steps." msgstr "" -"Каждая группа параметров отображается на раздельных вкладках. " -"При создании выглядит как пошаговый мастер." +"Каждая группа параметров отображается на раздельных вкладках. При " +"создании выглядит как пошаговый мастер." #: of vstutils.api.serializers.EmptySerializer:1 msgid "" @@ -2200,7 +2378,7 @@ msgstr "" ":class:`vstutils.api.fields.FkModelField` в сериализаторе, задайте " ":class:`vstutils.models.fields.FkModelField` в модели." -#: ../../backend.rst:63 +#: ../../backend.rst:70 msgid "Views" msgstr "Представления" @@ -2394,10 +2572,6 @@ msgstr "" "Viewset, предоставляющий CRUD-экшены над моделью. Наследуется от " ":class:`.GenericViewSet`." -#: of vstutils.api.base.ModelViewSet vstutils.api.responses.BaseResponseClass -msgid "Variables" -msgstr "Переменные" - #: of vstutils.api.base.ModelViewSet:3 msgid "DB model with data." msgstr "Модель БД с данными." @@ -2563,11 +2737,11 @@ msgstr "Заменить заголовок действия." msgid "Setup action icon classes." msgstr "Настроить классы иконок действия." -#: ../../backend.rst:73 +#: ../../backend.rst:80 msgid "Actions" msgstr "Actions (Действия)" -#: ../../backend.rst:75 +#: ../../backend.rst:82 msgid "" "Vstutils has the advanced system of working with actions. REST API works " "with data through verbs, which are called methods. However, to work with " @@ -2577,7 +2751,7 @@ msgstr "" "данными через глаголы, которые называются методами. Однако для работы с " "одной или списком сущностей этих действий может быть недостаточно." -#: ../../backend.rst:79 +#: ../../backend.rst:86 msgid "" "To expand the set of actions, you need to create an action that will work" " with some aspect of the described model. For these purposes, there is a " @@ -2592,7 +2766,7 @@ msgstr "" "также можно расширить с помощью схемы. Но для большего удобства в " "vstutils есть набор декораторов, которые позволяют избежать написания " -#: ../../backend.rst:83 +#: ../../backend.rst:90 msgid "" "The main philosophy for these wrappers is that the developer writes " "business logic without being distracted by the boilerplate code. Often, " @@ -2764,11 +2938,11 @@ msgstr "" "При назначении действия на объект список методов также заполняется " "необходимыми методами." -#: ../../backend.rst:92 +#: ../../backend.rst:99 msgid "Filtersets" msgstr "Filterset'ы" -#: ../../backend.rst:94 +#: ../../backend.rst:101 msgid "" "For greater development convenience, the framework provides additional " "classes and functions for filtering elements by fields." @@ -2854,11 +3028,11 @@ msgstr "" msgid "searching part of name." msgstr "часть названия для поиска." -#: ../../backend.rst:102 +#: ../../backend.rst:109 msgid "Responses" msgstr "Ответы (responses)" -#: ../../backend.rst:104 +#: ../../backend.rst:111 msgid "" "DRF provides a standard set of variables whose names correspond to the " "human-readable name of the HTTP code. For convenience, we have " @@ -2870,7 +3044,7 @@ msgstr "" "оборачиваем их в набор классов с соответствующими именами и дополнительно" " обеспечиваем следующие возможности:" -#: ../../backend.rst:109 +#: ../../backend.rst:116 msgid "" "String responses are wrapped in json like ``{ \"detail\": \"string " "response\" }``." @@ -2878,11 +3052,11 @@ msgstr "" "Строковые ответы оборачиваются в json, например ``{ \"detail\": \"string " "response\" }``." -#: ../../backend.rst:110 +#: ../../backend.rst:117 msgid "Attribute timings are kept for further processing in middleware." msgstr "Тайминги атрибутов сохраняются для дальнейшей обработки в middleware." -#: ../../backend.rst:111 +#: ../../backend.rst:118 msgid "" "Status code is set by class name (e.g. ``HTTP_200_OK`` or ``Response200``" " has code 200)." @@ -2890,7 +3064,7 @@ msgstr "" "Код состояния задается именем класса (например ``HTTP_200_OK`` или " "``Response200`` имеют код 200)." -#: ../../backend.rst:113 +#: ../../backend.rst:120 msgid "All classes inherit from:" msgstr "Все классы наследуются от:" @@ -2907,11 +3081,11 @@ msgstr "Код состояния HTTP." msgid "Response timings." msgstr "Тайминги ответов." -#: ../../backend.rst:120 +#: ../../backend.rst:127 msgid "Middleware" msgstr "Middleware" -#: ../../backend.rst:122 +#: ../../backend.rst:129 msgid "" "By default, Django `supposes " "`_ are used to modify model " @@ -3172,11 +3346,11 @@ msgstr "" "автогенерации схемы, предоставляемой REST-фреймворком, реализуя этот " "метод. Метод должен возвращать список OpenAPI сопоставлений схемы." -#: ../../backend.rst:141 +#: ../../backend.rst:148 msgid "Celery" msgstr "Celery" -#: ../../backend.rst:143 +#: ../../backend.rst:150 msgid "" "Celery is a distributed task queue. It's used to execute some actions " "asynchronously in a separate worker. For more details on Celery, check " @@ -3195,7 +3369,7 @@ msgstr "" "settings>`_ в settings.ini. Также вам понадобится установить " "дополнительные [rpc] зависимости." -#: ../../backend.rst:149 +#: ../../backend.rst:156 msgid "Tasks" msgstr "Tasks" @@ -3257,11 +3431,11 @@ msgstr "" msgid "The body of the task executed by workers." msgstr "Тело задачи выполняется worker'ами." -#: ../../backend.rst:155 +#: ../../backend.rst:162 msgid "Endpoint" msgstr "Endpoint" -#: ../../backend.rst:157 +#: ../../backend.rst:164 msgid "" "Endpoint view has two purposes: bulk requests execution and providing " "OpenAPI schema." @@ -3269,7 +3443,7 @@ msgstr "" "Endpoint-view имеет две цели: выполнение bulk-запросов и предоставление " "схемы OpenAPI." -#: ../../backend.rst:159 +#: ../../backend.rst:166 msgid "" "Endpoint url is ``/{API_URL}/endpoint/``, for example value with default " "settings is ``/api/endpoint/``." @@ -3277,7 +3451,7 @@ msgstr "" "URL endpoint'а - ``/{API_URL}/endpoint/``, например значение с " "настройками по умолчанию - ``/api/endpoint/``." -#: ../../backend.rst:161 +#: ../../backend.rst:168 msgid "``API_URL`` can be changed in ``settings.py``." msgstr "``API_URL`` может быть изменен в ``settings.py``." @@ -3351,11 +3525,11 @@ msgstr "Выполнить нетранзакционный bulk-запрос" msgid "One operation serializer class." msgstr "Класс сериализатора одной операции." -#: ../../backend.rst:168 +#: ../../backend.rst:175 msgid "Bulk requests" msgstr "Bulk-запросы" -#: ../../backend.rst:170 +#: ../../backend.rst:177 msgid "" "Bulk request allows you send multiple requests to api at once, it accepts" " json list of operations." @@ -3363,39 +3537,39 @@ msgstr "" "Bulk-запрос позволяет вам отсылать несколько запросов к api в одном. Он " "принимает json-список операций." -#: ../../backend.rst:173 +#: ../../backend.rst:180 msgid "Method" msgstr "Метод" -#: ../../backend.rst:173 +#: ../../backend.rst:180 msgid "Transactional (all operations in one transaction)" msgstr "Транзакционный (все операции в одной транзакции)" -#: ../../backend.rst:173 +#: ../../backend.rst:180 msgid "Synchronous (operations executed one by one in given order)" msgstr "Синхронный (операции выполняются одна за другой в указанном порядке)" -#: ../../backend.rst:177 +#: ../../backend.rst:184 msgid "``PUT /{API_URL}/endpoint/``" msgstr "``PUT /{API_URL}/endpoint/``" -#: ../../backend.rst:177 ../../backend.rst:181 +#: ../../backend.rst:184 ../../backend.rst:188 msgid "NO" msgstr "НЕТ" -#: ../../backend.rst:177 ../../backend.rst:179 +#: ../../backend.rst:184 ../../backend.rst:186 msgid "YES" msgstr "ДА" -#: ../../backend.rst:179 +#: ../../backend.rst:186 msgid "``POST /{API_URL}/endpoint/``" msgstr "``POST /{API_URL}/endpoint/``" -#: ../../backend.rst:181 +#: ../../backend.rst:188 msgid "``PATCH /{API_URL}/endpoint/``" msgstr "``PATCH /{API_URL}/endpoint/``" -#: ../../backend.rst:184 +#: ../../backend.rst:191 msgid "" "Parameters of one operation (required parameter marked by " ":superscript:`*`):" @@ -3403,25 +3577,25 @@ msgstr "" "Параметры одной операции (обязательный параметр помечается " ":superscript:`*`):" -#: ../../backend.rst:186 +#: ../../backend.rst:193 msgid "``method``:superscript:`*` - http method of request" msgstr "``method``:superscript:`*` - http-метод запроса" -#: ../../backend.rst:187 +#: ../../backend.rst:194 msgid "``path``:superscript:`*` - path of request, can be ``str`` or ``list``" msgstr "" "``path``:superscript:`*` - путь запроса, может быть типа ``str`` или " "``list``" -#: ../../backend.rst:188 +#: ../../backend.rst:195 msgid "``data`` - data to send" msgstr "``data`` - данные для отправки" -#: ../../backend.rst:189 +#: ../../backend.rst:196 msgid "``query`` - query parameters as ``str``" msgstr "``query`` - query-параметры типа ``str``" -#: ../../backend.rst:190 +#: ../../backend.rst:197 msgid "" "``let`` - string with name of variable (used for access to response " "result in templates)" @@ -3429,7 +3603,7 @@ msgstr "" "``let`` - строка с именем переменной (используется для доступа к " "результату ответа в шаблонах)" -#: ../../backend.rst:191 +#: ../../backend.rst:198 msgid "" "``headers`` - ``dict`` with headers which will be sent (key - header's " "name, value - header's value string)." @@ -3437,7 +3611,7 @@ msgstr "" "``headers`` - словарь с заголовками, которые будут переданы в запрос " "(ключ - имя заголовка, значение - строка со значением заголовка." -#: ../../backend.rst:192 +#: ../../backend.rst:199 msgid "" "``version`` - ``str`` with specified version of api, if not provided then" " ``VST_API_VERSION`` will be used" @@ -3445,7 +3619,7 @@ msgstr "" "``version`` - ``str`` с указанной версией api, если не задано, то " "используется ``VST_API_VERSION``" -#: ../../backend.rst:196 +#: ../../backend.rst:203 msgid "" "In previous versions header's names must follow `CGI specification " "`_ (e.g., ``CONTENT_TYPE``, " @@ -3455,7 +3629,7 @@ msgstr "" "`спецификации CGI `_ (например, " "``CONTENT_TYPE``, ``GATEWAY_INTERFACE``, ``HTTP_*``)." -#: ../../backend.rst:200 +#: ../../backend.rst:207 msgid "" "Since version 5.3 and after migrate to Django 4 names must follow HTTP " "specification instead of CGI." @@ -3463,7 +3637,7 @@ msgstr "" "Начиная с версии 5.3 и после миграции на Django 4 имена должны " "соответствовать HTTP спецификации вместо CGI." -#: ../../backend.rst:202 +#: ../../backend.rst:209 msgid "" "In any request parameter you can insert result value of previous " "operations (``<<{OPERATION_NUMBER or LET_VALUE}[path][to][value]>>``), " @@ -3473,27 +3647,27 @@ msgstr "" " операции (``<<{OPERATION_NUMBER or LET_VALUE}[path][to][value]>>``), " "например:" -#: ../../backend.rst:212 +#: ../../backend.rst:219 msgid "Result of bulk request is json list of objects for operation:" msgstr "Результат bulk-запроса - это список json-объектов, описывающих операцию:" -#: ../../backend.rst:214 +#: ../../backend.rst:221 msgid "``method`` - http method" msgstr "``method`` - http-метод" -#: ../../backend.rst:215 +#: ../../backend.rst:222 msgid "``path`` - path of request, always str" msgstr "``path`` - путь запроса, всегда строка" -#: ../../backend.rst:216 +#: ../../backend.rst:223 msgid "``data`` - data that needs to be sent" msgstr "``data`` - данные, которые нужно отправить" -#: ../../backend.rst:217 +#: ../../backend.rst:224 msgid "``status`` - response status code" msgstr "``status`` - код состояния ответа" -#: ../../backend.rst:219 +#: ../../backend.rst:226 msgid "" "Transactional bulk request returns ``502 BAG GATEWAY`` and does rollback " "after first failed request." @@ -3501,7 +3675,7 @@ msgstr "" "Транзакционный bulk-запрос возвращает ``502 BAG GATEWAY`` и делает откат " "к состоянию до запроса после первого неудачного запроса." -#: ../../backend.rst:222 +#: ../../backend.rst:229 msgid "" "If you send non-transactional bulk request, you will get ``200`` status " "and must validate statuses on each operation responses." @@ -3509,15 +3683,15 @@ msgstr "" "Если вы отправили нетранзакционный bulk-запрос, вы получите код 200 и " "должны будете проверить статус каждого ответа операции отдельно." -#: ../../backend.rst:226 +#: ../../backend.rst:233 msgid "OpenAPI schema" msgstr "Схема OpenAPI" -#: ../../backend.rst:228 +#: ../../backend.rst:235 msgid "Request on ``GET /{API_URL}/endpoint/`` returns Swagger UI." msgstr "Запрос на ``GET /{API_URL}/endpoint/`` возвращает Swagger UI." -#: ../../backend.rst:230 +#: ../../backend.rst:237 msgid "" "Request on ``GET /{API_URL}/endpoint/?format=openapi`` returns OpenAPI " "schema in json format. Also you can specify required version of schema " @@ -3528,7 +3702,7 @@ msgstr "" "OpenAPI в формате json. Также вы можете указать нужную версию схемы, " "используя query-параметр ``version`` " -#: ../../backend.rst:233 +#: ../../backend.rst:240 msgid "" "To change the schema after generating and before sending to user use " "hooks. Define one or more function, each taking 2 named arguments:" @@ -3537,15 +3711,15 @@ msgstr "" "используйте хуки. Напишите одну или несколько функций, каждая из которых " "принимает 2 именованных аргумента:" -#: ../../backend.rst:236 +#: ../../backend.rst:243 msgid "``request`` - user request object." msgstr "``request`` - объект запроса пользователя." -#: ../../backend.rst:237 +#: ../../backend.rst:244 msgid "``schema`` - ordered dict with OpenAPI schema." msgstr "``schema`` - ordered dict, содержащий схему OpenAPI." -#: ../../backend.rst:240 +#: ../../backend.rst:247 msgid "" "Sometimes hooks may raise an exception; in order to keep a chain of data " "modification, such exceptions are handled. The changes made to the schema" @@ -3555,11 +3729,11 @@ msgstr "" "модификации данных, такие исключения обрабатываются. Изменения, сделанные" " в схеме перед выбросом исключения, в любом случае сохраняются." -#: ../../backend.rst:249 +#: ../../backend.rst:256 msgid "Example hook:" msgstr "Пример хука:" -#: ../../backend.rst:251 +#: ../../backend.rst:258 msgid "" "To connect hook(s) to your app add function import name to the " "``OPENAPI_HOOKS`` list in ``settings.py``" @@ -3567,11 +3741,11 @@ msgstr "" "Чтобы присоединить хук(-и) к вашему приложению, добавьте строку импорта " "вашей функции в список ``OPENAPI_HOOKS`` в ``settings.py``" -#: ../../backend.rst:261 +#: ../../backend.rst:268 msgid "Testing Framework" msgstr "Фреймворк для тестирования" -#: ../../backend.rst:263 +#: ../../backend.rst:270 msgid "" "VST Utils Framework includes a helper in base test case class and " "improves support for making API requests. That means if you want make " @@ -3583,11 +3757,11 @@ msgstr "" " что для отправления bulk-запроса на endpoint нет необходимости создавать" " и инициализировать test client, а можно сразу делать запрос." -#: ../../backend.rst:274 +#: ../../backend.rst:281 msgid "Creating test case" msgstr "Создание тест-кейса" -#: ../../backend.rst:275 +#: ../../backend.rst:282 msgid "" "``test.py`` module contains test case classes based on " ":class:`vstutils.tests.BaseTestCase`. At the moment, we officially " @@ -3601,11 +3775,11 @@ msgstr "" "оберток запросов с проверкой выполнения и runtime-оптимизацией " "bulk-запросов с ручной проверкой значений." -#: ../../backend.rst:282 +#: ../../backend.rst:289 msgid "Simple example with classic tests" msgstr "Простой пример с классическими тестами" -#: ../../backend.rst:284 +#: ../../backend.rst:291 msgid "" "For example, if you have api endpoint like ``/api/v1/project/`` and model" " Project you can write test case like this:" @@ -3613,7 +3787,7 @@ msgstr "" "Например, если у вас endpoint вида ``/api/v1/project/`` и модель Project," " вы можете написать такой тест:" -#: ../../backend.rst:319 +#: ../../backend.rst:326 msgid "" "This example demonstrates functionality of default test case class. " "Default projects are initialized for the fastest and most efficient " @@ -3627,11 +3801,11 @@ msgstr "" " в разные классы. В данном примере показан классический подход к " "тестированию, однако вы можете использовать bulk-запросы в ваших тестах." -#: ../../backend.rst:327 +#: ../../backend.rst:334 msgid "Bulk requests in tests" msgstr "Bulk-запросы в тестах" -#: ../../backend.rst:329 +#: ../../backend.rst:336 msgid "" "Bulk query system is well suited for testing and executing valid queries." " Previous example could be rewritten as follows:" @@ -3639,7 +3813,7 @@ msgstr "" "Система bulk-запросов хорошо подходит для тестирования и запуска валидных" " запросов. Предыдущий пример может быть переписан так:" -#: ../../backend.rst:379 +#: ../../backend.rst:386 msgid "" "In this case, you have more code, but your tests are closer to GUI " "workflow, because vstutils-projects uses ``/api/endpoint/`` for requests." @@ -3654,7 +3828,7 @@ msgstr "" " котором используется bulk меньше по сравнению с тестом, использующим " "стандартный механизм." -#: ../../backend.rst:386 +#: ../../backend.rst:393 msgid "Test case API" msgstr "API тест-кейса" @@ -4026,11 +4200,11 @@ msgstr "" msgid "new user object for execution." msgstr "новый объект пользователя, от которого будет выполнение." -#: ../../backend.rst:393 +#: ../../backend.rst:400 msgid "Utils" msgstr "Утилиты" -#: ../../backend.rst:395 +#: ../../backend.rst:402 msgid "" "This is tested set of development utilities. Utilities include a " "collection of code that will be useful in one way or another for " @@ -4682,13 +4856,3 @@ msgstr "" "Использует функцию :func:`django.utils.translation.get_language` для " "получения кода языка и пытается получить перевод из списка доступных." -#~ msgid "" -#~ ":py:data:`typing.Union`\\[:py:class:`dict`, " -#~ ":py:class:`collections.OrderedDict`]" -#~ msgstr "" -#~ ":py:data:`typing.Union`\\[:py:class:`dict`, " -#~ ":py:class:`collections.OrderedDict`]" - -#~ msgid ":py:class:`rest_framework.serializers.BaseSerializer`" -#~ msgstr ":py:class:`rest_framework.serializers.BaseSerializer`" - diff --git a/doc/locale/ru/LC_MESSAGES/config.po b/doc/locale/ru/LC_MESSAGES/config.po index 0add414d..83667a25 100644 --- a/doc/locale/ru/LC_MESSAGES/config.po +++ b/doc/locale/ru/LC_MESSAGES/config.po @@ -7,14 +7,14 @@ msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-12-14 02:43+0000\n" +"POT-Creation-Date: 2024-01-11 03:28+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.13.1\n" +"Generated-By: Babel 2.14.0\n" #: ../../config.rst:2 msgid "Configuration manual" @@ -46,17 +46,16 @@ msgid "" "structure. To build a distributed scalable system you only need to " "connect to a shared database_, shared cache_, locks_ and a shared rpc_ " "service (MQ such as RabbitMQ, Redis, Tarantool, etc.). A shared file " -"storage may be required in some cases, but vstutils does not require " -"it." +"storage may be required in some cases, but vstutils does not require it." msgstr "" "Самое важное, о чем нужно помнить при планировании архитектуры вашего " "приложения, это то, что приложения, основанные на vstutils, имеют " "сервисно-ориентированную структуру. Чтобы построить распределенную " "масштабируемую систему, вам нужно только подключиться к :ref:`общей базе " "данных `, :ref:`общему кэшу `, :ref:`блокировкам " -"` и общей :ref:`службе rpc ` (MQ, такому как RabbitMQ, Redis, Tarantool " -"и т. д.). В некоторых случаях может потребоваться общее файловое " -"хранилище, но vstutils не требует его." +"` и общей :ref:`службе rpc ` (MQ, такому как RabbitMQ, Redis," +" Tarantool и т. д.). В некоторых случаях может потребоваться общее " +"файловое хранилище, но vstutils не требует его." #: ../../config.rst:17 msgid "Let's cover the main sections of the config and its parameters:" @@ -158,8 +157,12 @@ msgstr "" "умолчанию: ``0``." #: ../../config.rst:61 -msgid "**ldap-server** - LDAP server connection." -msgstr "**ldap-server** - Подключение к серверу LDAP." +msgid "" +"**ldap-server** - LDAP server connection. For example: " +"``ldap://your_ldap_server:389``" +msgstr "" +"**ldap-server** - Подключение к серверу LDAP.К примеру: " +"``ldap://your_ldap_server:389``" #: ../../config.rst:62 msgid "**ldap-default-domain** - Default domain for auth." @@ -178,10 +181,19 @@ msgid "**timezone** - Timezone for web-application. Default: UTC." msgstr "**timezone** - часовой пояс для веб-приложения. По умолчанию: UTC." #: ../../config.rst:65 -msgid "**log_level** - Logging level. Default: WARNING." -msgstr "**log_level** - Уровень логгирования. По умолчанию WARNING." +msgid "" +"**log_level** - Logging level. The verbosity level, configurable in " +"Django and Celery, dictates the extent of log information, with higher " +"levels providing detailed debugging insights for development and lower " +"levels streamlining logs for production environments. Default: WARNING." +msgstr "" +"**log_level** - Уровень отображения логов. Этот уровень настраивается для" +" Django и Celery, определяет объем информации в журнале. С более высокими" +" уровнями предоставляет детальную информацию для целей отладки во время " +"разработки и более низкие уровни выдают необходимую информацию для " +"рабочих окружений. По умолчанию уровень: WARNING." -#: ../../config.rst:66 +#: ../../config.rst:68 msgid "" "**enable_django_logs** - Enable or disable Django logger to output. " "Useful for debugging. Default: false." @@ -189,7 +201,7 @@ msgstr "" "**enable_django_logs** - Включить или выключить вывод логов Django. " "Полезно для отладки. По умолчанию: false." -#: ../../config.rst:68 +#: ../../config.rst:70 msgid "" "**enable_admin_panel** - Enable or disable Django Admin panel. Default: " "false." @@ -197,7 +209,7 @@ msgstr "" "**enable_admin_panel** - Включить или выключить панели администратора " "Django. По умолчанию: false." -#: ../../config.rst:69 +#: ../../config.rst:71 msgid "" "**enable_registration** - Enable or disable user self-registration. " "Default: false." @@ -205,7 +217,7 @@ msgstr "" "**enable_registration** - Включить или выключить самостоятельную " "регистрацию пользователей. По умолчанию: false." -#: ../../config.rst:70 +#: ../../config.rst:72 msgid "" "**enable_user_self_remove** - Enable or disable user self-removing. " "Default: false." @@ -213,7 +225,7 @@ msgstr "" "**enable_user_self_remove** - Включить или выключить самоудаление " "пользователей. По умолчанию: false." -#: ../../config.rst:71 +#: ../../config.rst:73 msgid "" "**auth-plugins** - Comma separated list of django authentication " "backends. Authorization attempt is made until the first successful one in" @@ -223,7 +235,7 @@ msgstr "" "разделенных запятыми. Попытка авторизации повторяется до первой успешной " "в соответствии с порядком, указанным в списке." -#: ../../config.rst:73 +#: ../../config.rst:75 msgid "" "**auth-cache-user** - Enable or disable user instance caching. It " "increases session performance on each request but saves model instance in" @@ -241,15 +253,15 @@ msgstr "" "информацию можно найти в документации " ":class:`vstutils.utils.SecurePickling`. По умолчанию: false." -#: ../../config.rst:83 +#: ../../config.rst:85 msgid "Databases settings" msgstr "Настройки базы данных" -#: ../../config.rst:85 +#: ../../config.rst:87 msgid "Section ``[databases]``." msgstr "Раздел ``[databases]``." -#: ../../config.rst:87 +#: ../../config.rst:89 msgid "" "The main section that is designed to manage multiple databases connected " "to the project." @@ -257,7 +269,7 @@ msgstr "" "Основной раздел, предназначенный для управления несколькими базами " "данных, которые подключены к проекту. " -#: ../../config.rst:90 +#: ../../config.rst:92 msgid "" "These settings are for all databases and are vendor-independent, with the" " exception of tablespace management." @@ -265,20 +277,31 @@ msgstr "" "Эти настройки актуальны для всех баз данных за исключением пространства " "таблиц." -#: ../../config.rst:93 +#: ../../config.rst:95 msgid "" "**default_tablespace** - Default tablespace to use for models that don’t " -"specify one, if the backend supports it. Read more at " -":django_topics:`Declaring tablespaces for tables `." -msgstr "" -"**default_tablespace** - Табличное пространство по умолчанию, " -"используемое для индексов полей, в которых оно не указано, если бэкенд " -"это поддерживает. Дополнительную информацию можно найти в разделе " -":django_topics:`Объявление табличных пространств для индексов " -"`." - -#: ../../config.rst:96 +"specify one, if the backend supports it. A tablespace is a storage " +"location on a database server where the physical data files corresponding" +" to database tables are stored. It allows you to organize and manage the " +"storage of your database tables, specifying the location on disk where " +"the table data is stored. Configuring tablespaces can be beneficial for " +"various reasons, such as optimizing performance by placing specific " +"tables or indexes (with ``default_index_tablespace``) on faster storage " +"devices, managing disk space efficiently, or segregating data for " +"administrative purposes. It provides a level of control over the physical" +" organization of data within the database, allowing developers to tailor " +"storage strategies based on the requirements and characteristics of their" +" application. Read more at :django_topics:`Declaring tablespaces for " +"tables `." +msgstr "" +"**default_tablespace** - Табличное пространство по умолчанию, которое " +"используется когда соответствующий бэкенд это поддерживает. Табличное " +"пространство — это место в файловой системе на сервере базы данных, где " +"хранятся физические файлы данных, соответствующие таблицам базы данных. " +"Это позволяет вам организовывать и управлять хранением таблиц вашей базы " +"данных, указывая место на диске, где будут располагаться данные таблицы." + +#: ../../config.rst:104 msgid "" "**default_index_tablespace** - Default tablespace to use for indexes on " "fields that don’t specify one, if the backend supports it. Read more at " @@ -291,7 +314,7 @@ msgstr "" ":django_topics:`Объявление табличных пространств для индексов " "`.\"" -#: ../../config.rst:99 +#: ../../config.rst:107 msgid "" "**databases_without_cte_support** - A comma-separated list of database " "section names that do not support CTEs (Common Table Expressions)." @@ -299,7 +322,7 @@ msgstr "" "**databases_without_cte_support** - Разделенный запятыми список разделов " "баз данных которые не поддерживают CTEs (Common Table Expressions)." -#: ../../config.rst:103 +#: ../../config.rst:111 msgid "" "Although MariaDB supports Common Table Expressions, but database " "connected to MariaDB still needs to be added to " @@ -314,7 +337,7 @@ msgstr "" "стандартной форме. MySQL (начиная с версии 8.0) работает ожидаемым " "образом." -#: ../../config.rst:108 +#: ../../config.rst:116 msgid "" "Also, all subsections of this section are available connections to the " "DBMS. So the ``databases.default`` section will be used by django as the " @@ -324,7 +347,7 @@ msgstr "" "подключения к СУБД. Таким образом, раздел ``databases.default`` будет " "использоваться Django в качестве подключения по умолчанию." -#: ../../config.rst:111 +#: ../../config.rst:119 msgid "" "Here you can change settings related to database, which vstutils-based " "application will use. vstutils-based application supports all databases " @@ -345,7 +368,7 @@ msgstr "" "vstutils, на нескольких узлах (кластере) используйте клиент-серверную " "базу данных (SQLite не подходит), используемую всеми узлами." -#: ../../config.rst:119 +#: ../../config.rst:127 msgid "" "You can also set the base template for connecting to the database in the " "``database`` section." @@ -353,11 +376,11 @@ msgstr "" "Вы также можете задать базовый шаблон для подключения к базе данныхв " "разделе ``database``." -#: ../../config.rst:123 +#: ../../config.rst:131 msgid "Section ``[database]``." msgstr "Раздел ``[database]``." -#: ../../config.rst:125 +#: ../../config.rst:133 msgid "" "This section is designed to define the basic template for connections to " "various databases. This can be useful to reduce the list of settings in " @@ -373,11 +396,11 @@ msgstr "" "сведения можно найти в документации Django :django_topics:`о " "множественных базах данных `" -#: ../../config.rst:130 +#: ../../config.rst:138 msgid "There is a list of settings, required for MySQL/MariaDB database." msgstr "Здесь приведен список настроек, необходимых для базы данных MySQL/MariaDB" -#: ../../config.rst:132 +#: ../../config.rst:140 msgid "" "Firstly, if you use MySQL/MariaDB and you have set timezone different " "from \"UTC\" you should run command below:" @@ -385,7 +408,7 @@ msgstr "" "Во-первых, если вы используете MySQL/MariaDB и установили часовой пояс, " "отличный от \"UTC\", вам следует выполнить следующую команду:" -#: ../../config.rst:139 +#: ../../config.rst:147 msgid "" "Secondly, to use MySQL/MariaDB set following options in ``settings.ini`` " "file:" @@ -393,19 +416,19 @@ msgstr "" "Во-вторых, чтобы использовать MySQL/MariaDB установите следующие " "настройки в файле ``settings.ini``:" -#: ../../config.rst:147 +#: ../../config.rst:155 msgid "Finally, add some options to MySQL/MariaDB configuration:" msgstr "Наконец, добавьте некоторые настройки в конфигурацию MySQL/MariaDB" -#: ../../config.rst:163 +#: ../../config.rst:171 msgid "Cache settings" msgstr "Настройки кэша" -#: ../../config.rst:165 +#: ../../config.rst:173 msgid "Section ``[cache]``." msgstr "Раздел ``[cache]``." -#: ../../config.rst:167 +#: ../../config.rst:175 msgid "" "This section is cache backend related settings used by vstutils-based " "application. vstutils supports all cache backends that Django does. " @@ -427,94 +450,93 @@ msgstr "" "клиент-серверного кэша. Мы рекомендуем использовать Redis в " "производственных окружениях." -#: ../../config.rst:177 +#: ../../config.rst:185 msgid "Tarantool Cache Backend for Django" -msgstr "" -"Tarantool в качестве сервера кеша для Django" +msgstr "Tarantool в качестве сервера кеша для Django" -#: ../../config.rst:179 +#: ../../config.rst:187 msgid "" "The ``TarantoolCache`` is a custom cache backend for Django that allows " "you to use Tarantool as a caching mechanism. To use this backend, you " "need to configure the following settings in your project's configuration:" msgstr "" -"``TarantoolCache`` это уникальный бэкенд для кеша Django, который позволяет " -"использовать Tarantool как сервер-хранилище кешей. Чтобы использовать этот механизм " -"необходимо выставить следующие настройки в конфигурации проекта:" +"``TarantoolCache`` это уникальный бэкенд для кеша Django, который " +"позволяет использовать Tarantool как сервер-хранилище кешей. Чтобы " +"использовать этот механизм необходимо выставить следующие настройки в " +"конфигурации проекта:" -#: ../../config.rst:193 +#: ../../config.rst:201 msgid "Explanation of Settings:" msgstr "Расшифровка настроек" -#: ../../config.rst:195 +#: ../../config.rst:203 msgid "" "**location** - The host name and port for connecting to the Tarantool " "server." -msgstr "" -"**location** - Имя хоста и порт для подключения к серверу Tarantool." +msgstr "**location** - Имя хоста и порт для подключения к серверу Tarantool." -#: ../../config.rst:196 +#: ../../config.rst:204 msgid "**backend** - The path to the TarantoolCache backend class." -msgstr "" -"**backend** - Путь до класса TarantoolCache бэкенда." +msgstr "**backend** - Путь до класса TarantoolCache бэкенда." -#: ../../config.rst:197 +#: ../../config.rst:205 msgid "" "**space** - The name of the space in Tarantool to use as the cache " "(default is ``DJANGO_CACHE``)." msgstr "" -"**space** - Имя спейса в Tarantool для использования кешем " -"(по умолчанию ``DJANGO_CACHE``)." +"**space** - Имя спейса в Tarantool для использования кешем (по умолчанию " +"``DJANGO_CACHE``)." -#: ../../config.rst:198 +#: ../../config.rst:206 msgid "" "**user** - The username for connecting to the Tarantool server (default " "is ``guest``)." msgstr "" -"**user** - Имя пользователя для подключения к Tarantool-серверу. (По умолчанию:" -" ``guest``)." +"**user** - Имя пользователя для подключения к Tarantool-серверу. (По " +"умолчанию: ``guest``)." -#: ../../config.rst:199 +#: ../../config.rst:207 msgid "" "**password** - The password for connecting to the Tarantool server. " "Optional." msgstr "" -"**password** - Пароль для подключения к серверу Tarantool. " -"Не обязательный." +"**password** - Пароль для подключения к серверу Tarantool. Не " +"обязательный." -#: ../../config.rst:201 +#: ../../config.rst:209 msgid "" "Additionally, you can set the ``connect_on_start`` variable in the " "``[cache.options]`` section. When set to ``true`` value, this variable " "triggers an initial connection to the Tarantool server to configure " "spaces and set up the service for automatic removal of outdated entries." msgstr "" -"Можно дополнительно указать переменную ``connect_on_start`` в " -"секции ``[cache.options]``. Когда задано значение ``true``, это указывает на " +"Можно дополнительно указать переменную ``connect_on_start`` в секции " +"``[cache.options]``. Когда задано значение ``true``, это указывает на " "вызов дополнительной инициализации на Tarantool сервере для настройки " -"спейсов и настройки сервиса для автоматического удаления просроченных объектов." +"спейсов и настройки сервиса для автоматического удаления просроченных " +"объектов." -#: ../../config.rst:206 +#: ../../config.rst:214 msgid "" "Note that this requires the ``expirationd`` module to be installed on the" " Tarantool server." msgstr "" -"Обратите внимание, что это требует установки модуля ``expirationd`` на" -" сервере Tarantool." +"Обратите внимание, что это требует установки модуля ``expirationd`` на " +"сервере Tarantool." -#: ../../config.rst:209 +#: ../../config.rst:217 msgid "" "When utilizing Tarantool as a cache backend in VST Utils, temporary " "spaces are automatically created to facilitate seamless operation. These " "temporary spaces are dynamically generated as needed and are essential " "for storing temporary data efficiently." msgstr "" -"Когда используется Tarantool в качестве кеша то, " -"автоматически создаются временные (в памяти) спейсы для операций с кешем. " -"Эти временные спейсы динамически создаются по необходимости и нужны, чтобы " -"хранить временные данные максимально эффективно." +"Когда используется Tarantool в качестве кеша то, автоматически создаются " +"временные (в памяти) спейсы для операций с кешем. Эти временные спейсы " +"динамически создаются по необходимости и нужны, чтобы хранить временные " +"данные максимально эффективно." -#: ../../config.rst:212 ../../config.rst:301 +#: ../../config.rst:220 ../../config.rst:320 msgid "" "It's important to mention that while temporary spaces are automatically " "handled, if you intend to use persistent spaces on disk, it is necessary " @@ -524,14 +546,15 @@ msgid "" "Tarantool server with the same schema configurations for consistent and " "reliable operation." msgstr "" -"Это важно, что эти спейсы автоматически создаются в памяти, и если необходимо " -"сделать, чтобы эти спейсы хранили данные на дисках, то необходимо " -"заранее создать их на серевере Tarantool с такой же схемой и аналогичными " -"настройками, которые используются в VST Utils. Убедитесь, что вам необходимо " -"использовать устойчивые хранилища в вашем приложении, прежде чем создавать их " -"на сервере Tarantool с аналогичными названиями. Это важно для стабильной и устойчивой работы." +"Это важно, что эти спейсы автоматически создаются в памяти, и если " +"необходимо сделать, чтобы эти спейсы хранили данные на дисках, то " +"необходимо заранее создать их на серевере Tarantool с такой же схемой и " +"аналогичными настройками, которые используются в VST Utils. Убедитесь, " +"что вам необходимо использовать устойчивые хранилища в вашем приложении, " +"прежде чем создавать их на сервере Tarantool с аналогичными названиями. " +"Это важно для стабильной и устойчивой работы." -#: ../../config.rst:218 +#: ../../config.rst:226 msgid "" "It's important to note that this cache driver is unique to vstutils and " "tailored to seamlessly integrate with the VST Utils framework." @@ -539,15 +562,15 @@ msgstr "" "Важно отметить, что этот драйвер кеша уникальный для vstutils и " "адаптирован для полной интеграции с фреймворком VST Utils." -#: ../../config.rst:225 +#: ../../config.rst:233 msgid "Locks settings" msgstr "Настройки блокировок" -#: ../../config.rst:227 +#: ../../config.rst:235 msgid "Section ``[locks]``." msgstr "Раздел ``[locks]``." -#: ../../config.rst:229 +#: ../../config.rst:237 msgid "" "Locks is a system that vstutils-based application uses to avoid damage " "from parallel actions working on the same entity simultaneously. It is " @@ -556,9 +579,10 @@ msgid "" "cache backend is used for locking must provide some guarantees, which do " "not required to usual cache: it MUST be shared for all vstutils-based " "application threads and nodes. So, for example, in-memory backend is not " -"suitable. In case of clusterization we strongly recommend to use Redis or" -" Memcached as backend for that purpose. Cache and locks backend can be " -"the same, but don't forget about requirement we said above." +"suitable. In case of clusterization we strongly recommend to use " +"Tarantool, Redis or Memcached as backend because they have enough speed " +"for this purposes. Cache and locks backend can be the same, but don't " +"forget about requirement we said above." msgstr "" "Блокировки - это система, которую приложение, основанное на vstutils, " "использует для предотвращения повреждения от параллельных действий, " @@ -569,19 +593,20 @@ msgstr "" "некоторые гарантии, которые не требуются для обычного кэша: он ДОЛЖЕН " "быть общим для всех потоков и узлов приложения, основанного на vstutils. " "Например, бэкенд in-memory не подходит. В случае кластеризации " -"настоятельно рекомендуется использовать Redis или Memcached в качестве " -"бэкэнда для этой цели. Бэкэнд кэша и блокировок могут быть одним и тем " -"же, но не забывайте о требованиях, о которых мы говорили выше." +"настоятельно рекомендуется использовать Tarantool, Redis или Memcached в " +"качестве бэкэнда, потому что они имеют достаточную производительность для" +" этих целей. Бэкэнд кэша и блокировок могут быть одним и тем же, но не " +"забывайте о требованиях, о которых мы говорили выше." -#: ../../config.rst:242 +#: ../../config.rst:250 msgid "Session cache settings" msgstr "Настройки кэша сессий" -#: ../../config.rst:244 +#: ../../config.rst:252 msgid "Section ``[session]``." msgstr "Раздел ``[session]``." -#: ../../config.rst:246 +#: ../../config.rst:254 msgid "" "vstutils-based application store sessions in database_, but for better " "performance, we use a cache-based session backend. It is based on Django " @@ -594,38 +619,131 @@ msgstr "" "группа настроек, аналогичных настройкам cache_. По умолчанию настройки " "получаются из cache_." -#: ../../config.rst:255 +#: ../../config.rst:263 msgid "Rpc settings" msgstr "Настройки RPC" -#: ../../config.rst:257 +#: ../../config.rst:265 msgid "Section ``[rpc]``." msgstr "Раздел ``[rpc]``." -#: ../../config.rst:259 +#: ../../config.rst:267 +msgid "" +"Celery is a distributed task queue system for handling asynchronous tasks" +" in web applications. Its primary role is to facilitate the execution of " +"background or time-consuming tasks independently from the main " +"application logic. Celery is particularly useful for offloading tasks " +"that don't need to be processed immediately, improving the overall " +"responsiveness and performance of an application." +msgstr "" +"Celery — это распределенная система очередей задач для обработки " +"асинхронных задач в веб-приложениях. Его основная роль — облегчить " +"выполнение фоновых или трудоемких задач независимо от основной логики " +"приложения. Celery особенно полезен для разгрузки задач, которые не " +"требуют немедленной обработки, что повышает общую скорость отклика и " +"производительность приложения." + +#: ../../config.rst:271 +msgid "" +"Key features and roles of Celery in an application with asynchronous " +"tasks include:" +msgstr "" +"Ключевые функции и роли Celery в приложении с асинхронными задачами " +"включают:" + +#: ../../config.rst:273 +msgid "" +"Asynchronous Task Execution: Celery allows developers to define tasks as " +"functions or methods and execute them asynchronously. This is beneficial " +"for tasks that might take a considerable amount of time, such as sending " +"emails, processing data, or generating reports." +msgstr "" +"Асинхронное выполнение задач: Celery позволяет разработчикам определять " +"задачи как функции или методы и выполнять их асинхронно. Это полезно для " +"задач, которые могут занять значительное время, таких как отправка " +"электронных писем, обработка данных или создание отчетов." + +#: ../../config.rst:274 +msgid "" +"Distributed Architecture: Celery operates in a distributed manner, making" +" it suitable for large-scale applications. It can distribute tasks across" +" multiple worker processes or even multiple servers, enhancing " +"scalability and performance." +msgstr "" +"Распределенная архитектура: Celery работает распределенным образом, что " +"делает его подходящим для крупномасштабных приложений. Он может " +"распределять задачи между несколькими рабочими процессами или даже " +"несколькими серверами, повышая масштабируемость и производительность." + +#: ../../config.rst:275 +msgid "" +"Message Queue Integration: Celery relies on message brokers (such as " +"RabbitMQ, Redis, Tarantool, SQS or others) to manage the communication " +"between the main application and the worker processes. This decoupling " +"ensures reliable task execution and allows for the efficient handling of " +"task queues." +msgstr "" +"Интеграция очереди сообщений: Celery полагается на брокеров сообщений " +"(таких как RabbitMQ, Redis, Tarantool, SQS или другие) для управления " +"связью между основным приложением и рабочими процессами. Такое разделение" +" обеспечивает надежное выполнение задач и позволяет эффективно " +"обрабатывать очереди задач." + +#: ../../config.rst:276 +msgid "" +"Periodic Tasks: Celery includes a scheduler that enables the execution of" +" periodic or recurring tasks. This is useful for automating tasks that " +"need to run at specific intervals, like updating data or performing " +"maintenance operations." +msgstr "" +"Периодические задачи: Celery включает в себя планировщик, который " +"позволяет выполнять периодические или повторяющиеся задачи. Это полезно " +"для автоматизации задач, которые необходимо выполнять через определенные " +"промежутки времени, например обновления данных или выполнения операций " +"обслуживания." + +#: ../../config.rst:277 +msgid "" +"Error Handling and Retry Mechanism: Celery provides mechanisms for " +"handling errors in tasks and supports automatic retries. This ensures " +"robustness in task execution, allowing the system to recover from " +"transient failures." +msgstr "" +"Обработка ошибок и механизм повторных попыток: Celery предоставляет " +"механизмы для обработки ошибок в задачах и поддерживает автоматические " +"повторные попытки. Это обеспечивает надежность выполнения задач, позволяя" +" системе восстанавливаться после временных сбоев." + +#: ../../config.rst:278 +msgid "" +"Task Result Storage: Celery supports storing the results of completed " +"tasks, which can be useful for tracking task progress or retrieving " +"results later. This feature is especially valuable for long-running " +"tasks." +msgstr "" +"Хранение результатов задач: Celery поддерживает хранение результатов " +"выполненных задач, что может быть полезно для отслеживания хода " +"выполнения задач или получения результатов позже. Эта функция особенно " +"ценна для длительных задач." + +#: ../../config.rst:280 msgid "" "vstutils-based application uses Celery for long-running async tasks. " -"Celery is based on message queue concept, so between web-service and " -"workers running under Celery must be some kind of message broker " -"(RabbitMQ or something). Those settings relate to this broker and Celery" -" itself. Those kinds of settings: broker backend, number of worker-" -"processes per node and some settings used for troubleshoot server-broker-" -"worker interaction problems." +"Those settings relate to this broker and Celery itself. Those kinds of " +"settings: broker backend, number of worker-processes per node and some " +"settings used for troubleshoot server-broker-worker interaction problems." msgstr "" "Приложение, основанное на vstutils, использует Celery для выполнения " -"длительных асинхронных задач. Celery основан на концепции очереди " -"сообщений, поэтому между веб-сервисом и рабочими процессами, работающими " -"под управлением Celery, должен быть некий брокер сообщений (например, " -"RabbitMQ). Эти настройки относятся к этому брокеру и самому Celery. В них" -" указывается бэкэнд брокера, количество рабочих процессов на узел и " -"некоторые настройки, используемые для устранения проблем взаимодействия " -"сервера, брокера и рабочих процессов." +"длительных асинхронных задач. Эти настройки относятся к этому брокеру и " +"самому Celery. В них указывается бэкэнд брокера, количество рабочих " +"процессов на узел и некоторые настройки, используемые для устранения " +"проблем взаимодействия сервера, брокера и рабочих процессов." -#: ../../config.rst:267 +#: ../../config.rst:286 msgid "This section require vstutils with `rpc` extra dependency." msgstr "Для этого раздела требуется vstutils с дополнительной зависимостью rpc." -#: ../../config.rst:269 +#: ../../config.rst:288 msgid "" "**connection** - Celery :celery_docs:`broker connection " "`. Default: " @@ -635,13 +753,13 @@ msgstr "" "`. По умолчанию: " "``filesystem:///var/tmp``." -#: ../../config.rst:270 +#: ../../config.rst:289 msgid "**concurrency** - Count of celery worker threads. Default: 4." msgstr "" "**concurrency** - Количество потоков рабочего процесса Celery. По " "умолчанию: 4." -#: ../../config.rst:271 +#: ../../config.rst:290 msgid "" "**heartbeat** - Interval between sending heartbeat packages, which says " "that connection still alive. Default: 10." @@ -649,7 +767,7 @@ msgstr "" "**heartbeat** - Интервал между отправкой пакетов-сигналов, которые " "говорят, что соединение все еще активно. По умолчанию: 10." -#: ../../config.rst:272 +#: ../../config.rst:291 msgid "" "**enable_worker** - Enable or disable worker with webserver. Default: " "true." @@ -657,7 +775,7 @@ msgstr "" "**enable_worker** - Включить или отключить рабочий процесс с " "веб-сервером. По умолчанию: true." -#: ../../config.rst:274 +#: ../../config.rst:293 msgid "" "The following variables from :celery_docs:`Django settings " "` are also supported" @@ -667,54 +785,56 @@ msgstr "" "Django ` (с " "соответствующими типами):" -#: ../../config.rst:277 +#: ../../config.rst:296 msgid "" "**prefetch_multiplier** - :celery_docs:`CELERYD_PREFETCH_MULTIPLIER " "`" msgstr "" -#: ../../config.rst:278 +#: ../../config.rst:297 msgid "" "**max_tasks_per_child** - :celery_docs:`CELERYD_MAX_TASKS_PER_CHILD " "`" msgstr "" -#: ../../config.rst:279 +#: ../../config.rst:298 msgid "" "**results_expiry_days** - :celery_docs:`CELERY_RESULT_EXPIRES " "`" msgstr "" -#: ../../config.rst:280 +#: ../../config.rst:299 msgid "" "**default_delivery_mode** - :celery_docs:`CELERY_DEFAULT_DELIVERY_MODE " "`" msgstr "" -#: ../../config.rst:281 +#: ../../config.rst:300 msgid "" "**task_send_sent_event** - :celery_docs:`CELERY_DEFAULT_DELIVERY_MODE " "`" msgstr "" -#: ../../config.rst:282 +#: ../../config.rst:301 msgid "" "**worker_send_task_events** - :celery_docs:`CELERY_DEFAULT_DELIVERY_MODE " "`" msgstr "" -#: ../../config.rst:284 +#: ../../config.rst:303 msgid "" "VST Utils provides seamless support for using Tarantool as a transport " "for Celery, allowing efficient and reliable message passing between " "distributed components. To enable this feature, ensure that the Tarantool" " server has the `queue` and `expirationd` modules installed." msgstr "" -"VST Utils так же предоставляет поддержку для использования Tarantool сервера как транспорта " -"в Celery, обеспечивая эффективную и надежную передачу сообщений между распределёнными компонентами. " -"Для использования этой возможности на сервере Tarantool должны быть установлены модули `queue` и `expirationd`." +"VST Utils так же предоставляет поддержку для использования Tarantool " +"сервера как транспорта в Celery, обеспечивая эффективную и надежную " +"передачу сообщений между распределёнными компонентами. Для использования " +"этой возможности на сервере Tarantool должны быть установлены модули " +"`queue` и `expirationd`." -#: ../../config.rst:287 +#: ../../config.rst:306 msgid "" "To configure the connection, use the following example URL: " "``tarantool://guest@localhost:3301/rpc``" @@ -722,64 +842,66 @@ msgstr "" "Для настройки подключения используйте в качестве примера следующий URL: " "``tarantool://guest@localhost:3301/rpc``" -#: ../../config.rst:289 +#: ../../config.rst:308 msgid "``tarantool://``: Specifies the transport." msgstr "``tarantool://``: Указывает тип транспорта." -#: ../../config.rst:290 +#: ../../config.rst:309 msgid "``guest``: Authentication parameters (in this case, no password)." msgstr "`guest``: Параметры аутентификации (в данном случае без пароля)." -#: ../../config.rst:291 +#: ../../config.rst:310 msgid "``localhost``: Server address." msgstr "``localhost``: Адрес сервера." -#: ../../config.rst:292 +#: ../../config.rst:311 msgid "``3301``: Port for connection." msgstr "``3301``: Порт для подключения." -#: ../../config.rst:293 +#: ../../config.rst:312 msgid "``rpc``: Prefix for queue names and/or result storage." msgstr "``rpc``: Префикс для имён очередей и/или спейса хранилища результата" -#: ../../config.rst:295 +#: ../../config.rst:314 msgid "" "VST Utils also supports Tarantool as a backend for storing Celery task " "results. Connection string is similar to the transport." msgstr "" -"VST Utils так же поддерживает Tarantool в качестве бэкенда для хранения результатов задач Celery. " -"Строка подключения аналогична как и у транспорта." +"VST Utils так же поддерживает Tarantool в качестве бэкенда для хранения " +"результатов задач Celery. Строка подключения аналогична как и у " +"транспорта." -#: ../../config.rst:298 +#: ../../config.rst:317 msgid "" "When utilizing Tarantool as a result backend or transport in VST Utils, " "temporary spaces and queues are automatically created to facilitate " "seamless operation. These temporary spaces are dynamically generated as " "needed and are essential for storing temporary data efficiently." msgstr "" -"Когда Tarantool используется для хранения результатов или в качестве транспорта в VST Utils, " -"то автоматически создаются спейсы и очереди для этих операций. " -"Эти временные хранилища создаются динамически по мере необходимости и необходимы для эффективного хранения временных данных." +"Когда Tarantool используется для хранения результатов или в качестве " +"транспорта в VST Utils, то автоматически создаются спейсы и очереди для " +"этих операций. Эти временные хранилища создаются динамически по мере " +"необходимости и необходимы для эффективного хранения временных данных." -#: ../../config.rst:309 +#: ../../config.rst:328 msgid "Worker settings" msgstr "Настройки рабочего процесса (worker`a)" -#: ../../config.rst:311 +#: ../../config.rst:330 msgid "Section ``[worker]``." msgstr "Раздел ``[worker]``." -#: ../../config.rst:314 +#: ../../config.rst:333 msgid "These settings are needed only for rpc-enabled applications." msgstr "" "Эти настройки необходимы только для приложений с включенной поддержкой " "RPC." -#: ../../config.rst:316 +#: ../../config.rst:335 msgid "Celery worker options:" msgstr "Настройки рабочего процесса celery:" -#: ../../config.rst:318 +#: ../../config.rst:337 msgid "" "**loglevel** - Celery worker log level. Default: from main_ section " "``log_level``." @@ -787,7 +909,7 @@ msgstr "" "**loglevel** - Уровень логгирования рабочего процесса. По умолчанию: из " "раздела main_ ``log_level``." -#: ../../config.rst:319 +#: ../../config.rst:338 msgid "" "**pidfile** - Celery worker pidfile. Default: " "``/run/{app_name}_worker.pid``" @@ -795,7 +917,7 @@ msgstr "" "**pidfile** - Файл pid для рабочего процесса Celery. по умолчанию: " "``/run/{app_name}_worker.pid``\"" -#: ../../config.rst:320 +#: ../../config.rst:339 msgid "" "**autoscale** - Options for autoscaling. Two comma separated numbers: " "max,min." @@ -803,25 +925,25 @@ msgstr "" "**autoscale** - Параметры для автомасштабирования. Два числа, разделенных" " запятой: максимальное,минимальное." -#: ../../config.rst:321 +#: ../../config.rst:340 msgid "**beat** - Enable or disable celery beat scheduler. Default: ``true``." msgstr "" "**beat** - Включить или отключить планировщик celery beat. По умолчанию: " "``true``." -#: ../../config.rst:323 +#: ../../config.rst:342 msgid "See other settings via ``celery worker --help`` command." msgstr "Другие настройки можно увидеть с помощью команды ``celery worker --help``" -#: ../../config.rst:330 +#: ../../config.rst:349 msgid "SMTP settings" msgstr "SMTP-настройки" -#: ../../config.rst:332 +#: ../../config.rst:351 msgid "Section ``[mail]``." msgstr "Раздел ``[mail]``." -#: ../../config.rst:334 +#: ../../config.rst:353 msgid "" "Django comes with several email sending backends. With the exception of " "the SMTP backend (default when ``host`` is set), these backends are " @@ -832,15 +954,28 @@ msgstr "" "установке ``host``), эти бэкэнды полезны только для тестирования и " "разработки." -#: ../../config.rst:337 +#: ../../config.rst:356 msgid "" "Applications based on vstutils uses only ``smtp`` and ``console`` " -"backends." +"backends. These two backends serve distinct purposes in different " +"environments. The SMTP backend ensures the reliable delivery of emails in" +" a production setting, while the console backend provides a convenient " +"way to inspect emails during development without the risk of " +"unintentional communication with external mail servers. Developers often " +"switch between these backends based on the context of their work, " +"choosing the appropriate one for the stage of development or testing." msgstr "" "Приложения, основанные на vstutils, используют только бэкэнды ``smtp`` и " -"``console``." +"``console``. Эти два бэкэнда служат разным целям в разных средах. SMTP " +"обеспечивает надежную доставку электронной почты в производственных " +"условиях, а консольный вариант обеспечивает удобный способ проверки " +"электронной почты во время разработки без риска непреднамеренного " +"взаимодействия с внешними почтовыми серверами. Разработчики часто " +"переключаются между этими бэкэндами в зависимости от контекста своей " +"работы, выбирая тот, который подходит для этапа разработки или " +"тестирования." -#: ../../config.rst:339 +#: ../../config.rst:362 msgid "" "**host** - IP or domain for smtp-server. If it not set vstutils uses " "``console`` backends. Default: ``None``." @@ -848,17 +983,17 @@ msgstr "" "**host** - IP-адрес или доменное имя smtp-сервера. Если не указано, " "vstutils использует бэкэнд ``console``. По умолчанию: ``None``." -#: ../../config.rst:340 +#: ../../config.rst:363 msgid "**port** - Port for smtp-server connection. Default: ``25``." msgstr "**port** - Порт для подключения к smtp-серверу. По умолчанию: ``25``." -#: ../../config.rst:341 +#: ../../config.rst:364 msgid "**user** - Username for smtp-server connection. Default: ``\"\"``." msgstr "" "**user** - Имя пользователя для подключения к SMTP-серверу. По умолчанию:" " ``\"\"``." -#: ../../config.rst:342 +#: ../../config.rst:365 msgid "" "**password** - Auth password for smtp-server connection. Default: " "``\"\"``." @@ -866,7 +1001,7 @@ msgstr "" "**password** - Пароль для аутентификации на smtp-сервере. По умолчанию: " "``\"\"``." -#: ../../config.rst:343 +#: ../../config.rst:366 msgid "" "**tls** - Enable/disable tls for smtp-server connection. Default: " "``False``." @@ -874,7 +1009,7 @@ msgstr "" "**tls** - Включить или отключить TLS для подключения к smtp-серверу. По " "умолчанию: ``False``" -#: ../../config.rst:344 +#: ../../config.rst:367 msgid "" "**send_confirmation** - Enable/disable confirmation message after " "registration. Default: ``False``." @@ -882,7 +1017,7 @@ msgstr "" "**send_confirmation** - Включить или отключить отправку сообщения с " "подтверждением после регистрации. По умолчанию: ``False``." -#: ../../config.rst:345 +#: ../../config.rst:368 msgid "" "**authenticate_after_registration** - Enable/disable autologin after " "registration confirmation. Default: ``False``." @@ -891,15 +1026,15 @@ msgstr "" "автоматический вход пользователя после подтверждения регистрации. По " "умолчанию: ``False``." -#: ../../config.rst:351 +#: ../../config.rst:374 msgid "Web settings" msgstr "Web-настройки" -#: ../../config.rst:353 +#: ../../config.rst:376 msgid "Section ``[web]``." msgstr "Раздел ``[web]``." -#: ../../config.rst:355 +#: ../../config.rst:378 msgid "" "These settings are related to web-server. Those settings includes: " "session_timeout, static_files_url and pagination limit." @@ -907,13 +1042,13 @@ msgstr "" "Эти настройки относятся к веб-серверу. Среди них: session_timeout, " "static_files_url и лимит пагинации." -#: ../../config.rst:358 +#: ../../config.rst:381 msgid "**allow_cors** - enable cross-origin resource sharing. Default: ``False``." msgstr "" "**allow_cors** - включить cross-origin resource sharing. По умолчанию: " "``False``." -#: ../../config.rst:359 +#: ../../config.rst:382 msgid "" "**cors_allowed_origins**, **cors_allowed_origins_regexes**, " "**cors_expose_headers**, **cors_allow_methods**, **cors_allow_headers**, " @@ -927,7 +1062,7 @@ msgstr "" "/django-cors-headers#configuration>`_ из библиотеки ``django-cors-" "headers`` со значениями по умолчанию." -#: ../../config.rst:362 +#: ../../config.rst:385 msgid "" "**enable_gravatar** - Enable/disable gravatar service using for users. " "Default: ``True``." @@ -935,7 +1070,7 @@ msgstr "" "**enable_gravatar** - Включить/отключить использование сервиса Gravatar " "для пользователей. По умолчанию: ``True``." -#: ../../config.rst:363 +#: ../../config.rst:386 msgid "" "**rest_swagger_description** - Help string in Swagger schema. Useful for " "dev-integrations." @@ -943,7 +1078,7 @@ msgstr "" "**rest_swagger_description** - Строка справки в схеме Swagger. Полезно " "для разработки интеграций." -#: ../../config.rst:364 +#: ../../config.rst:387 msgid "" "**openapi_cache_timeout** - Cache timeout for storing schema data. " "Default: ``120``." @@ -951,7 +1086,7 @@ msgstr "" "**openapi_cache_timeout** - Время кэширования данных схемы. По умолчанию:" " ``120``." -#: ../../config.rst:365 +#: ../../config.rst:388 msgid "" "**health_throttle_rate** - Count of requests to ``/api/health/`` " "endpoint. Default: ``60``." @@ -959,7 +1094,7 @@ msgstr "" "Количество запросов к конечной точке ``/api/health/``. По умолчанию: " "``60``." -#: ../../config.rst:366 +#: ../../config.rst:389 msgid "" "**bulk_threads** - Threads count for PATCH ``/api/endpoint/`` endpoint. " "Default: ``3``." @@ -967,13 +1102,13 @@ msgstr "" "**bulk_threads** - Количество потоков для PATCH ``/api/endpoint/``. По " "умолчанию: ``3``." -#: ../../config.rst:367 +#: ../../config.rst:390 msgid "**session_timeout** - Session lifetime. Default: ``2w`` (two weeks)." msgstr "" "**session_timeout** - Время жизни сессии. По умолчанию: ``2w`` (две " "недели)." -#: ../../config.rst:368 +#: ../../config.rst:391 msgid "" "**etag_default_timeout** - Cache timeout for Etag headers to control " "models caching. Default: ``1d`` (one day)." @@ -981,7 +1116,7 @@ msgstr "" "**etag_default_timeout** - Время кэширования заголовков Etag для " "управления кэшированием моделей. По умолчанию: ``1d`` (один день)." -#: ../../config.rst:369 +#: ../../config.rst:392 msgid "" "**rest_page_limit** and **page_limit** - Default limit of objects in API " "list. Default: ``1000``." @@ -989,7 +1124,7 @@ msgstr "" "**rest_page_limit** and **page_limit** - Максимальное количество объектов" " в списке API. По умолчанию: ``1000``." -#: ../../config.rst:370 +#: ../../config.rst:393 msgid "" "**session_cookie_domain** - The domain to use for session cookies. Read " ":django_docs:`more `. " @@ -999,7 +1134,7 @@ msgstr "" ":django_docs:`Подробнее `. " "По умолчанию: ``None``." -#: ../../config.rst:372 +#: ../../config.rst:395 msgid "" "**csrf_trusted_origins** - A list of hosts which are trusted origins for " "unsafe requests. Read :django_docs:`more `. По " "умолчанию: значение из **session_cookie_domain**." -#: ../../config.rst:374 +#: ../../config.rst:397 msgid "" "**case_sensitive_api_filter** - Enables/disables case sensitive search " "for name filtering. Default: ``True``." @@ -1017,7 +1152,7 @@ msgstr "" "**case_sensitive_api_filter** - Включить/отключить чувствительность к " "регистру при фильтрации по имени. По умолчанию: ``True``." -#: ../../config.rst:376 +#: ../../config.rst:399 msgid "" "**secure_proxy_ssl_header_name** - Header name which activates SSL urls " "in responses. Read :django_docs:`more `. По умолчанию: " "``HTTP_X_FORWARDED_PROTOCOL``." -#: ../../config.rst:378 +#: ../../config.rst:401 msgid "" "**secure_proxy_ssl_header_value** - Header value which activates SSL urls" " in responses. Read :django_docs:`more `. По " "умолчанию: ``https``." -#: ../../config.rst:382 +#: ../../config.rst:405 msgid "" "The following variables from Django settings are also supported (with the" " corresponding types):" @@ -1047,70 +1182,70 @@ msgstr "" "Также поддерживаются следующие переменные из настроек Django (с " "соответствующими типами):" -#: ../../config.rst:384 +#: ../../config.rst:407 msgid "" "**secure_browser_xss_filter** - :django_docs:`SECURE_BROWSER_XSS_FILTER " "`" msgstr "" -#: ../../config.rst:385 +#: ../../config.rst:408 msgid "" "**secure_content_type_nosniff** - " ":django_docs:`SECURE_CONTENT_TYPE_NOSNIFF `" msgstr "" -#: ../../config.rst:386 +#: ../../config.rst:409 msgid "" "**secure_hsts_include_subdomains** - " ":django_docs:`SECURE_HSTS_INCLUDE_SUBDOMAINS `" msgstr "" -#: ../../config.rst:387 +#: ../../config.rst:410 msgid "" "**secure_hsts_preload** - :django_docs:`SECURE_HSTS_PRELOAD `" msgstr "" -#: ../../config.rst:388 +#: ../../config.rst:411 msgid "" "**secure_hsts_seconds** - :django_docs:`SECURE_HSTS_SECONDS `" msgstr "" -#: ../../config.rst:389 +#: ../../config.rst:412 msgid "" "**password_reset_timeout_days** - " ":django_docs:`PASSWORD_RESET_TIMEOUT_DAYS `" msgstr "" -#: ../../config.rst:390 +#: ../../config.rst:413 msgid "" "**request_max_size** - :django_docs:`DATA_UPLOAD_MAX_MEMORY_SIZE " "`" msgstr "" -#: ../../config.rst:391 +#: ../../config.rst:414 msgid "" "**x_frame_options** - :django_docs:`X_FRAME_OPTIONS `" msgstr "" -#: ../../config.rst:392 +#: ../../config.rst:415 msgid "" "**use_x_forwarded_host** - :django_docs:`USE_X_FORWARDED_HOST " "`" msgstr "" -#: ../../config.rst:393 +#: ../../config.rst:416 msgid "" "**use_x_forwarded_port** - :django_docs:`USE_X_FORWARDED_PORT " "`" msgstr "" -#: ../../config.rst:395 +#: ../../config.rst:418 msgid "" "The following settings affects prometheus metrics endpoint (which can be " "used for monitoring application):" @@ -1118,7 +1253,7 @@ msgstr "" "Следующие настройки влияют на эндпоинт метрик Prometheus (который может " "использоваться для мониторинга приложения):" -#: ../../config.rst:397 +#: ../../config.rst:420 msgid "" "**metrics_throttle_rate** - Count of requests to ``/api/metrics/`` " "endpoint. Default: ``120``." @@ -1126,7 +1261,7 @@ msgstr "" "**metrics_throttle_rate** - Количество запросов к эндпоинту " "``/api/metrics/``. По умолчанию: ``120``." -#: ../../config.rst:398 +#: ../../config.rst:421 msgid "" "**enable_metrics** - Enable/disable ``/api/metrics/`` endpoint for app. " "Default: ``true``" @@ -1134,7 +1269,7 @@ msgstr "" "**enable_metrics** - Включить/отключить эндпоинт ``/api/metrics/`` для " "приложения. По умолчанию: ``true``." -#: ../../config.rst:399 +#: ../../config.rst:422 msgid "" "**metrics_backend** - Python class path with metrics collector backend. " "Default: ``vstutils.api.metrics.DefaultBackend`` Default backend collects" @@ -1144,11 +1279,11 @@ msgstr "" " умолчанию: ``vstutils.api.metrics.DefaultBackend``. Стандартный бэкенд " "собирает метрики из рабочих процессов uwsgi и информацию о версии Python." -#: ../../config.rst:403 +#: ../../config.rst:426 msgid "Section ``[uvicorn]``." msgstr "Раздел ``[uvicorn]``." -#: ../../config.rst:405 +#: ../../config.rst:428 msgid "" "You can configure the necessary settings to run the uvicorn server. " "``vstutils`` supports almost all options from the cli, except for those " @@ -1158,58 +1293,100 @@ msgstr "" "``vstutils`` поддерживает практически все опции из командной строки, за " "исключением тех, которые настраивают приложение и соединение." -#: ../../config.rst:408 +#: ../../config.rst:431 msgid "See all available uvicorn settings via ``uvicorn --help`` command." msgstr "" "Вы можете посмотреть все доступные настройки uvicorn, введя команду " "``uvicorn --help``" -#: ../../config.rst:413 +#: ../../config.rst:436 msgid "Centrifugo client settings" msgstr "Настройки клиента Centrifugo" -#: ../../config.rst:415 +#: ../../config.rst:438 msgid "Section ``[centrifugo]``." msgstr "Раздел ``[centrifugo]``." -#: ../../config.rst:417 +#: ../../config.rst:440 +msgid "" +"Centrifugo is employed to optimize real-time data updates within a Django" +" application by orchestrating seamless communication among its various " +"components. The operational paradigm involves the orchestrated generation" +" of Django signals, specifically ``post_save`` and ``post_delete`` " +"signals, dynamically triggered during HTTP requests or the execution of " +"Celery tasks. These signals, when invoked on user or BaseModel-derived " +"models within the vstutils framework, initiate the creation of messages " +"destined for all subscribers keen on the activities related to these " +"models. Subsequent to the completion of the HTTP request or Celery task, " +"the notification mechanism dispatches tailored messages to all relevant " +"subscribers. In effect, each active browser tab with a pertinent " +"subscription promptly receives a notification, prompting an immediate " +"data update request. Centrifugo's pivotal role lies in obviating the " +"necessity for applications to engage in periodic REST API polling at " +"fixed intervals (e.g., every 5 seconds). This strategic elimination of " +"redundant requests significantly alleviates the REST API's operational " +"load, rendering it more scalable to accommodate a larger user base. " +"Importantly, this real-time communication model ensures prompt and " +"synchronized data updates, fostering a highly responsive user experience." +msgstr "" +"Centrifugo используется для оптимизации моментального обновления данных в" +" приложении Django, обеспечивая беспрепятственное взаимодействие между " +"его различными компонентами. Основной принцип работы заключается в " +"оркестрированном создании сигналов Django, а именно ``post_save`` и " +"``post_delete``, которые динамически вызываются во время HTTP-запросов " +"или выполнения задач Celery. Эти сигналы, когда они вызываются на моделях" +" пользователей или моделях, унаследованных от BaseModel в рамках " +"фреймворка vstutils, инициируют создание сообщений для всех подписчиков, " +"заинтересованных в событиях, связанных с этими моделями. После завершения" +" HTTP-запроса или задачи Celery механизм уведомлений отправляет " +"настроенные сообщения всем соответствующим подписчикам. Это означает, что" +" каждая активная вкладка браузера с соответствующей подпиской мгновенно " +"получает уведомление, стимулируя мгновенный запрос на обновление данных. " +"Отсутствие необходимости для приложений в периодическом опросе REST API в" +" фиксированные интервалы (например, каждые 5 секунд) существенно снижает " +"операционную нагрузку REST API и обеспечивает его масштабируемость для " +"более крупной базы пользователей. Важно отметить, что эту модель " +"моментального обмена данными можно рассматривать как альтернативу по " +"сравнению с периодическими запросами. Вместе с тем, она гарантирует " +"мгновенное и синхронизированное обновление данных, способствуя " +"высококачественному пользовательскому опыту." + +#: ../../config.rst:454 msgid "" "To install app with centrifugo client, ``[centrifugo]`` section must be " "set. Centrifugo is used by application to auto-update page data. When " -"user change some data, other clients get notification on " -"``subscriptions_update`` channel with model label and primary key. " -"Without the service all GUI-clients get page data every 5 seconds (by " -"default)." +"user change some data, other clients get notification on channel with " +"model label and primary key. Without the service all GUI-clients get page" +" data every 5 seconds (by default)." msgstr "" "Для установки приложения с клиентом Centrifugo должен быть задан раздел " "``[centrifugo]``. Centrifugo используется приложением для автоматического" " обновления данных на странице. Когда пользователь изменяет какие-либо " -"данные, другие клиенты получают уведомление на канале " -"``subscriptions_update`` с меткой модели и первичным ключом. Без этой " -"службы все клиенты GUI получают данные страницы каждые 5 секунд (по " -"умолчанию)." +"данные, другие клиенты получают уведомление на канале с меткой модели и " +"первичным ключом. Без этой службы все клиенты GUI получают данные " +"страницы каждые 5 секунд (по умолчанию)." -#: ../../config.rst:423 +#: ../../config.rst:460 msgid "**address** - Centrifugo server address." msgstr "**address** - Адрес сервера Centrifugo." -#: ../../config.rst:424 +#: ../../config.rst:461 msgid "**api_key** - API key for clients." msgstr "**api_key** - Ключ API для клиентов." -#: ../../config.rst:425 +#: ../../config.rst:462 msgid "**token_hmac_secret_key** - API key for jwt-token generation." msgstr "**token_hmac_secret_key** - Ключ API для генерации JWT-токена." -#: ../../config.rst:426 +#: ../../config.rst:463 msgid "**timeout** - Connection timeout." msgstr "**timeout** - Таймаут подключения." -#: ../../config.rst:427 +#: ../../config.rst:464 msgid "**verify** - Connection verification." msgstr "**verify** - Проверка подключения." -#: ../../config.rst:428 +#: ../../config.rst:465 msgid "" "**subscriptions_prefix** - Prefix used for generating update channels, by" " default \"{VST_PROJECT}.update\"." @@ -1217,7 +1394,7 @@ msgstr "" "**subscriptions_prefix** - Префикс, используемый для генерации каналов " "обновления, по умолчанию \"{VST_PROJECT}.update\"." -#: ../../config.rst:431 +#: ../../config.rst:468 msgid "" "These settings also add parameters to the OpenAPI schema and change how " "the auto-update system works in the GUI. ``token_hmac_secret_key`` is " @@ -1229,15 +1406,15 @@ msgstr "" "генерации JWT-токена (на основе времени истечения сессии). Токен будет " "использоваться для клиента Centrifugo-JS." -#: ../../config.rst:439 +#: ../../config.rst:476 msgid "Storage settings" msgstr "Настройки хранилища" -#: ../../config.rst:441 +#: ../../config.rst:478 msgid "Section ``[storages]``." msgstr "Раздел ``[storages]``." -#: ../../config.rst:443 +#: ../../config.rst:480 msgid "" "Applications based on ``vstutils`` supports filesystem storage out of " "box. Setup ``media_root`` and ``media_url`` in ``[storages.filesystem]`` " @@ -1250,7 +1427,7 @@ msgstr "" "``media_url`` в разделе [storages.filesystem]. По умолчанию они будут " "равны ``{/path/to/project/module}/media`` и ``/media/``." -#: ../../config.rst:448 +#: ../../config.rst:485 msgid "" "Applications based on ``vstutils`` supports store files in external " "services with `Apache Libcloud `_ and `Boto3" @@ -1261,7 +1438,7 @@ msgstr "" "`_ и `Boto3 " "`_." -#: ../../config.rst:451 +#: ../../config.rst:488 msgid "" "Apache Libcloud settings grouped by sections named " "``[storages.libcloud.provider]``, where ``provider`` is name of storage. " @@ -1278,7 +1455,7 @@ msgstr "" "storages.readthedocs.io/en/latest/backends/apache_libcloud.html#libcloud-" "providers>`_." -#: ../../config.rst:456 +#: ../../config.rst:493 msgid "" "This setting is required to configure connections to cloud storage " "providers. Each entry corresponds to a single ‘bucket’ of storage. You " @@ -1291,7 +1468,7 @@ msgstr "" "(например, несколько buckets S3), и вы можете определить buckets в " "нескольких провайдерах." -#: ../../config.rst:461 +#: ../../config.rst:498 msgid "" "For ``Boto3`` all settings grouped by section named ``[storages.boto3]``." " Section must contain following keys: ``access_key_id``, " @@ -1306,7 +1483,7 @@ msgstr "" "amazon-S3 `_." -#: ../../config.rst:466 +#: ../../config.rst:503 msgid "" "Storage has following priority to choose storage engine if multiple was " "provided:" @@ -1314,19 +1491,19 @@ msgstr "" "При выборе движка хранилища используется следующий приоритет, если их " "было предоставлено несколько:" -#: ../../config.rst:468 +#: ../../config.rst:505 msgid "Libcloud store when config contains this section." msgstr "Хранилище Libcloud, когда конфигурация содержит этот раздел." -#: ../../config.rst:470 +#: ../../config.rst:507 msgid "Boto3 store, when you have section and has all required keys." msgstr "Хранилище Boto3, когда у вас есть раздел и имеются все необходимые ключи." -#: ../../config.rst:472 +#: ../../config.rst:509 msgid "FileSystem store otherwise." msgstr "В противном случае хранилище FileSystem." -#: ../../config.rst:474 +#: ../../config.rst:511 msgid "" "Once you have defined your Libcloud providers, you have an option of " "setting one provider as the default provider of Libcloud storage. You can" @@ -1339,7 +1516,7 @@ msgstr "" "``[storages.libcloud.default]`` или же vstutils установит первое " "хранилище, как хранилище по умолчанию." -#: ../../config.rst:479 +#: ../../config.rst:516 msgid "" "If you configure default libcloud provider, vstutils will use it as " "global file storage. To override it set " @@ -1360,7 +1537,7 @@ msgstr "" "``default=storages.backends.apache_libcloud.LibCloudStorage`` в разделе " "``[storages]`` и используйте провайдера Libcloud, как по умолчанию." -#: ../../config.rst:487 +#: ../../config.rst:524 msgid "" "Here is example for boto3 connection to minio cluster with public read " "permissions, external proxy domain and internal connection support:" @@ -1368,15 +1545,15 @@ msgstr "" "Вот пример подключения boto3 к кластеру minio с публичными правами на " "чтение, внешним доменом прокси и поддержкой внутреннего подключения:" -#: ../../config.rst:516 +#: ../../config.rst:553 msgid "Throttle settings" msgstr "Настройки Throttle" -#: ../../config.rst:518 +#: ../../config.rst:555 msgid "Section ``[throttle]``." msgstr "Раздел ``[throttle]``." -#: ../../config.rst:520 +#: ../../config.rst:557 msgid "" "By including this section to your config, you can setup global and per-" "view throttle rates. Global throttle rates are specified under root " @@ -1389,13 +1566,13 @@ msgstr "" "индивидуальные throttle rates для конкретного View, вам нужно добавить " "дочернюю секцию." -#: ../../config.rst:524 +#: ../../config.rst:561 msgid "For example, if you want to apply throttle to ``api/v1/author``:" msgstr "" "Например, если вы хотите применить ограничение количества запросов для " "``api/v1/author``:" -#: ../../config.rst:532 +#: ../../config.rst:569 msgid "" "**rate** - Throttle rate in format number_of_requests/time_period. " "Expected time_periods: second/minute/hour/day." @@ -1404,7 +1581,7 @@ msgstr "" "number_of_requests/time_period. Expected time_periods: " "second/minute/hour/day." -#: ../../config.rst:533 +#: ../../config.rst:570 msgid "" "**actions** - Comma separated list of drf actions. Throttle will be " "applied only on specified here actions. Default: update, partial_update." @@ -1413,7 +1590,7 @@ msgstr "" "количества запросов будет применяться только к указанным здесь действиям." " По умолчанию: update, partial_update." -#: ../../config.rst:535 +#: ../../config.rst:572 msgid "" "More on throttling at `DRF Throttle docs `_." @@ -1421,15 +1598,15 @@ msgstr "" "Подробнее об ограничении количества запросов в `документации DRF Throttle" " `_." -#: ../../config.rst:539 +#: ../../config.rst:576 msgid "Production web settings" msgstr "Настройки для продакшн-сервера" -#: ../../config.rst:541 +#: ../../config.rst:578 msgid "Section ``[uwsgi]``." msgstr "Раздел ``[uwsgi]``." -#: ../../config.rst:543 +#: ../../config.rst:580 msgid "" "Settings related to web-server used by vstutils-based application in " "production (for deb and rpm packages by default). Most of them related to" @@ -1442,7 +1619,7 @@ msgstr "" "т. д.). Дополнительные настройки смотрите в `документации uWSGI. `_." -#: ../../config.rst:549 +#: ../../config.rst:586 msgid "" "But keep in mind that uWSGI is deprecated and may be removed in future " "releases. Use the uvicorn settings to manage your app server." @@ -1450,12 +1627,225 @@ msgstr "" "Однако имейте в виду, что uWSGI устарел и может быть удален в будущих " "версиях. Используйте настройки uvicorn для управления сервером вашего " "приложения." +#: ../../config.rst:591 +msgid "Working behind the proxy server with TLS" +msgstr "Работа за прокси-сервером с поддержкой TLS" + +#: ../../config.rst:594 +msgid "Nginx" +msgstr "Nginx" -#: ../../config.rst:554 +#: ../../config.rst:596 +msgid "" +"To configure vstutils for operation behind Nginx with TLS, follow these " +"steps:" +msgstr "" +"Чтобы настроить vstutils для работы через Nginx с поддержкой TLS, выполните следующие шаги:" + +#: ../../config.rst:598 +msgid "**Install Nginx:**" +msgstr "**Установка Nginx:**" + +#: ../../config.rst:600 +msgid "" +"Ensure that Nginx is installed on your server. You can install it using " +"the package manager specific to your operating system." +msgstr "" +"Убедитесь, что Nginx установлен на вашем сервере. Вы можете установить его с помощью " +"менеджера пакетов, специфичного для вашей операционной системы." + +#: ../../config.rst:602 +msgid "**Configure Nginx:**" +msgstr "**Настройка Nginx:**" + +#: ../../config.rst:604 +msgid "" +"Create an Nginx configuration file for your vstutils application. Below " +"is a basic example of an Nginx configuration. Adjust the values based on " +"your specific setup." +msgstr "" +"Создайте файл конфигурации Nginx для вашего приложения vstutils. Ниже " +"приведен базовый пример конфигурации Nginx. Измените значения в соответствии с " +"вашей конкретной настройкой." + +#: ../../config.rst:643 +msgid "" +"Replace ``your_domain.com`` with your actual domain, and update the paths" +" for SSL certificates." +msgstr "" +"Замените ``your_domain.com`` на ваш реальный домен и обновите пути " +"к SSL-сертификатам." + +#: ../../config.rst:645 ../../config.rst:728 ../../config.rst:779 +msgid "**Update vstutils settings:**" +msgstr "**Обновление настроек vstutils:**" + +#: ../../config.rst:647 ../../config.rst:730 ../../config.rst:781 +msgid "" +"Ensure that your vstutils settings have the correct configurations for " +"HTTPS. In your ``/etc/vstutils/settings.ini`` (or project " +"``settings.ini``):" +msgstr "" +"Убедитесь, что настройки vstutils содержат правильные конфигурации для " +"HTTPS. В вашем ``/etc/vstutils/settings.ini`` (или в проекте " +"``settings.ini``):" + +#: ../../config.rst:655 +msgid "This ensures that vstutils recognizes the HTTPS connection." +msgstr "Это гарантирует, что vstutils распознает соединение по протоколу HTTPS." + +#: ../../config.rst:657 +msgid "**Restart Nginx:**" +msgstr "**Перезапустите Nginx:**" + +#: ../../config.rst:659 +msgid "After making these changes, restart Nginx to apply the new configurations:" +msgstr "После внесения этих изменений перезапустите Nginx, чтобы применить новые конфигурации:" + +#: ../../config.rst:665 +msgid "" +"Now, your vstutils application should be accessible via HTTPS through " +"Nginx. Adjust these instructions based on your specific environment and " +"security considerations." +msgstr "" +"Теперь ваше приложение vstutils должно быть доступно через HTTPS через " +"Nginx. Измените эти инструкции в зависимости от вашего конкретного окружения и " +"требований к безопасности." + +#: ../../config.rst:669 +msgid "Traefik" +msgstr "Traefik" + +#: ../../config.rst:671 +msgid "" +"To configure vstutils for operation behind Traefik with TLS, follow these" +" steps:" +msgstr "" +"Чтобы настроить vstutils для работы через Traefik с поддержкой TLS, выполните следующие шаги:" + +#: ../../config.rst:673 +msgid "**Install Traefik:**" +msgstr "**Установка Traefik:**" + +#: ../../config.rst:675 +msgid "" +"Ensure that Traefik is installed on your server. You can download the " +"binary from the official website or use a package manager specific to " +"your operating system." +msgstr "" +"Убедитесь, что Traefik установлен на вашем сервере. Вы можете скачать двоичный файл с " +"официального сайта или использовать менеджер пакетов, специфичный для вашей " +"операционной системы." + +#: ../../config.rst:677 +msgid "**Configure Traefik:**" +msgstr "**Настройка Traefik:**" + +#: ../../config.rst:679 +msgid "" +"Create a Traefik configuration file ``/path/to/traefik.toml``. Here's a " +"basic example:" +msgstr "" +"Создайте файл конфигурации Traefik ``/path/to/traefik.toml``. Вот базовый пример:" + +#: ../../config.rst:701 +msgid "**Create Traefik Toml Configuration:**" +msgstr "**Создайте конфигурацию Traefik Toml:**" + +#: ../../config.rst:703 +msgid "" +"Create the ``/path/to/traefik_config.toml`` file with the following " +"content:" +msgstr "" +"Создайте файл ``/path/to/traefik_config.toml`` с следующим " +"содержимым:" + +#: ../../config.rst:726 +msgid "Make sure to replace ``your_domain.com`` with your actual domain." +msgstr "Обязательно замените ``your_domain.com`` на ваш реальный домен." + +#: ../../config.rst:737 +msgid "**Start Traefik:**" +msgstr "**Запустите Traefik:**" + +#: ../../config.rst:739 +msgid "Start Traefik with the following command:" +msgstr "Запустите Traefik следующей командой:" + +#: ../../config.rst:745 +msgid "" +"Now, your vstutils application should be accessible via HTTPS through " +"Traefik. Adjust these instructions based on your specific environment and" +" requirements." +msgstr "" +"Теперь ваше приложение vstutils должно быть доступно через HTTPS через " +"Traefik. Измените эти инструкции в зависимости от вашего конкретного окружения и " +"требований." + +#: ../../config.rst:749 +msgid "HAProxy" +msgstr "HAProxy" + +#: ../../config.rst:751 +msgid "**Install HAProxy:**" +msgstr "**Установка HAProxy:**" + +#: ../../config.rst:753 +msgid "" +"Ensure that HAProxy is installed on your server. You can install it using" +" the package manager specific to your operating system." +msgstr "" +"Убедитесь, что HAProxy установлен на вашем сервере. Вы можете установить его с помощью" +" менеджера пакетов, специфичного для вашей операционной системы." + +#: ../../config.rst:755 +msgid "**Configure HAProxy:**" +msgstr "**Настройка HAProxy:**" + +#: ../../config.rst:757 +msgid "" +"Create an HAProxy configuration file for your vstutils application. Below" +" is a basic example of an HAProxy configuration. Adjust the values based " +"on your specific setup." +msgstr "" +"Создайте файл конфигурации HAProxy для вашего приложения vstutils. Вот базовый пример конфигурации" +" HAProxy. Измените значения в соответствии с вашей конкретной настройкой." + +#: ../../config.rst:777 +msgid "" +"Replace ``your_domain.com`` with your actual domain and update the paths " +"for SSL certificates." +msgstr "" +"Замените ``your_domain.com`` на ваш реальный домен и обновите пути " +"к SSL-сертификатам." + +#: ../../config.rst:788 +msgid "**Restart HAProxy:**" +msgstr "**Перезапуск HAProxy:**" + +#: ../../config.rst:790 +msgid "" +"After making these changes, restart HAProxy to apply the new " +"configurations:" +msgstr "" +"После внесения этих изменений перезапустите HAProxy, чтобы применить новые " +"конфигурации." + +#: ../../config.rst:796 +msgid "" +"Now, your vstutils application should be accessible via HTTPS through " +"HAProxy. Adjust these instructions based on your specific environment and" +" security considerations." +msgstr "" +"Теперь ваше приложение vstutils должно быть доступно через HTTPS через " +"HAProxy. Измените эти инструкции в зависимости от вашего конкретного окружения и " +"требований к безопасности." + +#: ../../config.rst:800 msgid "Configuration options" msgstr "Параметры конфигурации" -#: ../../config.rst:556 +#: ../../config.rst:802 msgid "" "This section contains additional information for configure additional " "elements." @@ -1463,7 +1853,7 @@ msgstr "" "В этом разделе содержится дополнительная информация для настройки " "дополнительных элементов." -#: ../../config.rst:558 +#: ../../config.rst:804 msgid "" "If you need set ``https`` for your web settings, you can do it using " "HAProxy, Nginx, Traefik or configure it in ``settings.ini``." @@ -1472,7 +1862,7 @@ msgstr "" " сделать это с помощью HAProxy, Nginx, Traefik или настроить в файле " "``settings.ini``." -#: ../../config.rst:570 +#: ../../config.rst:816 msgid "" "We strictly do not recommend running the web server from root. Use HTTP " "proxy to run on privileged ports." @@ -1480,7 +1870,7 @@ msgstr "" "Мы настоятельно не рекомендуем запускать веб-сервер от имени root. " "Используйте HTTP-прокси, чтобы работать на привилегированных портах." -#: ../../config.rst:572 +#: ../../config.rst:818 msgid "" "You can use `{ENV[HOME:-value]}` (where `HOME` is environment variable, " "`value` is default value) in configuration values." @@ -1488,7 +1878,7 @@ msgstr "" "Вы можете использовать `{ENV[HOME:-value]}` (где `HOME` - переменная " "окружения, `value` - значение по умолчанию) в значениях конфигурации." -#: ../../config.rst:575 +#: ../../config.rst:821 msgid "" "You can use environment variables for setup important settings. But " "config variables has more priority then env. Available settings are: " @@ -1501,7 +1891,7 @@ msgstr "" "``DJANGO_LOG_LEVEL``, ``TIMEZONE`` и некоторые настройки с префиксом " "``[ENV_NAME]``." -#: ../../config.rst:578 +#: ../../config.rst:824 msgid "" "For project without special settings and project levels named ``project``" " these variables will start with ``PROJECT_`` prefix. There is a list of " @@ -1525,7 +1915,7 @@ msgstr "" "``{ENV_NAME}_GLOBAL_THROTTLE_RATE``, и " "``{ENV_NAME}_GLOBAL_THROTTLE_ACTIONS``." -#: ../../config.rst:585 +#: ../../config.rst:831 msgid "" "There are also URI-specific variables for connecting to various services " "such as databases and caches. There are ``DATABASE_URL``, ``CACHE_URL``, " @@ -1539,7 +1929,7 @@ msgstr "" "``SESSIONS_CACHE_URL`` и ``ETAG_CACHE_URL``. Как видно из названий, они " "тесно связаны с ключами и именами соответствующих секций конфигурации." -#: ../../config.rst:589 +#: ../../config.rst:835 msgid "" "We recommend to install ``uvloop`` to your environment and setup ``loop =" " uvloop`` in ``[uvicorn]`` section for performance reasons." @@ -1547,3 +1937,37 @@ msgstr "" "Мы рекомендуем установить ``uvloop`` в ваше окружение и настроить ``loop " "= uvloop`` в разделе ``[uvicorn]`` для повышения производительности." +#: ../../config.rst:837 +msgid "" +"In the context of vstutils, the adoption of ``uvloop`` is paramount for " +"optimizing the performance of the application, especially because " +"utilizing ``uvicorn`` as the ASGI server. ``uvloop`` is an ultra-fast, " +"drop-in replacement for the default event loop provided by Python. It is " +"built on top of ``libuv``, a high-performance event loop library, and is " +"specifically designed to optimize the execution speed of asynchronous " +"code." +msgstr "" +"В контексте vstutils внедрение ``uvloop`` играет ключевую роль в " +"оптимизации производительности приложения, особенно при использовании " +"``uvicorn`` в качестве ASGI-сервера. ``uvloop`` представляет собой " +"ультра-быстрый, готовый к использованию заменитель стандартного цикла " +"событий, предоставляемого Python. Он построен на базе ``libuv``, " +"библиотеки высокопроизводительного цикла событий, и специально разработан" +" для оптимизации скорости выполнения асинхронного кода." + +#: ../../config.rst:841 +msgid "" +"By leveraging ``uvloop``, developers can achieve substantial performance " +"improvements in terms of reduced latency and increased throughput. This " +"is especially critical in scenarios where applications handle a large " +"number of concurrent connections. The improved efficiency of event loop " +"handling directly translates to faster response times and better overall " +"responsiveness of the application." +msgstr "" +"Используя ``uvloop``, разработчики могут достичь значительного улучшения " +"производительности за счет снижения задержек и увеличения пропускной " +"способности. Это особенно важно в сценариях, где приложения обрабатывают " +"большое количество одновременных подключений. Улучшенная эффективность " +"обработки цикла событий напрямую переводится в более быстрое время ответа" +" и лучшую общую отзывчивость приложения." + diff --git a/doc/locale/ru/LC_MESSAGES/quickstart.po b/doc/locale/ru/LC_MESSAGES/quickstart.po index bf860b4f..10dc08df 100644 --- a/doc/locale/ru/LC_MESSAGES/quickstart.po +++ b/doc/locale/ru/LC_MESSAGES/quickstart.po @@ -3,19 +3,18 @@ # This file is distributed under the same license as the VST Utils package. # FIRST AUTHOR , 2022. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-11-25 04:56+0000\n" +"POT-Creation-Date: 2024-01-10 23:37+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.13.1\n" +"Generated-By: Babel 2.14.0\n" #: ../../quickstart.rst:2 msgid "Quick Start" @@ -196,27 +195,27 @@ msgstr "Эта команда создает новый проект без по msgid "These commands create several files in ``project directory``." msgstr "Эти команды создают несколько файлов в ``project directory``:" -#: ../../quickstart.rst:114 +#: ../../quickstart.rst:112 msgid "where:" msgstr "где" -#: ../../quickstart.rst:116 +#: ../../quickstart.rst:114 msgid "**frontend_src** - directory that contains all sources for frontend;" msgstr "" "**frontend_src** - директория, содержащая все исходные файлы для " "фронтенда;" -#: ../../quickstart.rst:117 +#: ../../quickstart.rst:115 msgid "**MANIFEST.in** - this file is used for building installation package;" msgstr "" "**MANIFEST.in** - этот файл используется для создания установочного " "пакета;" -#: ../../quickstart.rst:118 +#: ../../quickstart.rst:116 msgid "**{{app_name}}** - directory with files of your application;" msgstr "**{{app_name}}** - директория с файлами вашего приложения;" -#: ../../quickstart.rst:119 +#: ../../quickstart.rst:117 msgid "" "**package.json** - this file contains list of frontend dependencies and " "commands to build;" @@ -224,7 +223,7 @@ msgstr "" "**package.json** - этот файл содержит список зависимостей фронтенда и " "команд для сборки;" -#: ../../quickstart.rst:120 +#: ../../quickstart.rst:118 msgid "" "**README.rst** - default README file for your application (this file " "includes base commands for starting/stopping your application);" @@ -232,7 +231,7 @@ msgstr "" "**README.rst** - стандартный README файл для вашего приложения (этот файл" " включает базовые команды для запуска/остановки вашего приложения);" -#: ../../quickstart.rst:121 +#: ../../quickstart.rst:119 msgid "" "**requirements-test.txt** - file with list of requirements for test " "environment;" @@ -240,29 +239,29 @@ msgstr "" "**requirements-test.txt** - файл со списком зависимостей для тестирования" " вашего окружения;" -#: ../../quickstart.rst:122 +#: ../../quickstart.rst:120 msgid "" "**requirements.txt** - file with list of requirements for your " "application;" msgstr "**requirements.txt** - файл со списком зависимостей вашего приложения;" -#: ../../quickstart.rst:123 -msgid "**setup.cfg** - this file is used for building installation package;" -msgstr "**setup.cfg** - файл, используемый для сборки установочного пакета;" +#: ../../quickstart.rst:121 +msgid "**pyproject.toml** - this file is used for building installation package;" +msgstr "**pyproject.toml** - файл, используемый для сборки установочного пакета;" -#: ../../quickstart.rst:124 +#: ../../quickstart.rst:122 msgid "**setup.py** - this file is used for building installation package;" msgstr "**setup.py** - файл, используемый для сборки установочного пакета;" -#: ../../quickstart.rst:125 +#: ../../quickstart.rst:123 msgid "**test.py** - this file is used for tests creation;" msgstr "**test.py** - этот файл используется для создания тестов;" -#: ../../quickstart.rst:126 +#: ../../quickstart.rst:124 msgid "**tox.ini** - this file is used for tests execution;" msgstr "**tox.ini** - этот файл используется для выполнения тестов;" -#: ../../quickstart.rst:127 +#: ../../quickstart.rst:125 msgid "" "**webpack.config.js.default** - this file contain minimal script for " "webpack (replace '.default' if write smthg in 'app.js')." @@ -270,7 +269,7 @@ msgstr "" "**webpack.config.js.default** - этот файл содержит минимальный скрипт для" " webpack (замените '.default', если пишите что-то в 'app.js')." -#: ../../quickstart.rst:129 +#: ../../quickstart.rst:127 msgid "" "You should execute below commands from the " "``/{{app_dir}}/{{app_name}}/`` directory. It is good practice to use tox " @@ -286,11 +285,11 @@ msgstr "" "``tox -e contrib`` в директории проекта, что автоматически создаст новое " "окружение с необходимыми зависимостями." -#: ../../quickstart.rst:134 +#: ../../quickstart.rst:132 msgid "**Apply migrations**" msgstr "**Применение миграций**" -#: ../../quickstart.rst:136 +#: ../../quickstart.rst:134 msgid "" "Let’s verify a newly created vstutils project does work. Change into the " "outer ``/{{app_dir}}/{{app_name}}`` directory, if you haven’t already, " @@ -300,7 +299,7 @@ msgstr "" " каталог ``/{{app_dir}}/{{app_name}}``, если вы еще этого не сделали, и " "выполните следующую команду:" -#: ../../quickstart.rst:144 +#: ../../quickstart.rst:142 msgid "" "This command create SQLite (by default) database with default SQL-schema." " VSTUTILS supports all databases `Django does " @@ -311,15 +310,15 @@ msgstr "" "`Django. " "`_" -#: ../../quickstart.rst:147 +#: ../../quickstart.rst:145 msgid "**Create superuser**" msgstr "**Создание суперпользователя**" -#: ../../quickstart.rst:153 +#: ../../quickstart.rst:151 msgid "**Start your application**" msgstr "**Запуск приложения**" -#: ../../quickstart.rst:159 +#: ../../quickstart.rst:157 msgid "" "Web-interface of your application has been started on the port 8080. " "You’ve started the vstutils production server based on `uWSGI `_." -#: ../../quickstart.rst:163 +#: ../../quickstart.rst:161 msgid "" "Now’s a good time to note: if you want to run the web-server with a " "debugger, then you should run `the standard Django's dev-server " @@ -341,11 +340,11 @@ msgstr "" " `_" -#: ../../quickstart.rst:168 +#: ../../quickstart.rst:166 msgid "If you need to stop the server, use following command:" msgstr "Если вам нужно остановить сервер, используйте следующую команду:" -#: ../../quickstart.rst:175 +#: ../../quickstart.rst:173 msgid "" "You've created the simplest application, based on VST Utils framework. " "This application only contains User Model. If you want to create your own" @@ -355,11 +354,11 @@ msgstr "" " приложение содержит только модель пользователя. Если вы хотите создать " "свои собственные модели, обратитесь к разделу ниже." -#: ../../quickstart.rst:180 +#: ../../quickstart.rst:178 msgid "Adding new models to application" msgstr "Добавление новых моделей в приложение" -#: ../../quickstart.rst:181 +#: ../../quickstart.rst:179 msgid "" "If you want to add some new entities to your application, you need to do " "following on the back-end:" @@ -367,35 +366,35 @@ msgstr "" "Если вы хотите добавить новые сущности в ваше приложение, вам необходимо " "выполнить следующие действия на серверной стороне: " -#: ../../quickstart.rst:183 +#: ../../quickstart.rst:181 msgid "Create Model;" msgstr "Создайте модель;" -#: ../../quickstart.rst:184 +#: ../../quickstart.rst:182 msgid "Create Serializer (optional);" msgstr "Создайте сериализатор (опционально);" -#: ../../quickstart.rst:185 +#: ../../quickstart.rst:183 msgid "Create View (optional);" msgstr "Создайте view (опционально);" -#: ../../quickstart.rst:186 +#: ../../quickstart.rst:184 msgid "Add created Model or View to the API;" msgstr "Добавьте созданную модель или view в API;" -#: ../../quickstart.rst:187 +#: ../../quickstart.rst:185 msgid "Make migrations;" msgstr "Создайте миграции;" -#: ../../quickstart.rst:188 +#: ../../quickstart.rst:186 msgid "Apply migrations;" msgstr "Примените миграции;" -#: ../../quickstart.rst:189 +#: ../../quickstart.rst:187 msgid "Restart your application." msgstr "Перезапустите ваше приложение." -#: ../../quickstart.rst:191 +#: ../../quickstart.rst:189 msgid "" "Let's look how you can do it on the AppExample - application, that has 2 " "custom models:" @@ -403,13 +402,13 @@ msgstr "" "Давайте посмотрим, как это можно сделать на примере приложения AppExample" " которое содержит 2 пользовательские модели: " -#: ../../quickstart.rst:193 +#: ../../quickstart.rst:191 msgid "Task (abstraction for some tasks/activities, that user should do);" msgstr "" "Task (абстракция для некоторых задач/активностей, которые пользователь " "должен выполнить);" -#: ../../quickstart.rst:194 +#: ../../quickstart.rst:192 msgid "" "Stage (abstraction for some stages, that user should do to complete the " "task. This model is nested into the Task Model)." @@ -417,11 +416,11 @@ msgstr "" "Stage (абстракция для некоторых этапов, которые пользователь должен " "пройти для выполнения задачи. Эта модель вложена в модель Task)." -#: ../../quickstart.rst:198 +#: ../../quickstart.rst:196 msgid "Models creation" msgstr "Создание моделей" -#: ../../quickstart.rst:199 +#: ../../quickstart.rst:197 msgid "" "Firstly, you need to create file ``{{model_name}}.py`` in the " "``/{{app_dir}}/{{app_name}}/{{app_name}}/models`` directory." @@ -429,7 +428,7 @@ msgstr "" "Сначала вам необходимо создать файл ``{{model_name}}.py`` в директории " "``/{{app_dir}}/{{app_name}}/{{app_name}}/models``." -#: ../../quickstart.rst:201 +#: ../../quickstart.rst:199 msgid "Let make out an example from **BModel**:" msgstr "Давайте рассмотрим пример с моделью **BModel**:" @@ -691,9 +690,10 @@ msgstr "" "вложенного view, kwargs для декоратора " ":class:`vstutils.api.decorators.nested_view`, но поддерживает атрибут " "``model`` в качестве вложенного). ``model`` может быть строкой для " -"импорта. Используйте параметр ``override_params`` в тех случаях, когда необходимо перегрузить" -"параметры генерируемого представления в качестве вложенного (работает только когда задан ``model`` " -"как вложенное представление)." +"импорта. Используйте параметр ``override_params`` в тех случаях, когда " +"необходимо перегрузить параметры генерируемого представления в качестве " +"вложенного (работает только когда задан ``model`` как вложенное " +"представление)." #: of vstutils.models.BModel:123 msgid "" @@ -784,10 +784,11 @@ msgid "" "Django Rest Framework (DRF) Generic ViewSets. It allows developers to " "define and customize various aspects of the associated DRF view class." msgstr "" -"Метод ``get_view_class()`` — это служебный метод в ORM Django " -"моделях, предназначенный для облегчения настройки и создания экземпляров" -"представлений Django Rest Framework (DRF). Это позволяет разработчикам " -"определить и настроить различные аспекты класса представления DRF." +"Метод ``get_view_class()`` — это служебный метод в ORM Django моделях, " +"предназначенный для облегчения настройки и создания " +"экземпляров представлений Django Rest Framework (DRF). Это позволяет " +"разработчикам определить и настроить различные аспекты класса " +"представления DRF." #: of vstutils.models.BModel:189 msgid "" @@ -796,12 +797,13 @@ msgid "" " backends, permission classes, etc. It uses attributes declared in meta " "attributes, but allows individual parts to be overriden." msgstr "" -"Разработчики могут использовать этот метод для изменения различных аспектов " -"получаемого представления, таких как классы сериализаторов, конфигурацию полей, фильтры," -"классы разрешений и т.п. Этот метод использует такие же атрибуты, которые были объявлены" -"в мета-атрибутах, но позволяет перегружать отдельные части." +"Разработчики могут использовать этот метод для изменения различных " +"аспектов получаемого представления, таких как классы сериализаторов, " +"конфигурацию полей, фильтры,классы разрешений и т.п. Этот метод " +"использует такие же атрибуты, которые были объявлены в мета-атрибутах, но " +"позволяет перегружать отдельные части." -#: ../../quickstart.rst:208 +#: ../../quickstart.rst:206 msgid "" "More information about Models you can find in `Django Models " "documentation " @@ -811,7 +813,7 @@ msgstr "" "`Django Models " "`_." -#: ../../quickstart.rst:210 +#: ../../quickstart.rst:208 msgid "" "If you don't need to create custom " ":ref:`serializers` or :ref:`view " @@ -823,17 +825,17 @@ msgstr "" "sets`, вы можете перейти к этому " ":ref:`этапу`." -#: ../../quickstart.rst:215 +#: ../../quickstart.rst:213 msgid "Serializers creation" msgstr "Создание сериализаторов" -#: ../../quickstart.rst:217 +#: ../../quickstart.rst:215 msgid "*Note - If you don't need custom serializer you can skip this section*" msgstr "" "*Примечание - Если вам не нужен пользовательский сериализатор, вы можете " "пропустить этот раздел.*" -#: ../../quickstart.rst:219 +#: ../../quickstart.rst:217 msgid "" "Firstly, you need to create file ``serializers.py`` in the " "``/{{app_dir}}/{{app_name}}/{{app_name}}/`` directory." @@ -841,13 +843,13 @@ msgstr "" "В первую очередь вам необходимо создать файл ``serializers.py`` в " "директории ``/{{app_dir}}/{{app_name}}/{{app_name}}/``." -#: ../../quickstart.rst:221 +#: ../../quickstart.rst:219 msgid "Then you need to add some code like this to ``serializers.py``:" msgstr "" "Затем вам нужно добавить некоторый код, подобный следующему, в файл " "``serializers.py``:" -#: ../../quickstart.rst:244 +#: ../../quickstart.rst:242 msgid "" "More information about Serializers you can find in `Django REST Framework" " documentation for Serializers `_." -#: ../../quickstart.rst:250 +#: ../../quickstart.rst:248 msgid "Views creation" msgstr "Создание views" -#: ../../quickstart.rst:252 +#: ../../quickstart.rst:250 msgid "*Note - If you don't need custom view set you can skip this section*" msgstr "" "*Примечание - Если вам не нужен пользовательский view set, вы можете " "пропустить этот раздел.*" -#: ../../quickstart.rst:254 +#: ../../quickstart.rst:252 msgid "" "Firstly, you need to create file ``views.py`` in the " "``/{{app_dir}}/{{app_name}}/{{app_name}}/`` directory." @@ -875,13 +877,13 @@ msgstr "" "В первую очередь вам необходимо создать файл ``views.py`` в директории " "``/{{app_dir}}/{{app_name}}/{{app_name}}/``." -#: ../../quickstart.rst:256 +#: ../../quickstart.rst:254 msgid "Then you need to add some code like this to ``views.py``:" msgstr "" "Затем вам нужно добавить некоторый код, подобный следующему, в файл " "``views.py``:" -#: ../../quickstart.rst:285 +#: ../../quickstart.rst:283 msgid "" "More information about Views and ViewSets you can find in `Django REST " "Framework documentation for views `_." -#: ../../quickstart.rst:291 +#: ../../quickstart.rst:289 msgid "Adding Models to API" msgstr "Добавление моделей в API" -#: ../../quickstart.rst:293 +#: ../../quickstart.rst:291 msgid "" "To add created Models to the API you need to write something like this at" " the end of your ``settings.py`` file:" @@ -903,11 +905,11 @@ msgstr "" "Для добавления моделей в APi вам нужно написать код, подобный этому в в " "конце файла ``settings.py``:" -#: ../../quickstart.rst:329 +#: ../../quickstart.rst:327 msgid "Migrations creation" msgstr "Создание миграций" -#: ../../quickstart.rst:330 +#: ../../quickstart.rst:328 msgid "" "To make migrations open ``/{{app_dir}}/{{app_name}}/`` directory and " "execute following command:" @@ -915,7 +917,7 @@ msgstr "" "Для создания миграций откройте директорию ``/{{app_dir}}/{{app_name}}/`` " "и выполните следующую команду:" -#: ../../quickstart.rst:336 +#: ../../quickstart.rst:334 msgid "" "More information about Migrations you can find in `Django Migrations " "documentation " @@ -925,11 +927,11 @@ msgstr "" "Django Migrations " "`_." -#: ../../quickstart.rst:340 +#: ../../quickstart.rst:338 msgid "Migrations applying" msgstr "Применение миграций" -#: ../../quickstart.rst:341 +#: ../../quickstart.rst:339 msgid "" "To apply migrations you need to open ``/{{app_dir}}/{{app_name}}/`` " "directory and execute following command:" @@ -937,11 +939,11 @@ msgstr "" "Для применения миграций вам необходимо открыть директорию " "``/{{app_dir}}/{{app_name}}/`` и выполнить следующую команду:" -#: ../../quickstart.rst:349 +#: ../../quickstart.rst:347 msgid "Restart of Application" msgstr "Перезапуск приложения" -#: ../../quickstart.rst:350 +#: ../../quickstart.rst:348 msgid "" "To restart your application, firstly, you need to stop it (if it was " "started before):" @@ -949,15 +951,15 @@ msgstr "" "Для перезапуска вашего приложения вам сначала нужно остановить его (если " "оно было запущено ранее):" -#: ../../quickstart.rst:356 +#: ../../quickstart.rst:354 msgid "And then start it again:" msgstr "Затем запустите его снова:" -#: ../../quickstart.rst:362 +#: ../../quickstart.rst:360 msgid "After cache reloading you will see following page:" msgstr "После перезагрузки кэша вы увидите следующую страницу:" -#: ../../quickstart.rst:366 +#: ../../quickstart.rst:364 msgid "" "As you can see, link to new Task View has been added to the sidebar menu." " Let's click on it." @@ -965,17 +967,17 @@ msgstr "" "Как вы можете видеть, ссылка на новое Task view добавлена в боковое меню." " Давайте нажмем на нее." -#: ../../quickstart.rst:370 +#: ../../quickstart.rst:368 msgid "There is no task instance in your app. Add it using 'new' button." msgstr "" "В вашем приложении нет экземпляра задачи. Добавьте его, используя кнопку " "'new'." -#: ../../quickstart.rst:374 +#: ../../quickstart.rst:372 msgid "After creating a new task you'll see a following page:" msgstr "После создания новой задачи вы увидите следующую страницу:" -#: ../../quickstart.rst:378 +#: ../../quickstart.rst:376 msgid "" "As you can see, there is 'stages' button, that opens page with this " "task's stages list. Let's click on it." @@ -983,15 +985,15 @@ msgstr "" "Как видите, есть кнопка 'stages', которая открывает страницу со списком " "этапов этой задачи. Давайте на нее нажмем." -#: ../../quickstart.rst:382 +#: ../../quickstart.rst:380 msgid "There is no stage instances in your app. Let's create 2 new stages." msgstr "В вашем приложении нет экземпляров этапов. Давайте создадим 2 новых этапа." -#: ../../quickstart.rst:387 +#: ../../quickstart.rst:385 msgid "After stages creation page with stages list will looks like this:" msgstr "После создания этапов страница со списком этапов будет выглядеть так:" -#: ../../quickstart.rst:391 +#: ../../quickstart.rst:389 msgid "" "Sorting by 'order' field works, as we mentioned in the our ``models.py`` " "file for Stage Model." @@ -999,7 +1001,7 @@ msgstr "" "Сортировка по полю `order` работает, как мы указали в нашем файле " "``models.py`` для модели Stage." -#: ../../quickstart.rst:393 +#: ../../quickstart.rst:391 msgid "" "Additional information about Django and Django REST Framework you can " "find in `Django documentation `_ " diff --git a/requirements-rtd.txt b/requirements-rtd.txt index 7ba0c2e6..69e55b64 100644 --- a/requirements-rtd.txt +++ b/requirements-rtd.txt @@ -1,7 +1,7 @@ -rrequirements.txt -rrequirements-doc.txt -rrequirements-rpc.txt -django~=4.2.8 -httpx>=0.25.2 +django~=4.2.9 +httpx>=0.26.0 typing-extensions sphinx-intl~=2.1.0 diff --git a/requirements-stubs.txt b/requirements-stubs.txt index c06820e6..946ca906 100644 --- a/requirements-stubs.txt +++ b/requirements-stubs.txt @@ -1,7 +1,7 @@ django-stubs[compatible-mypy]~=4.2.7 djangorestframework-stubs[compatible-mypy]~=3.14.5 celery-stubs~=0.1.3 -drf-yasg-stubs~=0.1.3 +drf-yasg-stubs~=0.1.4 django-filter-stubs~=0.1.3 types-PyMySQL==1.1.0.1 types-Markdown==3.5.0.3 diff --git a/requirements-test.txt b/requirements-test.txt index 17316ca1..cd14e6d9 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,6 +1,6 @@ # Packages needed for test -coverage~=7.3.2 +coverage~=7.4.0 fakeldap~=0.6.6 tblib==3.0.0 beautifulsoup4~=4.12.2 -httpx~=0.25.1 +httpx~=0.26.0 diff --git a/requirements.txt b/requirements.txt index 107020af..8a5ec114 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Main packages configparserc~=2.0.2 -Markdown~=3.5.1 +Markdown~=3.5.2 django-environ~=0.11.2 # REST API packages @@ -13,8 +13,10 @@ pyyaml~=6.0.1 # web server uvicorn~=0.25.0 -uwsgi==2.0.23 -fastapi~=0.105.0 +pyuwsgi==2.0.23.post0 +# Restore it if some problems with pyuwsgi +# uwsgi==2.0.23 +fastapi~=0.108.0 aiofiles==23.2.1 # Notifications diff --git a/setup.py b/setup.py index d718aece..3356c0ca 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ 'vstutils/static/bundle/.*\.js$' ], install_requires=[ - "django~=" + (os.environ.get('DJANGO_DEP', "") or "4.2.7"), + "django~=" + (os.environ.get('DJANGO_DEP', "") or "4.2.9"), ] + requirements + load_requirements('requirements-doc.txt'), @@ -53,7 +53,7 @@ 'doc': load_requirements('requirements-doc.txt'), 'prod': load_requirements('requirements-prod.txt'), 'stubs': load_requirements('requirements-stubs.txt'), - 'pil': ['Pillow~=10.1.0'], + 'pil': ['Pillow~=10.2.0'], 'boto3': [ i.replace('libcloud', 'libcloud,s3') for i in requirements diff --git a/vstutils/api/fields.py b/vstutils/api/fields.py index ad727eea..efa0ec55 100644 --- a/vstutils/api/fields.py +++ b/vstutils/api/fields.py @@ -49,16 +49,16 @@ def to_internal_value(self, data) -> _t.Text: class FileInStringField(VSTCharField): """ - Field extends :class:`.VSTCharField` and saves file's content as string. + This field extends :class:`.VSTCharField` and stores the content of a file as a string. - Value must be text (not binary) and saves in model as is. + The value must be text (not binary) and is saved in the model as is. - :param media_types: List of MIME types to select on the user's side. - Supported syntax using ``*``. Default: ``['*/*']`` - :type media_types: tuple,list + :param media_types: A list of MIME types to filter on the user's side. + Supports the use of ``*`` as a wildcard. Default: ``['*/*']`` + :type media_types: tuple, list .. note:: - Take effect only in GUI. In API it would behave as :class:`.VSTCharField`. + This setting only takes effect in the GUI. In the API, it behaves like :class:`.VSTCharField`. """ def __init__(self, **kwargs): @@ -68,16 +68,16 @@ def __init__(self, **kwargs): class SecretFileInString(FileInStringField): """ - Field extends :class:`.FileInStringField`, but hides it's value in admin interface. + This field extends :class:`.FileInStringField` but hides its value in the admin interface. - Value must be text (not binary) and saves in model as is. + The value must be text (not binary) and is saved in the model as is. - :param media_types: List of MIME types to select on the user's side. - Supported syntax using ``*``. Default: ``['*/*']`` - :type media_types: tuple,list + :param media_types: A list of MIME types to filter on the user's side. + Supports the use of ``*`` as a wildcard. Default: ``['*/*']`` + :type media_types: tuple, list .. note:: - Take effect only in GUI. In API it would behave as :class:`.VSTCharField`. + This setting only takes effect in the GUI. In the API, it behaves like :class:`.VSTCharField`. """ def __init__(self, **kwargs): @@ -87,14 +87,16 @@ def __init__(self, **kwargs): class BinFileInStringField(FileInStringField): """ - Field extends :class:`.FileInStringField`, but works with binary (base64) files. + This field extends :class:`.FileInStringField` and is specifically designed to handle binary files. + In the GUI, it functions as a file input field, accepting binary files from the user, which are then + converted to base64-encoded strings and stored in this field. - :param media_types: List of MIME types to select on the user's side. - Supported syntax using ``*``. Default: `['*/*']` - :type media_types: tuple,list + :param media_types: A list of MIME types to filter on the user's side. + Supports the use of ``*`` as a wildcard. Default: `['*/*']` + :type media_types: tuple, list .. note:: - Effective only in GUI. Works similar to :class:`.VSTCharField` in API. + This functionality is effective only in the GUI. In the API, it behaves similarly to :class:`.VSTCharField`. """ diff --git a/vstutils/models/custom_model.py b/vstutils/models/custom_model.py index 3dfc6047..211bc411 100644 --- a/vstutils/models/custom_model.py +++ b/vstutils/models/custom_model.py @@ -288,9 +288,10 @@ class Authors(ListModel): In this case, we setup source list via `setup_custom_queryset_kwargs` function, and any other chained call is going to work with this data. + + :ivar list data: List with data dicts. Empty by default. """ - #: List with data dicts. Empty by default. data = [] objects = CustomQuerySet.as_manager() @@ -319,25 +320,23 @@ def _get_data(cls, chunked_fetch=False, data_source=None): class FileModel(ListModel): """ - Custom model which loads data from YAML-file instead of database. - Path to the file stored in `FileModel.file_path` attribute. - + Custom model that loads data from a YAML file instead of a database. + The path to the file is specified in the `FileModel.file_path` attribute. Examples: - Source file stored in `/etc/authors.yaml` with content: + Suppose the source file is stored at `/etc/authors.yaml` with the following content: .. sourcecode:: YAML - name: "Sergey Klyuykov" - name: "Michael Taran" - Example: + You can create a custom model using this file: .. sourcecode:: python from vstutils.custom_model import FileModel, CharField - class Authors(FileModel): name = CharField(max_length=512) @@ -360,25 +359,83 @@ def _get_data(cls, chunked_fetch=False): class ExternalCustomModel(ListModel): """ - This custom model is intended for self-implementation of requests to external services. - The model allows you to pass filtering, limiting and sorting parameters to an external request, - receiving already limited data. + Represents a custom model designed for the self-implementation of requests to external services. + + This model facilitates the seamless interaction with external services by allowing the + passing of filtering, limiting, and sorting parameters to an external request. It is designed + to receive data that is already filtered and limited. + + To utilize this model effectively, developers need to implement the ``get_data_generator()`` + class method. This method receives a query object containing the necessary parameters, + enabling developers to customize interactions with external services. + + **Example:** + + .. code-block:: python + + class MyExternalModel(ExternalCustomModel): + # ... model fields ... + + class Meta: + managed = False + + @classmethod + def get_data_generator(cls, query): + data = ... # some fetched data from the external resource or generated from memory calculations. + for row in data: + yield row - To start using this model, it is enough to implement the ``get_data_generator()`` class method, - which receives the query object with the necessary parameters as an argument. """ class Meta: abstract = True @classmethod def get_data_generator(cls, query): + """ + This class method must be implemented by derived classes to define custom logic + for fetching data from an external service based on the provided query parameters. + + Query object might contain the following parameters: + + * filter (dict): A dictionary specifying the filtering criteria. + * exclude (dict): A dictionary specifying the exclusion criteria. + * order_by (list): A list specifying the sorting order. + * low_mark (int): The low index for slicing (if sliced). + * high_mark (int): The high index for slicing (if sliced). + * is_sliced (bool): A boolean indicating whether the query is sliced. + + :param query: An object containing filtering, limiting, and sorting parameters. + :type query: dict + + :return: A generator that yields the requested data. + :rtype: Generator + + :raises NotImplementedError: If the method is not implemented by the derived class. + """ raise NotImplementedError class ViewCustomModel(ExternalCustomModel): """ - This model implements the SQL View programming mechanism over other models. - In the ``get_view_queryset()`` method, a query is prepared, and all further actions are implemented on top of it. + Implements the SQL View programming mechanism over other models. + + This model provides a mechanism for implementing SQL View-like behavior over other models. + In the ``get_view_queryset()`` method, a base query is prepared, and all further actions + are implemented on top of it. + + **Example Usage:** + + .. code-block:: python + + class MyViewModel(ViewCustomModel): + # ... model fields ... + + class Meta: + managed = False + + @classmethod + def get_view_queryset(cls): + return SomeModel.objects.annotate(...) # add some additional annotations to query """ class Meta: @@ -386,6 +443,15 @@ class Meta: @classmethod def get_view_queryset(cls): + """ + This class method must be implemented by derived classes to define custom logic + for generating the base queryset for the SQL View. + + :return: The base queryset for the SQL View. + :rtype: django.db.models.query.QuerySet + + :raises NotImplementedError: If the method is not implemented by the derived class. + """ raise NotImplementedError @classmethod diff --git a/vstutils/models/decorators.py b/vstutils/models/decorators.py index dbcde2d6..e9e594ff 100644 --- a/vstutils/models/decorators.py +++ b/vstutils/models/decorators.py @@ -32,16 +32,46 @@ class register_view_action(register_view_decorator): # pylint: disable=invalid- """ Decorator for turning model methods to generated view `actions `_. - The decorated method becomes a method of generated view and `self` is an view object. - See supported args in :func:`vstutils.api.decorators.subaction`. + When a method is decorated, it becomes a part of the generated view and + the `self` reference within the method points to the view object. + This allows you to extend the functionality of generated views with custom actions. + + The `register_view_action` decorator supports various arguments, and you can refer to the documentation + for :func:`vstutils.api.decorators.subaction` to explore the complete list of supported arguments. + These arguments provide flexibility in defining the behavior and characteristics of the generated view actions. .. note:: - Sometimes you may need to use proxy models with a common set of actions. - To receive the action by the proxy model, pass the named argument ``inherit`` with ``True`` value. + In scenarios where you're working with proxy models that share a common set of actions, + you can use the `inherit` named argument with a value of `True`. + This allows the proxy model to inherit actions defined in the base model, + reducing redundancy and promoting code reuse. .. note:: - Often, an action does not transfer any parameters and requires only sending an empty query. - To speed up development, we set the default serializer to :class:`vstutils.api.serializers.EmptySerializer`. + In many cases, an action may not require any parameters and can be executed by sending an empty query. + To streamline development and enhance efficiency, the `register_view_action` decorator sets + the default serializer to :class:`vstutils.api.serializers.EmptySerializer`. + This means that the action expects no input data, + making it convenient for actions that operate without additional parameters. + + Example: + + This example demonstrates how to use the decorator to create a custom action within a model view. + The ``empty_action`` method becomes part of the generated view and expects no input parameters. + + .. sourcecode:: python + + from vstutils.models import BModel + from vstutils.models.decorators import register_view_action + from vstutils.api.responses import HTTP_200_OK + + + class MyModel(BModel): + # ... model fields ... + + @register_view_action(detail=False, inherit=True) + def empty_action(self, request, *args, **kwargs): + # in this case `self` will be reference within the method points to the view object + return HTTP_200_OK('OK') """ __slots__ = () # type: ignore diff --git a/vstutils/settings.py b/vstutils/settings.py index 964c8d6b..af004f9f 100644 --- a/vstutils/settings.py +++ b/vstutils/settings.py @@ -1051,11 +1051,7 @@ def parse_db(params): 'vstutils.api.schema.renderers.OpenAPIRenderer', ], 'DEEP_LINKING': True, - 'SECURITY_DEFINITIONS': { - 'basic': { - 'type': 'basic' - } - }, + 'SECURITY_DEFINITIONS': {}, 'LOGIN_URL': 'login', 'LOGOUT_URL': 'logout', } From ff7dfed2c919a4250d5acfdf7113d6c0fb04face Mon Sep 17 00:00:00 2001 From: Sergei Kliuikov Date: Tue, 16 Jan 2024 16:36:33 +1000 Subject: [PATCH 13/20] Docs(frontend): Translate frontend docs. --- doc/locale/ru/LC_MESSAGES/backend.po | 1707 +++++++++++++---- doc/locale/ru/LC_MESSAGES/frontend-manual.po | 416 ++-- doc/locale/ru/LC_MESSAGES/quickstart-front.po | 265 +-- tox.ini | 2 +- vstutils/api/fields.py | 660 +++++-- vstutils/api/serializers.py | 40 +- 6 files changed, 2310 insertions(+), 780 deletions(-) diff --git a/doc/locale/ru/LC_MESSAGES/backend.po b/doc/locale/ru/LC_MESSAGES/backend.po index fd7719ce..3dc603b2 100644 --- a/doc/locale/ru/LC_MESSAGES/backend.po +++ b/doc/locale/ru/LC_MESSAGES/backend.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-11 05:57+0000\n" +"POT-Creation-Date: 2024-01-12 06:04+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -62,8 +62,9 @@ msgstr "" #: of vstutils.api.actions.Action:5 vstutils.api.actions.EmptyAction:9 #: vstutils.api.actions.SimpleAction:20 vstutils.api.base.ModelViewSet:31 -#: vstutils.api.fields.FkField:44 vstutils.models.BModel:162 -#: vstutils.models.BModel:187 vstutils.models.custom_model.FileModel:20 +#: vstutils.api.fields.DeepFkField:37 vstutils.api.fields.FkField:37 +#: vstutils.models.BModel:162 vstutils.models.BModel:187 +#: vstutils.models.custom_model.FileModel:20 #: vstutils.models.custom_model.ListModel:16 #: vstutils.models.custom_model.ListModel:35 msgid "Examples:" @@ -544,9 +545,21 @@ msgstr "" #: of vstutils.api.base.FileResponseRetrieveMixin:3 #: vstutils.api.decorators.nested_view:37 +#: vstutils.api.fields.AutoCompletionField:28 +#: vstutils.api.fields.Barcode128Field:24 +#: vstutils.api.fields.CommaMultiSelect:20 +#: vstutils.api.fields.DependFromFkField:54 +#: vstutils.api.fields.DynamicJsonTypeField:40 vstutils.api.fields.HtmlField:25 +#: vstutils.api.fields.MaskedField:23 +#: vstutils.api.fields.NamedBinaryFileInJsonField:32 +#: vstutils.api.fields.PhoneField:19 vstutils.api.fields.QrCodeField:27 +#: vstutils.api.fields.TextareaField:20 vstutils.api.fields.UptimeField:14 +#: vstutils.api.fields.WYSIWYGField:19 #: vstutils.api.filter_backends.DeepViewFilterBackend:25 #: vstutils.api.filter_backends.VSTFilterBackend:22 #: vstutils.api.filters.FkFilterHandler:15 +#: vstutils.api.serializers.BaseSerializer:12 +#: vstutils.api.serializers.VSTSerializer:10 #: vstutils.middleware.BaseMiddleware:38 #: vstutils.models.decorators.register_view_action:24 #: vstutils.tasks.TaskClass:4 vstutils.utils.BaseEnum:4 vstutils.utils.Lock:35 @@ -685,6 +698,7 @@ msgstr "is_sliced (bool): Булево значение, указывающее, #: vstutils.api.fields.FileInStringField vstutils.api.fields.FkField #: vstutils.api.fields.FkModelField vstutils.api.fields.MaskedField #: vstutils.api.fields.MultipleNamedBinaryImageInJsonField +#: vstutils.api.fields.NamedBinaryFileInJsonField #: vstutils.api.fields.NamedBinaryImageInJsonField #: vstutils.api.fields.QrCodeField vstutils.api.fields.RatingField #: vstutils.api.fields.RedirectFieldMixin vstutils.api.fields.RelatedListField @@ -1103,72 +1117,113 @@ msgstr "" #: of vstutils.api.fields.AutoCompletionField:1 msgid "" -"Field that provides autocompletion on frontend, using specified list of " -"objects." +"Serializer field that provides autocompletion on the frontend, using a " +"specified list of objects." msgstr "" -"Поле, предоставляющее автодополнение на фронтенде. Использует указанный " -"список объектов." +"Поле сериализатора, обеспечивающее автодополнение на фронтенде с " +"использованием указанного списка объектов." #: of vstutils.api.fields.AutoCompletionField:3 msgid "" -"Autocompletion reference. You can set list/tuple with values or set " -"OpenAPI schema definition name. For definition name GUI will find optimal" -" link and will show values based on ``autocomplete_property`` and " -"``autocomplete_represent`` arguments." +"Autocompletion reference. You can set a list or tuple with values or " +"specify the name of an OpenAPI schema definition. For a definition name, " +"the GUI will find the optimal link and display values based on the " +"``autocomplete_property`` and ``autocomplete_represent`` arguments." msgstr "" -"Источник для автодополнения. Можно задать список/кортеж со значениями или" -" definition name в схеме OpenAPI. Для definition пользовательский " -"интерфейс найдет оптимальную ссылку и отобразит значения, основанные на " -"аргументах ``autocomplete_property`` и ``autocomplete_represent``." +"Ссылка для автодополнения. Можно установить список или кортеж с " +"значениями или указать имя определения схемы OpenAPI. Для имени " +"определения, GUI найдет оптимальную ссылку и отобразит значения на основе" +" аргументов ``autocomplete_property`` и ``autocomplete_represent``." #: of vstutils.api.fields.AutoCompletionField:9 msgid "" -"this argument indicates which attribute will be get from OpenAPI schema " -"definition model as value." +"Specifies which attribute to retrieve from the OpenAPI schema definition " +"model as the value. Default is 'id'." msgstr "" -"этот аргумент указывает, какие атрибуты будут взяты из model definition " -"схемы OpenAPI в качестве используемого значения." +"Указывает, какой атрибут из модели определения схемы OpenAPI использовать" +" в качестве значения. По умолчанию 'id'." -#: of vstutils.api.fields.AutoCompletionField:12 +#: of vstutils.api.fields.AutoCompletionField:13 msgid "" -"this argument indicates which attribute will be get from OpenAPI schema " -"definition model as represent value." +"Specifies which attribute to retrieve from the OpenAPI schema definition " +"model as the representational value. Default is 'name'." msgstr "" -"этот аргумент указывает, какие атрибуты будут взяты из model definition " -"схемы OpenAPI в качестве отображаемого значения." +"Указывает, какой атрибут из модели определения схемы OpenAPI использовать" +" в качестве представленного значения. По умолчанию 'name'." -#: of vstutils.api.fields.AutoCompletionField:14 vstutils.api.fields.FkField:16 -#: vstutils.api.fields.FkModelField:14 -msgid "prefetch values on frontend at list-view. Default is ``True``." +#: of vstutils.api.fields.AutoCompletionField:16 +msgid "Prefetch values on the frontend in list view. Default is ``True``." msgstr "" -"делает prefetch для значений на фронтенде в list-view. ``True`` по " -"умолчанию." +"Загружать значения на фронтенде в режиме просмотра списка. Значение по " +"умолчанию — ``True``." + +#: of vstutils.api.fields.AutoCompletionField:20 +#: vstutils.api.fields.BinFileInStringField:10 +msgid "" +"This functionality is effective only in the GUI. In the API, it behaves " +"similarly to :class:`.VSTCharField`." +msgstr "" +"Эта функциональность работает только в графическом интерфейсе. В API она " +"ведет себя так же, как и :class:`.VSTCharField`." -#: of vstutils.api.fields.AutoCompletionField:18 -#: vstutils.api.fields.CommaMultiSelect:22 vstutils.api.fields.HtmlField:7 -#: vstutils.api.fields.NamedBinaryFileInJsonField:18 -#: vstutils.api.fields.TextareaField:4 -msgid "Effective only in GUI. Works similar to :class:`.VSTCharField` in API." +#: of vstutils.api.fields.AutoCompletionField:45 +msgid "Usage:" +msgstr "Использование:" + +#: of vstutils.api.fields.AutoCompletionField:23 +msgid "" +"This field is designed to be used in serializers where a user needs to " +"input a value, and autocompletion based on a predefined list or an " +"OpenAPI schema definition is desired. If an OpenAPI schema is specified, " +"two additional parameters, ``autocomplete_property`` and " +"``autocomplete_represent``, can be configured to customize the appearance" +" of the dropdown list." msgstr "" -"Действует только в графическом интерфейсе. Работает аналогично " -":class:`.VSTCharField` в API." +"Это поле предназначено для использования в сериализаторах, где " +"пользователь должен ввести значение, и требуется автодополнение на основе" +" предопределенного списка или определения схемы OpenAPI. Если указана " +"схема OpenAPI, два дополнительных параметра, ``autocomplete_property`` и " +"``autocomplete_represent``, могут быть настроены для настройки внешнего " +"вида выпадающего списка." #: of vstutils.api.fields.Barcode128Field:1 msgid "" -"Simple string field. Value must always be a valid ASCII-string. The field" -" is going to represent as Barcode (Code 128) in user interface." +"A field for representing data as a Barcode (Code 128) in the user " +"interface." msgstr "" -"Простое строковое поле. Значение всегда должно быть допустимой строкой " -"ASCII. В пользовательском интерфейсе оно будет отображено как штрихкод " -"(код 128)." +"Поле для представления данных в виде штрихкода (Code 128) в " +"пользовательском интерфейсе." -#: of vstutils.api.fields.Barcode128Field:4 vstutils.api.fields.QrCodeField:4 +#: of vstutils.api.fields.Barcode128Field:3 msgid "" -"original data field for serialization or deserialization. Default: " +"This field accepts and validates data in the form of a valid ASCII " +"string. It is designed to display the data as a Code 128 barcode in the " +"graphical user interface. The underlying data is serialized or " +"deserialized using the specified child field." +msgstr "" +"Это поле принимает и проверяет данные в виде допустимой ASCII-строки. Оно" +" предназначено для отображения данных в виде штрихкода Code 128 в " +"графическом пользовательском интерфейсе. Основные данные сериализуются " +"или десериализуются с использованием указанного дочернего поля." + +#: of vstutils.api.fields.Barcode128Field:7 vstutils.api.fields.QrCodeField:6 +msgid "" +"The original data field for serialization or deserialization. Default: " +":class:`rest_framework.fields.CharField`" +msgstr "" +"Исходное поле данных для сериализации или десериализации. По умолчанию: " ":class:`rest_framework.fields.CharField`" + +#: of vstutils.api.fields.Barcode128Field:12 +msgid "" +"Suppose you have a model with a `product_code` field, and you want to " +"display its Code 128 barcode representation in the GUI. You can use " +"`Barcode128Field` in your serializer:" msgstr "" -"поле для сериализации или десериализации оригинальных данных.По " -"умолчанию: :class:`rest_framework.fields.CharField`" +"Предположим, у вас есть модель с полем `product_code`, и вы хотите " +"отображать его представление в виде штрихкода Code 128 в графическом " +"интерфейсе пользователя. Вы можете использовать `Barcode128Field` в своем" +" сериализаторе:" #: of vstutils.api.fields.BinFileInStringField:1 msgid "" @@ -1183,7 +1238,6 @@ msgstr "" "бинарные файлы от пользователя, которые затем конвертируются в строку в " "формате base64 и сохраняются в данном поле." - #: of vstutils.api.fields.BinFileInStringField:5 msgid "" "A list of MIME types to filter on the user's side. Supports the use of " @@ -1192,14 +1246,6 @@ msgstr "" "Список MIME-типов, доступных для выбора пользователем. Поддерживается " "синтаксис с использованием ``*``. По умолчанию ``['*/*']``" -#: of vstutils.api.fields.BinFileInStringField:10 -msgid "" -"This functionality is effective only in the GUI. In the API, it behaves " -"similarly to :class:`.VSTCharField`." -msgstr "" -"Действует только в графическом интерфейсе. В API ведет себя так же, как и" -" :class:`.VSTCharField`." - #: of vstutils.api.fields.CSVFileField:1 msgid "" "Field extends :class:`.FileInStringField`, using for works with csv " @@ -1284,55 +1330,65 @@ msgstr "" #: of vstutils.api.fields.CommaMultiSelect:1 msgid "" -"Field containing a list of values with specified separator (default: " -"\",\"). Gets list of values from another model or custom list. Provides " -"autocompletion as :class:`.AutoCompletionField`, but with comma-lists. " -"Suited for property-fields in model where main logic is already " -"implemented or with :class:`model.CharField`." -msgstr "" -"Поле, содержащее список значений с указанным разделителем (по умолчанию " -"\",\"). Получает список значений из другой модели или списка. " -"Предоставляет автодополнение, как и :class:`.AutoCompletionField`, но со " -"списками в виде строки, где слова разделяются запятыми. Подходит для " -"полей-свойств модели, где уже реализована основная логика, или для поля " +"Field that allows users to input multiple values, separated by a " +"specified delimiter (default: \",\"). It retrieves a list of values from " +"another model or a custom list and provides autocompletion similar to " +":class:`.AutoCompletionField`. This field is suitable for property fields" +" in a model where the main logic is already implemented or for use with " ":class:`model.CharField`." +msgstr "" +"Поле, позволяющее пользователям вводить несколько значений, разделенных " +"указанным разделителем (по умолчанию \",\"). Извлекает список значений из" +" другой модели или пользовательского списка и предоставляет " +"автодополнение аналогично :class:`.AutoCompletionField`. Это поле " +"подходит для полей-свойств модели, где основная логика уже реализована, " +"или для использования с :class:`model.CharField`." + +#: of vstutils.api.fields.CommaMultiSelect:5 +msgid "OpenAPI schema definition name or a list with values." +msgstr "Имя определения схемы OpenAPI или список со значениями." #: of vstutils.api.fields.CommaMultiSelect:7 -msgid "OpenAPI schema definition name or list with values." -msgstr "Определение имени схемы OpenAPI или списка со значениями." +msgid "The separator for values. The default is a comma." +msgstr "Разделитель значений. По умолчанию - запятая." #: of vstutils.api.fields.CommaMultiSelect:9 -msgid "separator of values. Default is comma." -msgstr "разделитель значений. По умолчанию запятая." +msgid "" +"These parameters function similarly to ``autocomplete_property`` and " +"``autocomplete_represent``. The default is ``name``." +msgstr "" +"Эти параметры работают аналогично ``autocomplete_property`` и " +"``autocomplete_represent``. По умолчанию - ``name``." #: of vstutils.api.fields.CommaMultiSelect:11 -msgid "" -"work as ``autocomplete_property`` and ``autocomplete_represent``. Default" -" is ``name``." +msgid "Prefetch values on the frontend in list view. The default is ``False``." msgstr "" -"работает так же, как ``autocomplete_property`` и " -"``autocomplete_represent`` По умолчанию ``name``." +"Загружать значения на фронтенде в режиме просмотра списка. Значение по " +"умолчанию - ``False``." #: of vstutils.api.fields.CommaMultiSelect:13 -msgid "prefetch values on frontend at list-view. Default is ``False``." -msgstr "делает prefetch значений на фронтенде в list-view. По умолчанию ``False``." - -#: of vstutils.api.fields.CommaMultiSelect:14 -#: vstutils.api.fields.FkModelField:15 -msgid "Show value as link to model. Default is ``True``." -msgstr "Отображает значение как ссылку на модель. По умолчанию ``True``." +msgid "Show values as links to the model. The default is ``True``." +msgstr "Отображать значения как ссылки на модель. По умолчанию - ``True``." #: of vstutils.api.fields.CommaMultiSelect:15 msgid "" -"Dictionary, where keys are name of field from the same model, and values " -"are name of query filter. If at least one of the fields that we depend on" -" is non nullable, required and set to null, autocompletion list will be " -"empty and field will be disabled." +"A dictionary where keys are the names of fields from the same model, and " +"values are the names of query filters. If at least one of the fields we " +"depend on is non-nullable, required, and set to null, the autocompletion " +"list will be empty, and the field will be disabled." msgstr "" "Словарь, где ключи - это имена полей из той же модели, а значения - " "названия query-фильтров. Если хотя бы одно из полей, от которых " -"существует зависимость не допускает null, обязательно или установлено в " -"null, список автодополнения будет пустым, а поле окажется выключенным." +"существует зависимость, не допускает null, обязательно или установлено в " +"null, список автодополнения будет пустым, и поле будет отключено." + +#: of vstutils.api.fields.CommaMultiSelect:43 +msgid "" +"This functionality is effective only in the GUI and works similarly to " +":class:`.VSTCharField` in the API." +msgstr "" +"Эта функциональность работает только в графическом интерфейсе. В API она " +"ведет себя так же, как и :class:`.VSTCharField`." #: of vstutils.api.fields.CrontabField:1 msgid "" @@ -1413,30 +1469,96 @@ msgid "Default value of each field if not specified is ``*``." msgstr "Значение по умолчанию для каждого поля, если не указано, составляет" #: of vstutils.api.fields.DeepFkField:1 -msgid "Extends :class:`.FkModelField`, but displays as tree on frontend." +msgid "" +"Extends :class:`.FkModelField`, specifically designed for hierarchical " +"relationships in the frontend." msgstr "" -"Расширяет :class:`.FkModelField`, но отображается в виде дерева на " -"фронтенде." +"Расширяет :class:`.FkModelField`, специально разработанный для " +"иерархических отношений на фронтенде." -#: of vstutils.api.fields.DeepFkField:4 +#: of vstutils.api.fields.DeepFkField:3 msgid "" -"This field does not support ``dependence``. Use ``filters`` at your own " -"risk, as it would rather break the tree structure." +"This field is intended for handling ForeignKey relationships within a " +"hierarchical or tree-like structure. It displays as a tree in the " +"frontend, offering a clear visual representation of parent-child " +"relationships." msgstr "" -"Это поле не поддерживает ``dependence``. Используйте ``filters`` на свой " -"страх и риск, так как они могут сломать структуру дерева." +"Это поле предназначено для работы с отношениями ForeignKey в " +"иерархической или древовидной структуре. Оно отображается в виде дерева " +"на фронтенде, предоставляя четкое визуальное представление отношений " +"родитель-ребенок." -#: of vstutils.api.fields.DeepFkField:8 +#: of vstutils.api.fields.DeepFkField:7 msgid "" -"if True then only allows a value to be selected if it has no children. " -"Default is `False`" +"This field intentionally does not support the ``dependence`` parameter, " +"as it operates in a tree structure. Usage of ``filters`` should be " +"approached with caution, as inappropriate filters may disrupt the tree " +"hierarchy." msgstr "" -"если True, то допускает к выбору только то значение, которое не имеет " -"дочерних элементов. По умолчанию `False`" +"Это поле специально не поддерживает параметр ``dependence``, так как " +"работает в структуре дерева. Использование параметра ``filters`` следует " +"приглядеться с осторожностью, поскольку неправильные фильтры могут " +"нарушить иерархию дерева." #: of vstutils.api.fields.DeepFkField:11 -msgid "name of parent field in model. Default is `parent`" -msgstr "название родительского поля в модели. По умолчанию `parent`" +msgid "" +"If True, the field restricts the selection to nodes without children. " +"Default is `False`. Useful when you want to enforce selection of leaf " +"nodes." +msgstr "" +"Если True, поле ограничит выбор узлов без детей. Значение по умолчанию - " +"`False`. Полезно, когда вы хотите обеспечить выбор листовых узлов." + +#: of vstutils.api.fields.DeepFkField:15 +msgid "" +"The name of the parent field in the related model. Default is `parent`. " +"Should be set to the ForeignKey field in the related model, representing " +"the parent-child relationship. For example, if your related model has a " +"ForeignKey like `parent = models.ForeignKey('self', ...)`, then " +"`parent_field_name` should be set to `'parent'`." +msgstr "" +"Имя поля родителя в связанной модели. Значение по умолчанию - `parent`. " +"Должно быть установлено в поле ForeignKey в связанной модели, " +"представляющее отношения родитель-ребенок. Например, если у вашей " +"связанной модели есть ForeignKey, например, `parent = " +"models.ForeignKey('self', ...)`, то `parent_field_name` должно быть " +"установлено в `'parent'`." + +#: of vstutils.api.fields.DeepFkField:24 +msgid "" +"Consider a related model with a ForeignKey field representing parent-" +"child relationships:" +msgstr "" +"Предположим, у вас есть связанная модель с полем ForeignKey, " +"представляющим отношения родитель-ребенок:" + +#: of vstutils.api.fields.DeepFkField:32 +msgid "" +"To use the DeepFkField with this related model in a serializer, you would" +" set the parent_field_name to 'parent':" +msgstr "" +"Чтобы использовать DeepFkField с этой связанной моделью в сериализаторе, " +"вы установили бы `parent_field_name` в 'parent':" + +#: of vstutils.api.fields.DeepFkField:39 +msgid "" +"This example assumes a Category related model with a ForeignKey 'parent' " +"field. The DeepFkField will then display the categories as a tree " +"structure in the frontend, providing an intuitive selection mechanism for" +" hierarchical relationships." +msgstr "" +"В этом примере предполагается, что у вас есть связанная модель Category с" +" полем ForeignKey 'parent'. Затем DeepFkField отобразит категории в виде " +"дерева на фронтенде, предоставляя интуитивно понятный механизм выбора для" +" иерархических отношений." + +#: of vstutils.api.fields.DeepFkField:44 vstutils.api.fields.UptimeField:26 +msgid "" +"Effective only in GUI. Works similarly to " +":class:`rest_framework.fields.IntegerField` in API." +msgstr "" +"Действует только в графическом интерфейсе. Работает аналогично " +":class:`rest_framework.fields.IntegerField` в API." #: of vstutils.api.fields.DependEnumField:1 msgid "" @@ -1449,15 +1571,12 @@ msgstr "" ":class:`property` в модели или для экшенов." #: of vstutils.api.fields.DependEnumField:4 -#: vstutils.api.fields.DynamicJsonTypeField:4 msgid "field in model which value change will change type of current value." msgstr "" "поле в модели, изменение значения которого будет менять тип текущего " "значения." #: of vstutils.api.fields.DependEnumField:6 -#: vstutils.api.fields.DependFromFkField:13 -#: vstutils.api.fields.DynamicJsonTypeField:6 msgid "" "key-value mapping where key is value of subscribed field and value is " "type (in OpenAPI format) of current field." @@ -1466,7 +1585,6 @@ msgstr "" "поля-подписчика, а значением - тип (формата OpenAPI) текущего поля." #: of vstutils.api.fields.DependEnumField:9 -#: vstutils.api.fields.DynamicJsonTypeField:9 msgid "" "variants of choices for different subscribed field values. Uses mapping " "where key is value of subscribed field and value is list with values to " @@ -1486,85 +1604,149 @@ msgstr "" #: of vstutils.api.fields.DependFromFkField:1 msgid "" -"Field extends :class:`DynamicJsonTypeField`. Validates field data by " -":attr:`.field_attribute` chosen in related model. By default, any value " -"of :attr:`.field_attribute` validates as :class:`.VSTCharField`. To " -"override this behavior set dict attribute ``{field_attribute " -"value}_fields_mapping`` in related model where:" +"A specialized field that extends :class:`DynamicJsonTypeField` and " +"validates field data based on a :attr:`.field_attribute` chosen in a " +"related model. The field data is validated against the type defined by " +"the chosen value of :attr:`.field_attribute`." msgstr "" -"Поле, расширяющее :class:`DynamicJsonTypeField`. Валидирует данные поля с" -" помощью :attr:`.field_attribute`, выбранного в связанной модели. По " -"умолчанию любое значение :attr:`.field_attribute` валидируется классом " -":class:`.VSTCharField`. Чтобы переопределить это поведение, установите " -"словарный атрибут ``{field_attribute value}_fields_mapping`` в связанной " -"модели, где:" +"Специализированное поле, расширяющее :class:`DynamicJsonTypeField` и " +"проверяющее данные поля на основе :attr:`.field_attribute`, выбранного в " +"связанной модели. Данные поля проверяются на соответствие типу, " +"определенному выбранным значением :attr:`.field_attribute`." -#: of vstutils.api.fields.DependFromFkField:5 +#: of vstutils.api.fields.DependFromFkField:7 msgid "" -"**key** - string representation of value type which is received from " -"related instance :attr:`.field_attribute`." +"By default, any value of :attr:`.field_attribute` validates as " +":class:`.VSTCharField`. To override this behavior, set the class " +"attribute ``{field_attribute}_fields_mapping`` in the related model. The " +"attribute should be a dictionary where keys are string representations of" +" the values of :attr:`.field_attribute`, and values are instances of " +":class:`rest_framework.Field` for validation. If a value is not found in " +"the dictionary, the default type will be :class:`.VSTCharField`." msgstr "" -"**key** - строковое представление типа значения, получаемое от связанной " -"модели :attr:`.field_attribute`." +"По умолчанию любое значение :attr:`.field_attribute` проверяется как " +":class:`.VSTCharField`. Чтобы изменить это поведение, установите атрибут " +"класса ``{field_attribute}_fields_mapping`` в связанной модели. Атрибут " +"должен быть словарем, где ключи - это строковые представления значений " +":attr:`.field_attribute`, а значения - экземпляры " +":class:`rest_framework.Field` для проверки. Если значение не найдено в " +"словаре, то тип по умолчанию будет :class:`.VSTCharField`." -#: of vstutils.api.fields.DependFromFkField:6 -msgid "**value** - :class:`rest_framework.Field` instance for validation." -msgstr "**value** - экземпляр :class:`rest_framework.Field` для валидации." - -#: of vstutils.api.fields.DependFromFkField:8 +#: of vstutils.api.fields.DependFromFkField:14 msgid "" -"field in model which value change changes type of current value. Field " -"must be :class:`.FkModelField`." +"The field in the model whose value change determines the type of the " +"current value. The field must be of type :class:`.FkModelField`." msgstr "" -"поле в модели, чье изменение значения меняет тип текущего значения. Поле " -"должно быть классом :class:`.FkModelField`." +"Поле в модели, значение которого определяет тип текущего значения. Поле " +"должно быть типа :class:`.FkModelField`." -#: of vstutils.api.fields.DependFromFkField:11 -msgid "attribute of related model instance with name of type." -msgstr "атрибут связанного экземпляра модели с именем типа." +#: of vstutils.api.fields.DependFromFkField:17 +msgid "" +"The attribute of the related model instance containing the name of the " +"type." +msgstr "Атрибут экземпляра связанной модели, содержащий имя типа." -#: of vstutils.api.fields.DependFromFkField:18 +#: of vstutils.api.fields.DependFromFkField:19 +#: vstutils.api.fields.DynamicJsonTypeField:6 msgid "" -"``field_attribute`` in related model must be " -":class:`rest_framework.fields.ChoicesField` or GUI will show field as " -"simple text." +"A key-value mapping where the key is the value of the subscribed field, " +"and the value is the type (in OpenAPI format) of the current field." +msgstr "" +"Отображение ключ-значение, где ключ - это значение подписанного поля, а " +"значение - тип (в формате OpenAPI) текущего поля." + +#: of vstutils.api.fields.DependFromFkField:24 +msgid "" +"The ``field_attribute`` in the related model must be of type " +":class:`rest_framework.fields.ChoicesField` to ensure proper functioning " +"in the GUI; otherwise, the field will be displayed as simple text." msgstr "" "``field_attribute`` в связанной модели должно быть типа " ":class:`rest_framework.fields.ChoicesField`, иначе в графическом " "интерфейсе поле будет отображаться как обычное текстовое." +#: of vstutils.api.fields.DependFromFkField:29 +msgid "" +"Suppose you have a model with a ForeignKey field `related_model` and a " +"field `type_attribute` in `RelatedModel` that determines the type of " +"data. You can use `DependFromFkField` to dynamically adapt the " +"serialization based on the value of `type_attribute`:" +msgstr "" +"Предположим, у вас есть модель с полем ForeignKey `related_model` и полем" +" `type_attribute` в `RelatedModel`, которое определяет тип данных. Вы " +"можете использовать `DependFromFkField` для динамической адаптации " +"сериализации на основе значения `type_attribute`:" + #: of vstutils.api.fields.DynamicJsonTypeField:1 msgid "" -"Field which type is based on another field. It converts value to internal" -" string and represent field as json object." +"A versatile serializer field that dynamically adapts its type based on " +"the value of another field in the model. It facilitates complex scenarios" +" where the type of data to be serialized depends on the value of a " +"related field." msgstr "" -"Поле, тип которого зависит от другого поля. Хранит значение в виде " -"строки, а отображает поле в виде объекта json." +"Универсальное поле сериализатора, которое динамически адаптирует свой тип" +" в зависимости от значения другого поля в модели. Оно облегчает сложные " +"сценарии, где тип данных для сериализации зависит от значения связанного " +"поля." + +#: of vstutils.api.fields.DynamicJsonTypeField:4 +msgid "" +"The field in the model whose value change will dynamically determine the " +"type of the current field." +msgstr "" +"Поле в модели, изменение значения которого будет динамически определять " +"тип текущего поля." + +#: of vstutils.api.fields.DynamicJsonTypeField:9 +msgid "" +"Variants of choices for different subscribed field values. Uses a mapping" +" where the key is the value of the subscribed field, and the value is a " +"list with values to choose from." +msgstr "" +"Варианты выбора для разных значений подписанных полей. Использует " +"отображение, где ключ - это значение подписанного поля, а значение - " +"список значений для выбора." #: of vstutils.api.fields.DynamicJsonTypeField:13 msgid "" -"Allows to use parent views data as source for field creation. Exact view " -"path (`/user/{id}/`) or relative parent specifier " -"(`<>.<>.<>`) can be provided. For example if " -"current page is `/user/1/role/2/` and `source_view` is " -"`<>.<>` then data from `/user/1/` will be used. Only " -"detail views if supported." +"Allows using parent views data as a source for field creation. The exact " +"view path (`/user/{id}/`) or relative parent specifier " +"(`<>.<>.<>`) can be provided. For example, if the" +" current page is `/user/1/role/2/` and `source_view` is " +"`<>.<>`, then data from `/user/1/` will be used. Only " +"detail views are supported." msgstr "" -"Позволяет использовать данные родительских view в качестве источника для " -"создания полей. Можно указать точный путь представления (`/user/{id}/`) " -"или относительный спецификатор родительского представления " +"Позволяет использовать данные родительских представлений в качестве " +"источника для создания поля. Можно указать точный путь представления " +"(`/user/{id}/`) или относительный указатель родителя " "(`<>.<>.<>`). Например, если текущая страница - " "`/user/1/role/2/`, а `source_view` - `<>.<>`, то будут " -"использованы данные из `/user/1/`. Поддерживаются только представления " -"деталей." +"использованы данные из `/user/1/`. Поддерживаются только детальные " +"представления." #: of vstutils.api.fields.DynamicJsonTypeField:22 msgid "" -"Effective only in GUI. In API works similar to :class:`.VSTCharField` " -"without value modifications." +"Suppose you have a serializer `MySerializer` with a `field_type` (e.g., a" +" `ChoiceField`) and a corresponding `object_data`. The `object_data` " +"field can have different types based on the value of `field_type`. Here's" +" an example configuration:" msgstr "" -"Действует только в графическом интерфейсе. В API работает аналогично " -":class:`.VSTCharField` без изменения значения." +"Предположим, у вас есть сериализатор `MySerializer` с полем `field_type` " +"(например, `ChoiceField`) и соответствующим полем `object_data`. Поле " +"`object_data` может иметь разные типы в зависимости от значения " +"`field_type`. Вот пример конфигурации:" + +#: of vstutils.api.fields.DynamicJsonTypeField:40 +msgid "" +"In this example, the `object_data` field dynamically adapts its type " +"based on the selected value of `field_type`. The `types` argument defines" +" different types for each possible value of `field_type`, allowing for " +"flexible and dynamic serialization." +msgstr "" +"В этом примере поле `object_data` динамически адаптирует свой тип в " +"зависимости от выбранного значения `field_type`. Аргумент `types` " +"определяет разные типы для каждого возможного значения `field_type`, " +"позволяя гибкую и динамичную сериализацию." #: of vstutils.api.fields.FileInStringField:1 msgid "" @@ -1599,151 +1781,164 @@ msgstr "" #: of vstutils.api.fields.FkField:1 msgid "" -"Implementation of ForeignKeyField. You can specify which field of a " -"related model will be stored in field (default: \"id\"), and which will " -"represent field on frontend." +"An implementation of ForeignKeyField, designed for use in serializers. " +"This field allows you to specify which field of a related model will be " +"stored in the field (default: \"id\"), and which field will represent the" +" value on the frontend." msgstr "" -"Реализация ForeignKeyField. Вы можете указать, какое поле связанной " -"модели будет храниться в этом поле (по умолчанию: \"id\") и какое будет " -"отображаться на фронтенде." +"Реализация ForeignKeyField, предназначенная для использования в " +"сериализаторах. Это поле позволяет указать, какое поле связанной модели " +"будет храниться в этом поле (по умолчанию: \"id\"), а какое поле будет " +"представлять значение на фронтенде." -#: of vstutils.api.fields.FkField:4 +#: of vstutils.api.fields.FkField:5 msgid "OpenAPI schema definition name." msgstr "Имя определения схемы OpenAPI." -#: of vstutils.api.fields.FkField:6 vstutils.api.fields.FkModelField:7 +#: of vstutils.api.fields.FkField:7 msgid "" -"this argument indicates which attribute will be get from OpenAPI schema " -"definition model as value. Default is ``id``." +"Specifies which attribute will be retrieved from the OpenAPI schema " +"definition model as the value. Default is ``id``." msgstr "" -"этот аргумент указывает, какие атрибуты будут взяты из model definition " -"схемы OpenAPI в качестве используемого значения. По умолчанию ``id``." +"Указывает, какой атрибут из модели определения схемы OpenAPI использовать" +" в качестве значения. По умолчанию 'id'." -#: of vstutils.api.fields.FkField:10 vstutils.api.fields.FkModelField:11 +#: of vstutils.api.fields.FkField:10 msgid "" -"this argument indicates which attribute will be get from OpenAPI schema " -"definition model as represent value. Default is ``name``." +"Specifies which attribute will be retrieved from the OpenAPI schema " +"definition model as the representational value. Default is ``name``." msgstr "" -"этот аргумент указывает, какие атрибуты будут взяты из model definition " -"схемы OpenAPI в качестве отображаемого значения. По умолчанию ``name``." +"Указывает, какой атрибут из модели определения схемы OpenAPI использовать" +" в качестве представленного значения. По умолчанию 'name'." -#: of vstutils.api.fields.FkField:13 +#: of vstutils.api.fields.FkField:12 msgid "" -"defines the autocomplete_property type for further definition in the " -"schema and casting to the type from the api. Default is passthroughs but " -"require `int` or `str` objects." +"Defines the type of the autocomplete_property for further definition in " +"the schema and casting to the type from the API. Default is passthrough " +"but requires `int` or `str` objects." msgstr "" "Определяет тип поля autocomplete_property для дальнейшего описания в " "схеме и преобразования этого типа из API. По умолчанию пропускается, но " -"требуект объекты `int`или `str`." +"требует объекты `int` или `str`." -#: of vstutils.api.fields.FkField:18 -msgid "show value as link to model. Default is ``True``." -msgstr "Отображает значение как ссылку на модель. По умолчанию ``True``." +#: of vstutils.api.fields.FkField:15 +msgid "Prefetch values on the frontend at list-view. Default is ``True``." +msgstr "" +"Загружать значения на фронтенде в режиме просмотра списка. Значение по " +"умолчанию — ``True``." + +#: of vstutils.api.fields.FkField:17 +msgid "Show the value as a link to the related model. Default is ``True``." +msgstr "" +"Отображать значение как ссылку на модель. Значение по умолчанию — " +"``True``." -#: of vstutils.api.fields.FkField:20 +#: of vstutils.api.fields.FkField:19 msgid "" -"dictionary, where keys are names of a field from the same model, and " -"values are names of query filter. If at least one of the fields that we " -"depend on is non nullable, required and set to null, autocompletion list " -"will be empty and field will be disabled. There are some special keys " -"for dependence dictionary to get data that is stored on frontend without " -"additional database query: ``'<>'`` gets primary key of current " -"instance, ``'<>'`` gets view name from Vue component, " -"``'<>'`` gets parent view name from Vue component, " -"``'<>'`` gets view level, ``'<>'`` gets " -"operation_id, ``'<>`` gets parent_operation_id." +"A dictionary where keys are names of fields from the same model, and " +"values are names of query filters. If at least one of the fields that we " +"depend on is non-nullable, required, and set to null, the autocompletion " +"list will be empty, and the field will be disabled. There are some " +"special keys for the dependence dictionary to get data that is stored on " +"the frontend without additional database query: - ``'<>'`` gets the " +"primary key of the current instance, - ``'<>'`` gets the view " +"name from the Vue component, - ``'<>'`` gets the parent" +" view name from the Vue component, - ``'<>'`` gets the view " +"level, - ``'<>'`` gets the operation_id, - " +"``'<>`` gets the parent_operation_id." msgstr "" -"словарь, где ключи - это имена полей из той же модели, а значения - " +"Словарь, где ключи - это имена полей из той же модели, а значения - " "названия query-фильтров. Если хотя бы одно из полей, от которых " "существует зависимость, не допускает null, обязательно или установлено в " -"null, список автодополнения будет пустым, а поле окажется выключенным. " -"Есть несколько специальных ключей dependence-словаря, с помощью которых " -"можно получить данные, хранящиеся на фронтенде, не делая лишних запросов " -"в базу данных: ``'<>'`` получает первичный ключ текущего экземпляра," -" ``'<>'`` получает имя view из компонента Vue, " +"null, список автодополнения будет пустым, а поле будет отключено. Есть " +"несколько специальных ключей dependence-словаря, с помощью которых можно " +"получить данные, хранящиеся на фронтенде, не делая лишних запросов в базу" +" данных: ``'<>'`` получает первичный ключ текущего экземпляра, " +"``'<>'`` получает имя view из компонента Vue, " "``'<>'`` получает имя родительского view из компонента " "Vue, ``'<>'`` получает уровень view, ``'<>'``" -" получает operation_id, ``'<>`` получает " +" получает operation_id, ``'<>`` получает " "родительский operation_id." -#: of vstutils.api.fields.FkField:20 +#: of vstutils.api.fields.FkField:19 msgid "" -"dictionary, where keys are names of a field from the same model, and " -"values are names of query filter. If at least one of the fields that we " -"depend on is non nullable, required and set to null, autocompletion list " -"will be empty and field will be disabled." +"A dictionary where keys are names of fields from the same model, and " +"values are names of query filters. If at least one of the fields that we " +"depend on is non-nullable, required, and set to null, the autocompletion " +"list will be empty, and the field will be disabled." msgstr "" -"словарь, где ключи - это имена полей из той же модели, а значения - " +"Словарь, где ключи - это имена полей из той же модели, а значения - " "названия query-фильтров. Если хотя бы одно из полей, от которых " -"существует зависимость не допускает null, обязательно или установлено в " -"null, список автодополнения будет пустым, а поле окажется выключенным." +"существует зависимость, не допускает null, обязательно или установлено в " +"null, список автодополнения будет пустым, и поле будет отключено." -#: of vstutils.api.fields.FkField:25 +#: of vstutils.api.fields.FkField:23 msgid "" -"There are some special keys for dependence dictionary to get data that is" -" stored on frontend without additional database query:" +"There are some special keys for the dependence dictionary to get data " +"that is stored on the frontend without additional database query:" msgstr "" "Есть несколько специальных ключей dependence-словаря, с помощью которых " "можно получить данные, хранящиеся на фронтенде, не делая лишних запросов " "в базу данных:" -#: of vstutils.api.fields.FkField:28 -msgid "``'<>'`` gets primary key of current instance," +#: of vstutils.api.fields.FkField:26 +msgid "``'<>'`` gets the primary key of the current instance," msgstr "``'<>'`` получает первичный ключ текущего экземпляра," -#: of vstutils.api.fields.FkField:30 -msgid "``'<>'`` gets view name from Vue component," +#: of vstutils.api.fields.FkField:27 +msgid "``'<>'`` gets the view name from the Vue component," msgstr "``'<>'`` получает имя view из компонента Vue," -#: of vstutils.api.fields.FkField:32 -msgid "``'<>'`` gets parent view name from Vue component," +#: of vstutils.api.fields.FkField:28 +msgid "" +"``'<>'`` gets the parent view name from the Vue " +"component," msgstr "" "``'<>'`` получает имя родительского view из компонента " "Vue," -#: of vstutils.api.fields.FkField:34 -msgid "``'<>'`` gets view level," +#: of vstutils.api.fields.FkField:29 +msgid "``'<>'`` gets the view level," msgstr "``'<>'`` получает уровень view," -#: of vstutils.api.fields.FkField:36 -msgid "``'<>'`` gets operation_id," +#: of vstutils.api.fields.FkField:30 +msgid "``'<>'`` gets the operation_id," msgstr "``'<>'`` получает operation_id," -#: of vstutils.api.fields.FkField:38 -msgid "``'<>`` gets parent_operation_id." +#: of vstutils.api.fields.FkField:31 +msgid "``'<>`` gets the parent_operation_id." msgstr "``'<>`` получает родительский operation_id." -#: of vstutils.api.fields.FkField:46 +#: of vstutils.api.fields.FkField:39 msgid "" -"This filter will get pk of current object and make query on frontend " -"'/category?my_filter=3' where '3' is primary key of current instance." +"This filter will get the primary key of the current object and make a " +"query on the frontend ``/category?my_filter=3`` where ``3`` is the " +"primary key of the current instance." msgstr "" -"Этот фильтр получит первичный ключ текущего объекта и сделает запрос на " -"фронтенде '/category?my_filter=3', где '3' - первичный ключ текущего " +"Этот фильтр получит первичный ключ текущего объекта и выполнит запрос на " +"фронтенде ``/category?my_filter=3``, где ``3`` - первичный ключ текущего " "экземпляра." -#: of vstutils.api.fields.FkField:50 +#: of vstutils.api.fields.FkField:43 msgid "" -"dictionary, where keys are names of a field from a related (by this " -"FkField) model, and values are values of that field." +"A dictionary where keys are names of fields from a related model " +"(specified by this FkField), and values are values of that field." msgstr "" -"словарь, где ключи - это имена поля связанной модели, а значения - " -"значения этого поля." +"Словарь, где ключи - это имена полей из связанной модели (указанной в " +"этом FkField), а значения - значения этого поля." -#: of vstutils.api.fields.FkField:55 +#: of vstutils.api.fields.FkField:48 msgid "" -"Intersection of `dependence.values()` and `filters.keys()` will throw " -"error to prevent ambiguous filtering." +"The intersection of `dependence.values()` and `filters.keys()` will throw" +" an error to prevent ambiguous filtering." msgstr "" -"Пересечение `dependence.values()` и `filters.keys()` выкинет ошибку для " -"предотвращения неоднозначности при фильтрации." +"Пересечение `dependence.values()` и `filters.keys()` вызовет ошибку для " +"предотвращения неоднозначной фильтрации." -#: of vstutils.api.fields.FkField:57 vstutils.api.fields.RedirectCharField:4 -#: vstutils.api.fields.RedirectIntegerField:4 vstutils.api.fields.UptimeField:4 +#: of vstutils.api.fields.FkField:50 msgid "" -"Effective only in GUI. Works similar to " -":class:`rest_framework.fields.IntegerField` in API." +"Effective only in the GUI. Works similarly to " +":class:`rest_framework.fields.IntegerField` in the API." msgstr "" "Действует только в графическом интерфейсе. Работает аналогично " ":class:`rest_framework.fields.IntegerField` в API." @@ -1765,6 +1960,32 @@ msgstr "" "класс модели (основанный на :class:`vstutils.models.BModel`) или " "сериализатор, используемый в API и имеющий свой путь в схеме OpenAPI." +#: of vstutils.api.fields.FkModelField:7 +msgid "" +"this argument indicates which attribute will be get from OpenAPI schema " +"definition model as value. Default is ``id``." +msgstr "" +"этот аргумент указывает, какие атрибуты будут взяты из model definition " +"схемы OpenAPI в качестве используемого значения. По умолчанию ``id``." + +#: of vstutils.api.fields.FkModelField:11 +msgid "" +"this argument indicates which attribute will be get from OpenAPI schema " +"definition model as represent value. Default is ``name``." +msgstr "" +"этот аргумент указывает, какие атрибуты будут взяты из model definition " +"схемы OpenAPI в качестве отображаемого значения. По умолчанию ``name``." + +#: of vstutils.api.fields.FkModelField:14 +msgid "prefetch values on frontend at list-view. Default is ``True``." +msgstr "" +"делает prefetch для значений на фронтенде в list-view. ``True`` по " +"умолчанию." + +#: of vstutils.api.fields.FkModelField:15 +msgid "Show value as link to model. Default is ``True``." +msgstr "Отображает значение как ссылку на модель. По умолчанию ``True``." + #: of vstutils.api.fields.FkModelField:19 msgid "" "Model class get object from database during `.to_internal_value` " @@ -1784,35 +2005,81 @@ msgstr "" #: of vstutils.api.fields.HtmlField:1 msgid "" -"Field contains html text and marked as format:html. The field does not " -"validate whether its content is HTML." +"A specialized field for handling HTML text content, marked with the " +"format:html." +msgstr "" +"Специализированное поле для обработки HTML-текстового контента, " +"отмеченного форматом: html." + +#: of vstutils.api.fields.HtmlField:9 vstutils.api.fields.TextareaField:4 +msgid "" +"This field is designed for use in the graphical user interface (GUI) and " +"functions similarly to :class:`.VSTCharField` in the API." msgstr "" -"Поле, содержащее текст html и помеченное как format:html. Это поле не " -"проверяет, является ли его содержимое валидным HTML." +"Это поле предназначено для использования в графическом интерфейсе " +"пользователя (GUI) и работает аналогично :class:`.VSTCharField` в API." -#: of vstutils.api.fields.HtmlField:4 +#: of vstutils.api.fields.HtmlField:13 msgid "" -"To avoid vulnerability, do not allow users to modify this data because " -"users ate able to execute their scripts." +"If you have a model with an `html_content` field that stores HTML-" +"formatted text, you can use `HtmlField` in your serializer to handle this" +" content in the GUI:" msgstr "" -"Чтобы избежать уязвимости, не позволяйте пользователям изменять эти " -"данные, так как они могут выполнить нежелательный скрипт." +"Если у вас есть модель с полем `html_content`, в котором хранится " +"HTML-форматированный текст, вы можете использовать `HtmlField` в своем " +"сериализаторе для обработки этого контента в графическом интерфейсе " +"пользователя:" #: of vstutils.api.fields.MaskedField:1 msgid "" -"Extends class 'rest_framework.serializers.CharField'. Field that applies " -"mask to value." +"Extends the 'rest_framework.serializers.CharField' class. Field that " +"applies a mask to the input value." msgstr "" "Расширяет класс 'rest_framework.serializers.CharField'. Поле, применяющее" " маску к значению." -#: of vstutils.api.fields.MaskedField:5 -msgid "`IMask `_" -msgstr "`IMask `_" +#: of vstutils.api.fields.MaskedField:4 +msgid "" +"This field is designed for applying a mask to the input value on the " +"frontend. It extends the 'rest_framework.serializers.CharField' and " +"allows the use of the `IMask `_ library " +"for defining masks." +msgstr "" +"Это поле предназначено для применения маски к значению на фронтенде. Оно " +"расширяет класс 'rest_framework.serializers.CharField' и позволяет " +"использовать библиотеку `IMask `_ для " +"определения масок." #: of vstutils.api.fields.MaskedField:9 -msgid "Effective only on frontend." -msgstr "Действует только на фронтенде." +msgid "" +"The mask to be applied to the value. It can be either a dictionary or a " +"string following the `IMask` library format." +msgstr "" +"Маска, которая будет применена к значению. Это может быть как словарь, " +"так и строка в формате библиотеки `IMask`." + +#: of vstutils.api.fields.MaskedField:14 +msgid "In a serializer, include this field to apply a mask to a value:" +msgstr "В сериализаторе включите это поле для применения маски к значению:" + +#: of vstutils.api.fields.MaskedField:21 +msgid "" +"This example assumes a serializer where the ``masked_value`` field " +"represents a value with a predefined mask. The ``MaskedField`` will apply" +" the specified mask on the frontend, providing a masked input for users." +msgstr "" +"В этом примере предполагается, что сериализатор имеет поле " +"`masked_value`, представляющее значение с предопределенной маской. " +"`MaskedField` применит указанную маску на фронтенде, предоставляя " +"маскированный ввод для пользователей." + +#: of vstutils.api.fields.MaskedField:26 +msgid "" +"The effectiveness of this field is limited to the frontend, and the mask " +"is applied during user input." +msgstr "" +"Эффективность этого поля ограничивается фронтендом, и маска применяется " +"при вводе пользователя." #: of vstutils.api.fields.MultipleNamedBinaryFileInJsonField:1 msgid "" @@ -1825,7 +2092,6 @@ msgstr "" ":class:`NamedBinaryFileInJsonField`." #: of vstutils.api.fields.MultipleNamedBinaryFileInJsonField:4 -#: vstutils.api.fields.NamedBinaryFileInJsonField:13 msgid "" "Attrs: :attr:`NamedBinaryInJsonField.file`: if True, accept only " "subclasses of File as input. If False, accept only string input. Default:" @@ -1846,7 +2112,6 @@ msgstr "" " работает как список объектов :class:`NamedBinaryImageInJsonField`." #: of vstutils.api.fields.MultipleNamedBinaryImageInJsonField:4 -#: vstutils.api.fields.NamedBinaryImageInJsonField:5 msgid "" "Color to fill area that is not covered by image after cropping. " "Transparent by default but will be black if image format is not " @@ -1857,50 +2122,220 @@ msgstr "" "поддерживает прозрачность. Может быть любым допустимым цветом CSS." #: of vstutils.api.fields.NamedBinaryFileInJsonField:1 +msgid "Field that represents a binary file in JSON format." +msgstr "Поле, представляющее бинарный файл в формате JSON." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:3 msgid "" -"Field that takes JSON with properties: * name - string - name of file; * " -"mediaType - string - MIME type of file; * content - base64 string - " -"content of file." +"If True, accept only subclasses of File as input. If False, accept only " +"string input. Default: False." msgstr "" -"Поле, принимающее на вход JSON со следующими свойствами: * name - string " -"- имя файла; * mediaType - string - MIME-тип файла; * content - base64 " -"string - содержимое файла." +"Если True, принимает только подклассы File в качестве входных данных. " +"Если False, принимает только строковые входные данные. По умолчанию: " +"False." #: of vstutils.api.fields.NamedBinaryFileInJsonField:6 msgid "" -"This field is useful for saving binary files with their names in " +"Functions to process the file after validation. Each function takes two " +"arguments: ``binary_data`` (file bytes) and ``original_data`` (reference " +"to the original JSON object). The function should return the processed " +"``binary_data``." +msgstr "" +"Функции для обработки файла после валидации. Каждая функция принимает два" +" аргумента: ``binary_data`` (байты файла) и ``original_data`` (ссылка на " +"исходный JSON-объект). Функция должна возвращать обработанный " +"``binary_data``." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:11 +msgid "" +"Functions to process the file before validation. Each function takes two " +"arguments: ``binary_data`` (file bytes) and ``original_data`` (reference " +"to the original JSON object). The function should return the processed " +"``binary_data``." +msgstr "" +"Функции для обработки файла перед валидацией. Каждая функция принимает " +"два аргумента: ``binary_data`` (байты файла) и ``original_data`` (ссылка " +"на исходный JSON-объект). Функция должна возвращать обработанный " +"``binary_data``." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:16 +msgid "Maximum allowed size of the file content in bytes." +msgstr "Максимально допустимый размер содержимого файла в байтах." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:17 +msgid "Minimum allowed size of the file content in bytes." +msgstr "Минимально допустимый размер содержимого файла в байтах." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:18 +msgid "Minimum length of the file name. Only applicable when ``file=True``." +msgstr "Минимальная длина имени файла. Применяется только при ``file=True``." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:19 +msgid "Maximum length of the file name. Only applicable when ``file=True``." +msgstr "Максимальная длина имени файла. Применяется только при ``file=True``." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:22 +msgid "" +"This field is designed for storing binary files alongside their names in " ":class:`django.db.models.CharField` or " -":class:`django.db.models.TextField` model fields. All manipulations with " -"decoding and encoding binary content data executes on client. This " -"imposes reasonable limits on file size." +":class:`django.db.models.TextField` model fields. All manipulations " +"involving decoding and encoding binary content data occur on the client, " +"imposing reasonable limits on file size." msgstr "" -"Это поле полезно для сохранения бинарных файлов с их именами в полях " -"модели :class:`django.db.models.CharField` или " -":class:`django.db.models.TextField`. Все операции по кодированию или " -"декодированию бинарного содержимого осуществляется на клиенте. Это " -"накладывает разумные ограничения на размер файла." +"Это поле предназначено для хранения бинарных файлов вместе с их именами в" +" полях модели :class:`django.db.models.CharField` или " +":class:`django.db.models.TextField`. Все манипуляции, связанные с " +"декодированием и кодированием данных бинарного содержимого, выполняются " +"на клиенте, что накладывает разумные ограничения на размер файла." -#: of vstutils.api.fields.NamedBinaryFileInJsonField:10 +#: of vstutils.api.fields.NamedBinaryFileInJsonField:27 msgid "" -"Additionally, this field can construct " +"Additionally, this field can construct a " ":class:`django.core.files.uploadedfile.SimpleUploadedFile` from incoming " -"JSON and store it as file in :class:`django.db.models.FileField` if " -"`file` argument is set to `True`" +"JSON and store it as a file in :class:`django.db.models.FileField` if the" +" `file` attribute is set to `True`." msgstr "" "Кроме того, это поле может создать " ":class:`django.core.files.uploadedfile.SimpleUploadedFile` из входящего " "JSON и сохранить его как файл в :class:`django.db.models.FileField`, если" -" для аргумента `file` установлено значение `True`" +" атрибут `file` установлен в значение `True`." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:34 +msgid "In a serializer, include this field to handle binary files:" +msgstr "В сериализаторе включите это поле для обработки бинарных файлов:" + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:41 +msgid "" +"This example assumes a serializer where the ``binary_data`` field " +"represents binary file information in JSON format. The " +"``NamedBinaryFileInJsonField`` will then handle the storage and retrieval" +" of binary files in a user-friendly manner." +msgstr "" +"В этом примере предполагается, что в сериализаторе поле ``binary_data`` " +"представляет информацию о бинарном файле в формате JSON. Поле " +"``NamedBinaryFileInJsonField`` обеспечит обработку хранения и извлечения " +"бинарных файлов удобным для пользователя образом." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:46 +msgid "The binary file is represented in JSON with the following properties:" +msgstr "Бинарный файл представлен в формате JSON со следующими свойствами:" + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:48 +msgid "**name** (str): Name of the file." +msgstr "**name** (str): Имя файла." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:49 +msgid "**mediaType** (str): MIME type of the file." +msgstr "**mediaType** (str): MIME-тип файла." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:50 +msgid "" +"**content** (str or File): Content of the file. If `file` is True, it " +"will be a reference to the file; otherwise, it will be base64-encoded " +"content." +msgstr "" +"**content** (str или File): Содержимое файла. Если `file` установлен в " +"True, это будет ссылка на файл; в противном случае содержимое будет " +"закодировано base64." + +#: of vstutils.api.fields.NamedBinaryFileInJsonField:54 +msgid "" +"The client application will display the content as a download link. Users" +" will interact with the binary file through the application, with the " +"exchange between the Rest API and the client occurring through the " +"presented JSON object." +msgstr "" +"Приложение клиента отобразит содержимое в виде ссылки для скачивания. " +"Пользователи будут взаимодействовать с бинарным файлом через приложение, " +"а обмен между Rest API и клиентом будет происходить через представленный " +"объект JSON." #: of vstutils.api.fields.NamedBinaryImageInJsonField:1 msgid "" -"Extends :class:`.NamedBinaryFileInJsonField` to represent image on " -"frontend (if binary image is valid). Validate this field with " -":class:`vstutils.api.validators.ImageValidator`." +"Field that represents an image in JSON format, extending " +":class:`.NamedBinaryFileInJsonField`." msgstr "" -"Расширяет :class:`.NamedBinaryFileInJsonField` для view изображения на " -"фронтенде (если бинарное изображение валидно). Валидируйте это поле с " -"помощью :class:`vstutils.api.validators.ImageValidator`." +"Поле, представляющее изображение в формате JSON, расширяющее " +":class:`.NamedBinaryFileInJsonField`." + +#: of vstutils.api.fields.NamedBinaryImageInJsonField:3 +msgid "" +"Color to fill the area not covered by the image after cropping. " +"Transparent by default but will be black if the image format does not " +"support transparency. Can be any valid CSS color." +msgstr "" +"Цвет для заполнения области, не покрытой изображением после обрезки. По " +"умолчанию прозрачный, но будет черным, если формат изображения не " +"поддерживает прозрачность. Может быть любым допустимым цветом CSS." + +#: of vstutils.api.fields.NamedBinaryImageInJsonField:8 +msgid "" +"This field is designed for handling image files alongside their names in " +":class:`django.db.models.CharField` or " +":class:`django.db.models.TextField` model fields. It extends the " +"capabilities of :class:`.NamedBinaryFileInJsonField` to specifically " +"handle images." +msgstr "" +"Это поле предназначено для хранения бинарных файлов вместе с их именами в" +" полях модели :class:`django.db.models.CharField` или " +":class:`django.db.models.TextField`. Оно расширяет возможности " +":class:`.NamedBinaryFileInJsonField` для специальной обработки " +"изображений." + +#: of vstutils.api.fields.NamedBinaryImageInJsonField:12 +msgid "" +"Additionally, this field validates the image using the following " +"validators: - :class:`vstutils.api.validators.ImageValidator` - " +":class:`vstutils.api.validators.ImageResolutionValidator` - " +":class:`vstutils.api.validators.ImageHeightValidator`" +msgstr "" +"Кроме того, это поле проверяет изображение с использованием следующих " +"валидаторов: - :class:`vstutils.api.validators.ImageValidator` - " +":class:`vstutils.api.validators.ImageResolutionValidator` - " +":class:`vstutils.api.validators.ImageHeightValidator`" + +#: of vstutils.api.fields.NamedBinaryImageInJsonField:17 +msgid "" +"When saving and with the added validators, the field will display a " +"corresponding window for adjusting the image to the specified parameters," +" providing a user-friendly interface for managing images." +msgstr "" +"При сохранении и с добавленными валидаторами поле будет отображать " +"соответствующее окно для настройки изображения по указанным параметрам, " +"предоставляя удобный интерфейс для управления изображениями." + +#: of vstutils.api.fields.NamedBinaryImageInJsonField:20 +msgid "The image file is represented in JSON with the following properties:" +msgstr "Бинарный файл представлен в формате JSON со следующими свойствами:" + +#: of vstutils.api.fields.NamedBinaryImageInJsonField:22 +msgid "**name** (str): Name of the image file." +msgstr "**name** (str): Имя файла изображения." + +#: of vstutils.api.fields.NamedBinaryImageInJsonField:23 +msgid "**mediaType** (str): MIME type of the image file." +msgstr "**mediaType** (str): MIME-тип файла изображения." + +#: of vstutils.api.fields.NamedBinaryImageInJsonField:24 +msgid "" +"**content** (str or File): Content of the image file. If `file` is True, " +"it will be a reference to the image file; otherwise, it will be " +"base64-encoded content." +msgstr "" +"**content** (str или File): Содержимое файла изображения. Если `file` " +"установлен в True, это будет ссылка на файл; в противном случае " +"содержимое будет закодировано base64." + +#: of vstutils.api.fields.NamedBinaryImageInJsonField:28 +msgid "" +"The client application will display the content as an image. Users will " +"interact with the image through the application, with the exchange " +"between the Rest API and the client occurring through the presented JSON " +"object." +msgstr "" +"Приложение клиента отобразит содержимое как изображение. Пользователи " +"будут взаимодействовать с изображением через приложение, и обмен между " +"Rest API и клиентом будет происходить через представленный объект JSON." #: of vstutils.api.fields.PasswordField:1 msgid "" @@ -1915,19 +2350,86 @@ msgstr "" #: of vstutils.api.fields.PhoneField:1 msgid "" -"Extends class 'rest_framework.serializers.CharField'. Field for phone in " -"international format" +"Extends the 'rest_framework.serializers.CharField' class. Field for " +"representing a phone number in international format." msgstr "" -"Расширяет класс 'rest_framework.serializers.CharField'. Поле для ввода " -"номера телефона в международном формате" +"Расширяет класс 'rest_framework.serializers.CharField'. Поле для " +"представления номера телефона в международном формате." + +#: of vstutils.api.fields.PhoneField:4 +msgid "" +"This field is designed for capturing and validating phone numbers in " +"international format. It extends the " +"'rest_framework.serializers.CharField' and adds custom validation to " +"ensure that the phone number contains only digits." +msgstr "" +"Это поле предназначено для захвата и валидации номеров телефонов в " +"международном формате. Оно расширяет класс " +"'rest_framework.serializers.CharField' и добавляет пользовательскую " +"валидацию для обеспечения того, что номер телефона содержит только цифры." + +#: of vstutils.api.fields.PhoneField:9 +msgid "In a serializer, include this field to handle phone numbers:" +msgstr "В сериализаторе включите это поле для обработки номеров телефонов:" + +#: of vstutils.api.fields.PhoneField:16 +msgid "" +"This example assumes a serializer where the ``phone_number`` field " +"represents a phone number in international format. The ``PhoneField`` " +"will then handle the validation and representation of phone numbers, " +"making it convenient for users to input standardized phone numbers." +msgstr "" +"В этом примере предполагается, что сериализатор имеет поле " +"`phone_number`, представляющее номер телефона в международном формате. " +"`PhoneField` затем обработает валидацию и представление номеров " +"телефонов, облегчая пользователям ввод стандартизированных номеров " +"телефонов." + +#: of vstutils.api.fields.PhoneField:21 +msgid "" +"The field will be displayed in the client application as an input field " +"for entering a phone number, including the country code." +msgstr "" +"Поле будет отображаться в клиентском приложении в виде поля ввода для " +"ввода номера телефона, включая код страны." #: of vstutils.api.fields.QrCodeField:1 +msgid "A versatile field for encoding various types of data into QR codes." +msgstr "Универсальное поле для кодирования различных типов данных в QR-коды." + +#: of vstutils.api.fields.QrCodeField:3 msgid "" -"Simple string field. The field is going to represent as QrCode in user " -"interface." +"This field can encode a wide range of data into a QR code representation," +" making it useful for displaying QR codes in the user interface. It works" +" by serializing or deserializing data using the specified child field." +msgstr "" +"Это поле может закодировать различные типы данных в представление " +"QR-кода, что делает его полезным для отображения QR-кодов в " +"пользовательском интерфейсе. Оно работает путем сериализации или " +"десериализации данных с использованием указанного дочернего поля." + +#: of vstutils.api.fields.QrCodeField:11 +msgid "" +"Suppose you have a model with a `data` field, and you want to display its" +" QR code representation in the GUI. You can use `QrCodeField` in your " +"serializer:" +msgstr "" +"Предположим, у вас есть модель с полем `data`, и вы хотите отображать его" +" представление в виде QR-кода в графическом интерфейсе пользователя. Вы " +"можете использовать `QrCodeField` в своем сериализаторе:" + +#: of vstutils.api.fields.QrCodeField:26 +msgid "" +"In this example, the qr_code_data field will represent the QR code " +"generated from the data field in the GUI. Users can interact with this QR" +" code, and if their device supports it, they can scan the code for " +"further actions." msgstr "" -"Простое строковое поле. В пользовательском интерфейсе оно будет " -"отображено как QR-код." +"В этом примере поле qr_code_data будет представлять QR-код, " +"сгенерированный из поля данных в графическом интерфейсе пользователя. " +"Пользователи могут взаимодействовать с этим QR-кодом, и, если их " +"устройство поддерживает, они могут сканировать код для дополнительных " +"действий." #: of vstutils.api.fields.RatingField:1 msgid "" @@ -1990,6 +2492,15 @@ msgstr "" "Поле для редиректа по строке. Часто используется в экшенах для редиректа " "после выполнения." +#: of vstutils.api.fields.RedirectCharField:4 +#: vstutils.api.fields.RedirectIntegerField:4 +msgid "" +"Effective only in GUI. Works similar to " +":class:`rest_framework.fields.IntegerField` in API." +msgstr "" +"Действует только в графическом интерфейсе. Работает аналогично " +":class:`rest_framework.fields.IntegerField` в API." + #: of vstutils.api.fields.RedirectFieldMixin:1 msgid "" "Field mixin indicates that this field is used to send redirect address to" @@ -2028,68 +2539,72 @@ msgstr "" #: of vstutils.api.fields.RelatedListField:1 msgid "" -"Extends class :class:`.VSTCharField`. With this field you can output " -"reverse ForeignKey relation as a list of related instances." +"Extends :class:`.VSTCharField` to represent a reverse ForeignKey relation" +" as a list of related instances." msgstr "" -"Расширяет класс :class:`.VSTCharField`. С этим полем вы можете получить " -"обратное ForeignKey отношение как список связанных экземпляров." +"Расширяет класс :class:`.VSTCharField`. Представляет обратное ForeignKey " +"отношение в виде списка связанных экземпляров." -#: of vstutils.api.fields.RelatedListField:4 +#: of vstutils.api.fields.RelatedListField:3 msgid "" -"To use it, you specify 'related_name' kwarg (related_manager for reverse " -"ForeignKey) and 'fields' kwarg (list or tuple of fields from related " -"model, which needs to be included)." +"This field allows you to output the reverse ForeignKey relation as a list" +" of related instances. To use it, specify the ``related_name`` kwarg " +"(related manager for reverse ForeignKey) and the ``fields`` kwarg (list " +"or tuple of fields from the related model to be included)." msgstr "" -"Для использования следует указать kwarg 'related_name' (related_manager " -"для обратного ForeignKey) и kwarg 'fields' (список или кортеж полей из " -"связанной модели, которая должна быть включена)." +"Это поле позволяет представить обратное ForeignKey отношение в виде " +"списка связанных экземпляров. Для использования укажите ``related_name`` " +"(related manager для обратного ForeignKey) и ``fields`` (список или " +"кортеж полей из связанной модели, которые должны быть включены)." #: of vstutils.api.fields.RelatedListField:7 msgid "" -"By default :class:`.VSTCharField` used to serialize all field values and " -"represent it on frontend. You can specify `serializer_class` and override" -" fields as you need. For example title, description and other field " -"properties can be set to customize frontend behavior." +"By default, :class:`.VSTCharField` is used to serialize all field values " +"and represent them on the frontend. You can specify the " +"`serializer_class` and override fields as needed. For example, title, " +"description, and other field properties can be set to customize frontend " +"behavior." msgstr "" -"По умолчанию :class:`.VSTCharField` используется для сериализации всех " -"значений поля и отображения их на фронтенде. Вы можете указать " -"`serializer_class` и переопределить поля, которые вам нужны. Например, " -"title, description или другие поля, свойства которых можно задать так, " -"чтобы определить новое поведение на фронтенде." +"По умолчанию используется :class:`.VSTCharField` для сериализации всех " +"значений поля и их представления на фронтенде. Вы можете указать " +"`serializer_class` и переопределить поля по мере необходимости. Например," +" заголовок, описание и другие свойства полей можно установить для " +"настройки поведения на фронтенде." #: of vstutils.api.fields.RelatedListField:12 -msgid "name of a related manager for reverse foreign key" -msgstr "имя related manager'a для обратного foreign key отношения" +msgid "Name of a related manager for reverse ForeignKey." +msgstr "Имя related manager для обратного ForeignKey." #: of vstutils.api.fields.RelatedListField:15 -msgid "list of related model fields." -msgstr "список связанных полей модели." +msgid "List of related model fields." +msgstr "Список связанных полей модели." #: of vstutils.api.fields.RelatedListField:18 msgid "" -"determines how field are represented on frontend. Must be either 'list' " -"or 'table'." +"Determines how fields are represented on the frontend. Must be either " +"``list`` or ``table``." msgstr "" -"определяет, как поля будут отображены на фронтенде. Должен быть либо " -"'list', либо 'table'." +"Определяет, как поля будут представлены на фронтенде. Должен быть либо " +"``list``, либо ``table``." #: of vstutils.api.fields.RelatedListField:20 msgid "" -"includes custom handlers, where key: field_name, value: callable_obj that" -" takes params: instance[dict], fields_mapping[dict], model, " -"field_name[str]" +"Custom handlers mapping, where key: field_name, value: callable_obj that " +"takes params: instance[dict], fields_mapping[dict], model, " +"field_name[str]." msgstr "" -"включает пользовательские handler'ы, где ключ: field_name, значение: " -"callable_obj, который принимает параметры: instance[dict], " -"fields_mapping[dict], model, field_name[str]" +"Отображение пользовательских обработчиков, где ключ: field_name, " +"значение: callable_obj, принимающий параметры: instance[dict], " +"fields_mapping[dict], model, field_name[str]." -#: of vstutils.api.fields.RelatedListField:24 +#: of vstutils.api.fields.RelatedListField:25 msgid "" -"Serializer to customize types of fields, if no serializer provided " -":class:`.VSTCharField` will be used for every field in `fields` list" +"Serializer to customize types of fields. If no serializer is provided, " +":class:`.VSTCharField` will be used for every field in the `fields` list." msgstr "" -"Сериализатор для определения типов полей, если не задано, будет " -"использован :class:`.VSTCharField` для каждого поля из списка `fields`" +"Сериализатор для настройки типов полей. Если сериализатор не " +"предоставлен, будет использован :class:`.VSTCharField` для каждого поля " +"из списка `fields`." #: of vstutils.api.fields.SecretFileInString:1 msgid "" @@ -2100,14 +2615,69 @@ msgstr "" "значение в интерфейсе администратора." #: of vstutils.api.fields.TextareaField:1 -msgid "Field containing multiline string." -msgstr "Поле, содержащее многострочный текст." +msgid "A specialized field that allows the input and display of multiline text." +msgstr "" + +#: of vstutils.api.fields.TextareaField:8 +msgid "" +"Suppose you have a model with a `description` field that can contain " +"multiline text. You can use `TextareaField` in your serializer to enable " +"users to input and view multiline text in the GUI:" +msgstr "" #: of vstutils.api.fields.UptimeField:1 -msgid "Time duration field, in seconds. May be used to compute some uptime." +msgid "" +"Time duration field, in seconds, specifically designed for computing and " +"displaying system uptime." +msgstr "" +"Поле продолжительности времени, в секундах, специально разработанное для " +"вычисления и отображения времени работы системы." + +#: of vstutils.api.fields.UptimeField:3 +msgid "" +"This field is effective only in the GUI and behaves similarly to " +":class:`rest_framework.fields.IntegerField` in the API." +msgstr "" +"Это поле действует только в графическом интерфейсе и ведет себя " +"аналогично :class:`rest_framework.fields.IntegerField` в API." + +#: of vstutils.api.fields.UptimeField:6 +msgid "" +"The `UptimeField` class transforms time in seconds into a user-friendly " +"representation on the frontend. It intelligently selects the most " +"appropriate pattern from the following templates:" +msgstr "" +"Класс `UptimeField` преобразует время в секундах в удобочитаемое " +"представление на фронтенде. Он интеллектуально выбирает наиболее " +"подходящий шаблон из следующих:" + +#: of vstutils.api.fields.UptimeField:9 +msgid "``HH:mm:ss`` (e.g., 23:59:59)" +msgstr "" + +#: of vstutils.api.fields.UptimeField:10 +msgid "``dd HH:mm:ss`` (e.g., 01d 00:00:00)" +msgstr "" + +#: of vstutils.api.fields.UptimeField:11 +msgid "``mm dd HH:mm:ss`` (e.g., 01m 30d 00:00:00)" +msgstr "" + +#: of vstutils.api.fields.UptimeField:12 +msgid "``yy mm dd HH:mm:ss`` (e.g., 99y 11m 30d 22:23:24)" msgstr "" -"Поле продолжительности времени, в секундах. Может быть использовано для " -"подсчета времени работы чего-либо." + +#: of vstutils.api.fields.UptimeField:21 +msgid "" +"This example assumes a serializer where the `uptime` field represents a " +"time duration in seconds. The `UptimeField` will then display the " +"duration in a human-readable format on the frontend, making it convenient" +" for users to interpret and input values." +msgstr "" +"В этом примере предполагается, что сериализатор имеет поле `uptime`, " +"представляющее продолжительность времени в секундах. `UptimeField` затем " +"отобразит продолжительность в удобочитаемом формате на фронтенде, " +"облегчая пользователям интерпретацию и ввод значений." #: of vstutils.api.fields.VSTCharField:1 msgid "" @@ -2119,15 +2689,66 @@ msgstr "" #: of vstutils.api.fields.WYSIWYGField:1 msgid "" -"On frontend renders https://ui.toast.com/tui-editor. Saves data as " -"markdown and escapes all html tags." +"Extends the :class:`.TextareaField` class to render the " +"https://ui.toast.com/tui-editor on the frontend." +msgstr "" +"Расширяет класс :class:`.TextareaField` для отображения редактора " +"https://ui.toast.com/tui-на фронтенде." + +#: of vstutils.api.fields.WYSIWYGField:3 +msgid "" +"This field is specifically designed for rendering a WYSIWYG editor on the" +" frontend, using the https://ui.toast.com/tui-editor. It saves data as " +"markdown and escapes all HTML tags." +msgstr "" +"Это поле специально разработано для отображения WYSIWYG-редактора на " +"фронтенде с использованием https://ui.toast.com/tui-editor. Сохраняет " +"данные в формате markdown и экранирует все HTML-теги." + +#: of vstutils.api.fields.WYSIWYGField:6 +msgid "" +"HTML-escape input. Enabled by default to prevent HTML injection " +"vulnerabilities." +msgstr "" +"Экранирование HTML-тегов для входных данных. Включено по умолчанию для " +"предотвращения уязвимостей HTML-инъекций." + +#: of vstutils.api.fields.WYSIWYGField:10 +msgid "" +"In a serializer, include this field to render a WYSIWYG editor on the " +"frontend:" +msgstr "" +"Включите это поле в сериализаторе, чтобы отобразить WYSIWYG-редактор на " +"фронтенде:" + +#: of vstutils.api.fields.WYSIWYGField:17 +msgid "" +"This example assumes a serializer where the ``wysiwyg_content`` field " +"represents data to be rendered in a WYSIWYG editor on the frontend. The " +"``WYSIWYGField`` ensures that the content is saved as markdown and HTML " +"tags are escaped to enhance security." +msgstr "" +"В этом примере предполагается, что сериализатор имеет поле " +"`wysiwyg_content`, представляющее данные, которые должны быть отображены " +"в WYSIWYG-редакторе на фронтенде. `WYSIWYGField` гарантирует, что " +"содержимое сохраняется в формате markdown, и HTML-теги экранируются для " +"повышения безопасности." + +#: of vstutils.api.fields.WYSIWYGField:22 +msgid "" +"Enabling the ``escape`` option is recommended to prevent potential HTML " +"injection vulnerabilities." msgstr "" -"На фронтенде отображается https://ui.toast.com/tui-editor. Сохраняет " -"данные в формате markdown, экранируя все html-теги." +"Рекомендуется включить опцию ``escape`` для предотвращения потенциальных " +"уязвимостей HTML-инъекций." -#: of vstutils.api.fields.WYSIWYGField:4 -msgid "html-escape input. Enabled by default." -msgstr "экранирование входящих html-символов. Включено по умолчанию." +#: of vstutils.api.fields.WYSIWYGField:25 +msgid "" +"The rendering on the frontend is achieved using the https://ui.toast.com" +"/tui-editor." +msgstr "" +"Отображение на фронтенде достигается с использованием " +"https://ui.toast.com/tui-editor." #: ../../backend.rst:56 msgid "Validators" @@ -2305,28 +2926,40 @@ msgstr "" "`_." #: of vstutils.api.serializers.BaseSerializer:1 +msgid "Default serializer with logic to work with objects." +msgstr "Сериализатор по умолчанию с логикой для работы с объектами." + +#: of vstutils.api.serializers.BaseSerializer:3 msgid "" -"Default serializer with logic to work with objects. Read more in `DRF " -"serializer's documentation `_ how to create Serializers and work with" -" them." +"This serializer serves as a base class for creating serializers to work " +"with non-model objects. It extends the " +"'rest_framework.serializers.Serializer' class and includes additional " +"logic for handling object creation and updating." msgstr "" -"Стандартный сериализатор с логикой работы с объектами. Читайте подробнее " -"в `документации сериализатора `_ как создавать сериализаторы и работать " -"с ними." +"Этот сериализатор служит базовым классом для создания сериализаторов для " +"работы с объектами, не являющимися моделями. Он расширяет класс " +"'rest_framework.serializers.Serializer' и включает дополнительную логику " +"для обработки создания и обновления объектов." -#: of vstutils.api.serializers.BaseSerializer:7 +#: of vstutils.api.serializers.BaseSerializer:8 msgid "" -"You can also setup ``generated_fields`` in class attribute ``Meta`` to " -"get serializer with default CharField fields. Setup attribute " -"``generated_field_factory`` to change default fabric method." +"You can set the ``generated_fields`` attribute in the ``Meta`` class to " +"automatically include default CharField fields. You can also customize " +"the field creation using the ``generated_field_factory`` attribute." msgstr "" "Вы также можете настроить ``generated_fields`` в атрибуте класса " "``Meta``, чтобы получить сериализатор с полями CharField по умолчанию. " "Настройте атрибут ``generated_field_factory`` чтобы изменить фабричный " "метод по умолчанию." +#: of vstutils.api.serializers.BaseSerializer:21 +msgid "" +"In this example, the ``MySerializer`` class extends ``BaseSerializer`` " +"and includes an additional generated field." +msgstr "" +"В этом примере класс ``MySerializer`` расширяет ``BaseSerializer`` и " +"включает дополнительное созданное поле." + #: of vstutils.api.serializers.DisplayMode:1 msgid "" "For any serializer displayed on frontend property `_display_mode` can be " @@ -2363,21 +2996,35 @@ msgid "" "documentation `_ how to create Model Serializers. " "This serializer matches model fields to extended set of serializer " -"fields. List of available pairs specified in " +"fields." +msgstr "" +"Сериализатор модели по умолчанию, основанный на " +":class:`rest_framework.serializers.ModelSerializer`. Узнайте больше в " +"`документации DRF `_ о том, как создавать сериализаторы " +"модели. Этот сериализатор сопоставляет поля модели с расширенным набором " +"полей сериализатора." + +#: of vstutils.api.serializers.VSTSerializer:6 +msgid "" +"List of available pairs specified in " "`VSTSerializer.serializer_field_mapping`. For example, to set " ":class:`vstutils.api.fields.FkModelField` in serializer use " ":class:`vstutils.models.fields.FkModelField` in a model." msgstr "" -"Стандартный сериализатор модели, основанный на " -":class:`rest_framework.serializers.ModelSerializer`. Читайте подробнее в " -"`документации DRF `_, как создавать сериализаторы " -"модели. Этот сериализатор сопоставляет полям модели расширенный набор " -"полей сериализатора. Список доступных пар описан в " -"`VSTSerializer.serializer_field_mapping`. Например, чтобы использовать " -":class:`vstutils.api.fields.FkModelField` в сериализаторе, задайте " +"Список доступных пар, указанных в " +"`VSTSerializer.serializer_field_mapping`. Например, чтобы установить " +":class:`vstutils.api.fields.FkModelField` в сериализаторе, используйте " ":class:`vstutils.models.fields.FkModelField` в модели." +#: of vstutils.api.serializers.VSTSerializer:21 +msgid "" +"In this example, the ``MySerializer`` class extends ``VSTSerializer`` and" +" is associated with the ``MyModel`` model." +msgstr "" +"В этом примере класс ``MySerializer`` расширяет ``VSTSerializer`` и " +"ассоциируется с моделью ``MyModel``." + #: ../../backend.rst:70 msgid "Views" msgstr "Представления" @@ -4856,3 +5503,331 @@ msgstr "" "Использует функцию :func:`django.utils.translation.get_language` для " "получения кода языка и пытается получить перевод из списка доступных." +#~ msgid "prefetch values on frontend at list-view. Default is ``False``." +#~ msgstr "" +#~ "делает prefetch значений на фронтенде в" +#~ " list-view. По умолчанию ``False``." + +#~ msgid "" +#~ "Dictionary, where keys are name of " +#~ "field from the same model, and " +#~ "values are name of query filter. " +#~ "If at least one of the fields " +#~ "that we depend on is non nullable," +#~ " required and set to null, " +#~ "autocompletion list will be empty and" +#~ " field will be disabled." +#~ msgstr "" +#~ "Словарь, где ключи - это имена " +#~ "полей из той же модели, а значения" +#~ " - названия query-фильтров. Если хотя " +#~ "бы одно из полей, от которых " +#~ "существует зависимость не допускает null, " +#~ "обязательно или установлено в null, " +#~ "список автодополнения будет пустым, а " +#~ "поле окажется выключенным." + +#~ msgid "" +#~ "Field which type is based on " +#~ "another field. It converts value to " +#~ "internal string and represent field as" +#~ " json object." +#~ msgstr "" +#~ "Поле, тип которого зависит от другого" +#~ " поля. Хранит значение в виде строки," +#~ " а отображает поле в виде объекта " +#~ "json." + +#~ msgid "" +#~ "Allows to use parent views data as" +#~ " source for field creation. Exact " +#~ "view path (`/user/{id}/`) or relative " +#~ "parent specifier (`<>.<>.<>`) " +#~ "can be provided. For example if " +#~ "current page is `/user/1/role/2/` and " +#~ "`source_view` is `<>.<>` then " +#~ "data from `/user/1/` will be used. " +#~ "Only detail views if supported." +#~ msgstr "" +#~ "Позволяет использовать данные родительских " +#~ "view в качестве источника для создания" +#~ " полей. Можно указать точный путь " +#~ "представления (`/user/{id}/`) или относительный " +#~ "спецификатор родительского представления " +#~ "(`<>.<>.<>`). Например, если " +#~ "текущая страница - `/user/1/role/2/`, а " +#~ "`source_view` - `<>.<>`, то " +#~ "будут использованы данные из `/user/1/`. " +#~ "Поддерживаются только представления деталей." + +#~ msgid "" +#~ "Effective only in GUI. In API " +#~ "works similar to :class:`.VSTCharField` " +#~ "without value modifications." +#~ msgstr "" +#~ "Действует только в графическом интерфейсе. " +#~ "В API работает аналогично " +#~ ":class:`.VSTCharField` без изменения значения." + +#~ msgid "" +#~ "A versatile field in Django Rest " +#~ "Framework that dynamically adapts its " +#~ "type based on the value of another" +#~ " field in the model. It facilitates" +#~ " complex scenarios where the type of" +#~ " data to be serialized depends on " +#~ "the value of a related field." +#~ msgstr "" + +#~ msgid "" +#~ "Example: Suppose you have a serializer" +#~ " `MySerializer` with a `field_type` (e.g.," +#~ " a `ChoiceField`) and a corresponding " +#~ "`object_data`. The `object_data` field can " +#~ "have different types based on the " +#~ "value of `field_type`. Here's an example" +#~ " configuration:" +#~ msgstr "" + +#~ msgid "" +#~ "Field extends :class:`DynamicJsonTypeField`. " +#~ "Validates field data by " +#~ ":attr:`.field_attribute` chosen in related " +#~ "model. By default, any value of " +#~ ":attr:`.field_attribute` validates as " +#~ ":class:`.VSTCharField`. To override this " +#~ "behavior set dict attribute ``{field_attribute" +#~ " value}_fields_mapping`` in related model " +#~ "where:" +#~ msgstr "" +#~ "Поле, расширяющее :class:`DynamicJsonTypeField`. " +#~ "Валидирует данные поля с помощью " +#~ ":attr:`.field_attribute`, выбранного в связанной " +#~ "модели. По умолчанию любое значение " +#~ ":attr:`.field_attribute` валидируется классом " +#~ ":class:`.VSTCharField`. Чтобы переопределить это " +#~ "поведение, установите словарный атрибут " +#~ "``{field_attribute value}_fields_mapping`` в " +#~ "связанной модели, где:" + +#~ msgid "" +#~ "**key** - string representation of value" +#~ " type which is received from related" +#~ " instance :attr:`.field_attribute`." +#~ msgstr "" +#~ "**key** - строковое представление типа " +#~ "значения, получаемое от связанной модели " +#~ ":attr:`.field_attribute`." + +#~ msgid "**value** - :class:`rest_framework.Field` instance for validation." +#~ msgstr "**value** - экземпляр :class:`rest_framework.Field` для валидации." + +#~ msgid "Field containing multiline string." +#~ msgstr "Поле, содержащее многострочный текст." + +#~ msgid "" +#~ "Field contains html text and marked " +#~ "as format:html. The field does not " +#~ "validate whether its content is HTML." +#~ msgstr "" +#~ "Поле, содержащее текст html и помеченное" +#~ " как format:html. Это поле не " +#~ "проверяет, является ли его содержимое " +#~ "валидным HTML." + +#~ msgid "" +#~ "To avoid vulnerability, do not allow " +#~ "users to modify this data because " +#~ "users ate able to execute their " +#~ "scripts." +#~ msgstr "" +#~ "Чтобы избежать уязвимости, не позволяйте " +#~ "пользователям изменять эти данные, так " +#~ "как они могут выполнить нежелательный " +#~ "скрипт." + +#~ msgid "" +#~ "Simple string field. Value must always" +#~ " be a valid ASCII-string. The " +#~ "field is going to represent as " +#~ "Barcode (Code 128) in user interface." +#~ msgstr "" +#~ "Простое строковое поле. Значение всегда " +#~ "должно быть допустимой строкой ASCII. В" +#~ " пользовательском интерфейсе оно будет " +#~ "отображено как штрихкод (код 128)." + +#~ msgid "" +#~ "Simple string field. The field is " +#~ "going to represent as QrCode in " +#~ "user interface." +#~ msgstr "" +#~ "Простое строковое поле. В пользовательском " +#~ "интерфейсе оно будет отображено как " +#~ "QR-код." + +#~ msgid "warning" +#~ msgstr "предупреждение" + +#~ msgid "" +#~ "Exercise caution when using this field," +#~ " as it does not validate whether " +#~ "the content is valid HTML. Be " +#~ "aware of the potential security risks" +#~ " associated with allowing users to " +#~ "modify HTML content, as they may " +#~ "execute scripts." +#~ msgstr "" +#~ "Будьте осторожны при использовании этого " +#~ "поля, так как оно не проверяет, " +#~ "является ли контент допустимым HTML. " +#~ "Будьте в курсе потенциальных рисков " +#~ "безопасности, связанных с разрешением " +#~ "пользователям изменять HTML-контент, так как" +#~ " они могут выполнять сценарии." + +#~ msgid "note" +#~ msgstr "None" + +#~ msgid "show value as link to model. Default is ``True``." +#~ msgstr "Отображает значение как ссылку на модель. По умолчанию ``True``." + +#~ msgid "" +#~ "dictionary, where keys are names of " +#~ "a field from the same model, and" +#~ " values are names of query filter." +#~ " If at least one of the fields" +#~ " that we depend on is non " +#~ "nullable, required and set to null, " +#~ "autocompletion list will be empty and" +#~ " field will be disabled." +#~ msgstr "" +#~ "словарь, где ключи - это имена " +#~ "полей из той же модели, а значения" +#~ " - названия query-фильтров. Если хотя " +#~ "бы одно из полей, от которых " +#~ "существует зависимость не допускает null, " +#~ "обязательно или установлено в null, " +#~ "список автодополнения будет пустым, а " +#~ "поле окажется выключенным." + +#~ msgid "" +#~ "This field does not support " +#~ "``dependence``. Use ``filters`` at your " +#~ "own risk, as it would rather break" +#~ " the tree structure." +#~ msgstr "" +#~ "Это поле не поддерживает ``dependence``. " +#~ "Используйте ``filters`` на свой страх и" +#~ " риск, так как они могут сломать " +#~ "структуру дерева." + +#~ msgid "" +#~ "if True then only allows a value" +#~ " to be selected if it has no" +#~ " children. Default is `False`" +#~ msgstr "" +#~ "если True, то допускает к выбору " +#~ "только то значение, которое не имеет " +#~ "дочерних элементов. По умолчанию `False`" + +#~ msgid "name of parent field in model. Default is `parent`" +#~ msgstr "название родительского поля в модели. По умолчанию `parent`" + +#~ msgid "" +#~ "Field that takes JSON with properties:" +#~ " * name - string - name of " +#~ "file; * mediaType - string - MIME" +#~ " type of file; * content - " +#~ "base64 string - content of file." +#~ msgstr "" +#~ "Поле, принимающее на вход JSON со " +#~ "следующими свойствами: * name - string" +#~ " - имя файла; * mediaType - " +#~ "string - MIME-тип файла; * content " +#~ "- base64 string - содержимое файла." + +#~ msgid "Effective only in GUI. Works similar to :class:`.VSTCharField` in API." +#~ msgstr "" +#~ "Действует только в графическом интерфейсе. " +#~ "Работает аналогично :class:`.VSTCharField` в " +#~ "API." + +#~ msgid "**Attributes:**" +#~ msgstr "**Атрибуты**:" + +#~ msgid ":attr:`bool file`: If True, accept only subclasses of File as input." +#~ msgstr "" +#~ ":attr:`bool file`: Если True, принимает " +#~ "только подклассы File в качестве входных" +#~ " данных." + +#~ msgid "If False, accept only string input. Default: False." +#~ msgstr "" +#~ "Если False, принимает только строковые " +#~ "входные данные. По умолчанию: False." + +#~ msgid "Максимально допустимый размер содержимого файла в байтах." +#~ msgstr "Максимально допустимый размер содержимого файла в байтах." + +#~ msgid "Минимально допустимый размер содержимого файла в байтах." +#~ msgstr "Минимально допустимый размер содержимого файла в байтах." + +#~ msgid "Минимальная длина имени файла. Применяется только при ``file=True``." +#~ msgstr "Минимальная длина имени файла. Применяется только при ``file=True``." + +#~ msgid "Максимальная длина имени файла. Применяется только при ``file=True``." +#~ msgstr "Максимальная длина имени файла. Применяется только при ``file=True``." + +#~ msgid "" +#~ "Extends :class:`.NamedBinaryFileInJsonField` to " +#~ "represent image on frontend (if binary" +#~ " image is valid). Validate this field" +#~ " with :class:`vstutils.api.validators.ImageValidator`." +#~ msgstr "" +#~ "Расширяет :class:`.NamedBinaryFileInJsonField` для " +#~ "view изображения на фронтенде (если " +#~ "бинарное изображение валидно). Валидируйте это" +#~ " поле с помощью " +#~ ":class:`vstutils.api.validators.ImageValidator`." + +#~ msgid "**name** (str): Имя файла изображения." +#~ msgstr "**name** (str): Имя файла." + +#~ msgid "**mediaType** (str): MIME-тип файла изображения." +#~ msgstr "**mediaType** (str): MIME-тип файла." + +#~ msgid "" +#~ "**content** (str or File): Содержимое " +#~ "файла изображения. Если `file` установлен " +#~ "в True, это будет ссылка на файл" +#~ " изображения; в противном случае содержимое" +#~ " будет закодировано в формате base64." +#~ msgstr "" +#~ "**content** (str или File): Содержимое " +#~ "файла. Если `file` установлен в True," +#~ " это будет ссылка на файл " +#~ "изображения; в противном случае содержимое " +#~ "будет закодировано в формате base64." + +#~ msgid "`IMask `_" +#~ msgstr "`IMask `_" + +#~ msgid "Effective only on frontend." +#~ msgstr "Действует только на фронтенде." + +#~ msgid "" +#~ "Default serializer with logic to work" +#~ " with objects. Read more in `DRF " +#~ "serializer's documentation `_ " +#~ "how to create Serializers and work " +#~ "with them." +#~ msgstr "" +#~ "Стандартный сериализатор с логикой работы " +#~ "с объектами. Читайте подробнее в " +#~ "`документации сериализатора `_ " +#~ "как создавать сериализаторы и работать с" +#~ " ними." + diff --git a/doc/locale/ru/LC_MESSAGES/frontend-manual.po b/doc/locale/ru/LC_MESSAGES/frontend-manual.po index 05642b14..0ca979bd 100644 --- a/doc/locale/ru/LC_MESSAGES/frontend-manual.po +++ b/doc/locale/ru/LC_MESSAGES/frontend-manual.po @@ -3,45 +3,46 @@ # This file is distributed under the same license as the VST Utils package. # FIRST AUTHOR , 2022. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-05-24 08:36+0000\n" +"POT-Creation-Date: 2024-01-10 23:37+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.12.1\n" +"Generated-By: Babel 2.14.0\n" #: ../../frontend-manual.rst:2 msgid "Frontend documentation" -msgstr "" +msgstr "Документация по фронтенду" #: ../../frontend-manual.rst:5 msgid "API Flowchart" -msgstr "" +msgstr "Схема API" #: ../../frontend-manual.rst:7 msgid "This flowchart shows how data goes though application from and to API." -msgstr "" +msgstr "Эта схема показывает, как данные проходят через приложение от и до API." #: ../../frontend-manual.rst:39 msgid "Signals" -msgstr "" +msgstr "Сигналы" #: ../../frontend-manual.rst:40 msgid "" "System of signals is a mechanism, that VST Utils uses for app " "customization." msgstr "" +"Система сигналов - это механизм, который VST Utils использует для " +"настройки приложения." #: ../../frontend-manual.rst:42 msgid "Let's look how it works." -msgstr "" +msgstr "Давайте посмотрим, как это работает." #: ../../frontend-manual.rst:44 msgid "" @@ -49,10 +50,13 @@ msgid "" "But how can you know about this event? And what if you need to know about" " this event in several blocks of code?" msgstr "" +"Очень часто вам нужно что-то изменить после того, как произошло какое-то " +"событие. Но как вы можете узнать о этом событии? А что, если вам нужно " +"знать об этом событии в нескольких блоках кода?" #: ../../frontend-manual.rst:47 msgid "To solve this problem VST Utils uses system of signals, where:" -msgstr "" +msgstr "Чтобы решить эту проблему, VST Utils использует систему сигналов, где:" #: ../../frontend-manual.rst:49 msgid "" @@ -60,6 +64,9 @@ msgid "" "has occurred, and pass some data/variables from the context, where this " "event has occurred;" msgstr "" +"вы можете отправить какой-то сигнал, который сообщит всем подписчикам, " +"что произошло какое-то событие, и передать некоторые данные/переменные из" +" контекста, где произошло это событие;" #: ../../frontend-manual.rst:51 msgid "" @@ -68,54 +75,65 @@ msgid "" "data/variables, that were passed from the context, where event had " "occurred." msgstr "" +"вы можете подписаться на какой-то сигнал, который уведомит вас о каком-то" +" событии, и вы также можете передать некоторый обратный вызов " +"(обработчик), который может делать что-то с данными/переменными, которые " +"были переданы из контекста, где произошло событие." #: ../../frontend-manual.rst:55 msgid "Emit signal" -msgstr "" +msgstr "Генерация сигнала" #: ../../frontend-manual.rst:56 msgid "To emit some signal you need to write following in you code:" -msgstr "" +msgstr "Чтобы сгенерировать сигнал, вам нужно написать следующее в своем коде:" #: ../../frontend-manual.rst:62 ../../frontend-manual.rst:86 msgid "where:" -msgstr "" +msgstr "где:" #: ../../frontend-manual.rst:64 ../../frontend-manual.rst:88 msgid "**name_of_signal** - string, which stores name of signal (event);" -msgstr "" +msgstr "**name_of_signal** - строка, которая хранит имя сигнала (события);" #: ../../frontend-manual.rst:65 msgid "" "**context** - some variable of any type, that will be passed to the " "callback (handler) during connection to this signal." msgstr "" +"**context** - некоторая переменная любого типа, которая будет передана в " +"обратный вызов (обработчик) во время подключения к этому сигналу." #: ../../frontend-manual.rst:67 msgid "Example of signal emitting:" -msgstr "" +msgstr "Пример генерации сигнала:" #: ../../frontend-manual.rst:79 msgid "Connect to signal" -msgstr "" +msgstr "Подключение к сигналу" #: ../../frontend-manual.rst:80 msgid "To connect to some signal you need to write following in you code:" msgstr "" +"Чтобы подключиться к какому-то сигналу, вам нужно написать следующее в " +"своем коде:" #: ../../frontend-manual.rst:89 msgid "" "**callback** - function, that can do something with variables, which will" " be passed from event's context to this callback as arguments." msgstr "" +"**callback** - функция, которая может выполнять действия с переменными, " +"которые будут переданы из контекста события в этот обратный вызов в " +"качестве аргументов." #: ../../frontend-manual.rst:91 msgid "Example of connecting to signal:" -msgstr "" +msgstr "Пример подключения к сигналу:" #: ../../frontend-manual.rst:104 msgid "List of signals in VST Utils" -msgstr "" +msgstr "Список сигналов в VST Utils" #: ../../frontend-manual.rst:105 msgid "" @@ -124,18 +142,24 @@ msgid "" "these signals and add callback function with desired behavior. Also you " "can emit you own signals in your project." msgstr "" +"VST Utils имеет несколько сигналов, которые генерируются во время работы " +"приложения. Если вам нужно настроить что-то в вашем проекте, вы можете " +"подписаться на эти сигналы и добавить функцию обратного вызова с желаемым" +" поведением. Также вы можете генерировать свои сигналы в своем проекте." #: ../../frontend-manual.rst:112 msgid "openapi.loaded" -msgstr "" +msgstr "openapi.loaded" #: ../../frontend-manual.rst:113 msgid "**Signal name:** \"openapi.loaded\"." -msgstr "" +msgstr "**Имя сигнала:** \"openapi.loaded\"." #: ../../frontend-manual.rst:115 msgid "**Context argument:** openapi - {object} - OpenAPI schema loaded from API." msgstr "" +"**Аргумент контекста:** openapi - {object} - OpenAPI-схема, загруженная " +"из API." #: ../../frontend-manual.rst:117 msgid "" @@ -143,46 +167,53 @@ msgid "" "You can use this signal if you need to change something in the OpenAPI " "schema, before it was parsed." msgstr "" +"**Описание:** Этот сигнал генерируется после загрузки схемы OpenAPI. Вы " +"можете использовать этот сигнал, если вам нужно что-то изменить в схеме " +"OpenAPI, прежде чем она будет разобрана." #: ../../frontend-manual.rst:121 msgid "resource.loaded" -msgstr "" +msgstr "resource.loaded" #: ../../frontend-manual.rst:122 msgid "**Signal name:** \"resource.loaded\"." -msgstr "" +msgstr "**Имя сигнала:** \"resource.loaded\"." #: ../../frontend-manual.rst:124 ../../frontend-manual.rst:132 msgid "**Context argument:** None." -msgstr "" +msgstr "**Аргумент контекста:** Нет." #: ../../frontend-manual.rst:126 msgid "" "**Description:** This signal is emitted after all static files were " "successfully loaded and added to the page." msgstr "" +"**Описание:** Этот сигнал генерируется после успешной загрузки всех " +"статических файлов и их добавления на страницу." #: ../../frontend-manual.rst:129 msgid "app.version.updated" -msgstr "" +msgstr "app.version.updated" #: ../../frontend-manual.rst:130 msgid "**Signal name:** \"app.version.updated\"." -msgstr "" +msgstr "**Имя сигнала:** \"app.version.updated\"." #: ../../frontend-manual.rst:134 msgid "" "**Description:** This signal is emitted during app loading if VST Utils " "detects, that version of your project was updated." msgstr "" +"**Описание:** Этот сигнал генерируется во время загрузки приложения, если" +" VST Utils обнаруживает, что версия вашего проекта была обновлена." #: ../../frontend-manual.rst:139 msgid "app.beforeInitRouter" -msgstr "" +msgstr "app.beforeInitRouter" #: ../../frontend-manual.rst:140 msgid "**Signal name:** \"app.beforeInitRouter\"." -msgstr "" +msgstr "**Имя сигнала:** \"app.beforeInitRouter\"." #: ../../frontend-manual.rst:142 msgid "" @@ -190,26 +221,33 @@ msgid "" "{routerConstructor: RouterConstructor}, where routerConstructor is an " "instance of RouterConstructor." msgstr "" +"**Аргумент контекста:** obj - {object} - Объект со следующей структурой: " +"{routerConstructor: RouterConstructor}, где routerConstructor - это " +"экземпляр RouterConstructor." #: ../../frontend-manual.rst:144 msgid "" "**Description:** This signal is emitted after creation of " "RouterConstructor instance and before app creation" msgstr "" +"**Описание:** Этот сигнал генерируется после создания экземпляра " +"RouterConstructor и перед созданием приложения." #: ../../frontend-manual.rst:148 msgid "app.beforeInit" -msgstr "" +msgstr "app.beforeInit" #: ../../frontend-manual.rst:149 msgid "**Signal name:** \"app.beforeInit\"." -msgstr "" +msgstr "**Имя сигнала:** \"app.beforeInit\"." #: ../../frontend-manual.rst:151 ../../frontend-manual.rst:160 msgid "" "**Context argument:** obj - {object} - Object with following structure: " "{app: app}, where app is an instance of App class." msgstr "" +"**Аргумент контекста:** obj - {object} - Объект со следующей структурой: " +"{app: app}, где app - это экземпляр класса App." #: ../../frontend-manual.rst:153 msgid "" @@ -217,48 +255,59 @@ msgid "" " (OpenAPI schema was parsed, models and views were created), but before " "app was mounted to the page." msgstr "" +"**Описание:** Этот сигнал генерируется после инициализации переменной " +"приложения (разбор схемы OpenAPI, создание моделей и представлений), но " +"перед тем, как приложение будет смонтировано на страницу." #: ../../frontend-manual.rst:157 msgid "app.afterInit" -msgstr "" +msgstr "app.afterInit" #: ../../frontend-manual.rst:158 msgid "**Signal name:** \"app.afterInit\"." -msgstr "" +msgstr "**Имя сигнала:** \"app.afterInit\"." #: ../../frontend-manual.rst:162 msgid "**Description:** This signal is emitted after app was mounted to the page." msgstr "" +"**Описание:** Этот сигнал генерируется после того, как приложение было " +"смонтировано на страницу." #: ../../frontend-manual.rst:165 msgid "app.language.changed" -msgstr "" +msgstr "app.language.changed" #: ../../frontend-manual.rst:166 msgid "**Signal name:** \"app.language.changed\"." -msgstr "" +msgstr "**Имя сигнала:** \"app.language.changed\"." #: ../../frontend-manual.rst:168 msgid "" "**Context argument:** obj - {object} - Object with following structure: " "{lang: lang}, where lang is an code of applied language." msgstr "" +"**Аргумент контекста:** obj - {object} - Объект со следующей структурой: " +"{lang: lang}, где lang - это код примененного языка." #: ../../frontend-manual.rst:170 msgid "" "**Description:** This signal is emitted after app interface language was " "changed." msgstr "" +"**Описание:** Этот сигнал генерируется после изменения языка интерфейса " +"приложения." #: ../../frontend-manual.rst:173 msgid "models[model_name].fields.beforeInit" -msgstr "" +msgstr "models[model_name].fields.beforeInit" #: ../../frontend-manual.rst:174 msgid "" "**Signal name:** \"models[\" + model_name + \"].fields.beforeInit\". For " "example, for User model: \"models[User].fields.beforeInit\"." msgstr "" +"**Имя сигнала:** \"models[\" + model_name + \"].fields.beforeInit\". " +"Например, для модели пользователя: \"models[User].fields.beforeInit\"." #: ../../frontend-manual.rst:176 msgid "" @@ -267,12 +316,17 @@ msgid "" " moment, field - is just object with options, it is not guiFields " "instance." msgstr "" +"**Контекстный аргумент:** fields - {object} - Объект с парами " +"ключ-значение, где ключ - имя поля, значение - объект с его опциями. На " +"данный момент поле - просто объект с опциями, это не экземпляр guiFields." #: ../../frontend-manual.rst:179 msgid "" "**Description:** This signal is emitted before creation of guiFields " "instances for Model fields." msgstr "" +"**Описание:** Этот сигнал генерируется перед созданием экземпляров " +"guiFields для полей модели." #: ../../frontend-manual.rst:182 msgid "models[model_name].fields.afterInit" @@ -283,18 +337,24 @@ msgid "" "**Signal name:** \"models[\" + model_name + \"].fields.afterInit\". For " "example, for User model: \"models[User].fields.afterInit\"." msgstr "" +"**Имя сигнала:** \"models[\" + model_name + \"].fields.afterInit\". " +"Например, для модели пользователя: \"models[User].fields.afterInit\"." #: ../../frontend-manual.rst:185 msgid "" "**Context argument:** fields - {object} - Object with pairs of key, " "value, where key - name of field, value - guiFields instance." msgstr "" +"**Контекстный аргумент:** fields - {object} - Объект с парами " +"ключ-значение, где ключ - имя поля, значение - экземпляр guiFields." #: ../../frontend-manual.rst:187 msgid "" "**Description:** This signal is emitted after creation of guiFields " "instances for Model fields." msgstr "" +"**Описание:** Этот сигнал генерируется после создания экземпляров " +"guiFields для полей модели." #: ../../frontend-manual.rst:190 msgid "models[model_name].created" @@ -305,16 +365,20 @@ msgid "" "**Signal name:** \"models[\" + model_name + \"].created\". For example, " "for User model: \"models[User].created\"." msgstr "" +"**Имя сигнала:** \"models[\" + model_name + \"].created\". Например, для " +"модели пользователя: \"models[User].created\"." #: ../../frontend-manual.rst:193 msgid "" "**Context argument:** obj - {object} - Object with following structure: " "{model: model}, where model is the created Model." msgstr "" +"**Контекстный аргумент:** obj - {object} - Объект со следующей " +"структурой: {model: model}, где model - созданная модель." #: ../../frontend-manual.rst:195 msgid "**Description:** This signal is emitted after creation of Model object." -msgstr "" +msgstr "**Описание:** Этот сигнал генерируется после создания объекта модели." #: ../../frontend-manual.rst:198 msgid "allModels.created" @@ -322,17 +386,20 @@ msgstr "" #: ../../frontend-manual.rst:199 msgid "**Signal name:** \"allModels.created\"." -msgstr "" +msgstr "**Имя сигнала:** \"allModels.created\"." #: ../../frontend-manual.rst:201 msgid "" "**Context argument:** obj - {object} - Object with following structure: " "{models: models}, where models is the object, storing Models objects." msgstr "" +"**Контекстный аргумент:** obj - {object} - Объект со следующей " +"структурой: {models: models}, где models - объект, хранящий объекты " +"моделей." #: ../../frontend-manual.rst:203 msgid "**Description:** This signal is emitted after all models were created." -msgstr "" +msgstr "**Описание:** Этот сигнал генерируется после создания всех моделей." #: ../../frontend-manual.rst:207 msgid "allViews.created" @@ -340,13 +407,16 @@ msgstr "" #: ../../frontend-manual.rst:208 msgid "**Signal name:** \"allViews.created\"." -msgstr "" +msgstr "**Имя сигнала:** \"allViews.created\"." #: ../../frontend-manual.rst:210 msgid "" "**Context argument:** obj - {object} - Object with following structure: " "{views: views}, where views - object with all View Instances." msgstr "" +"**Контекстный аргумент:** obj - {object} - Объект со следующей " +"структурой: {views: views}, где views - объект со всеми экземплярами " +"представлений." #: ../../frontend-manual.rst:213 msgid "" @@ -354,6 +424,9 @@ msgid "" "Instances, with set actions / child_links / multi_actions / operations / " "sublinks properties." msgstr "" +"**Описание:** Этот сигнал генерируется после создания всех экземпляров " +"представлений с установленными свойствами actions / child_links / " +"multi_actions / operations / sublinks." #: ../../frontend-manual.rst:217 msgid "routes[name].created" @@ -364,6 +437,8 @@ msgid "" "**Signal name:** \"routes[\" + name + \"].created\". For example, for " "``/user/`` view: \"routes[/user/].created\"." msgstr "" +"**Имя сигнала:** \"routes[\" + name + \"].created\". Например, для " +"представления ``/user/``: \"routes[/user/].created\"." #: ../../frontend-manual.rst:220 msgid "" @@ -372,12 +447,18 @@ msgid "" "route, path - template of route's path, component - component, that will " "be rendered for current route." msgstr "" +"**Контекстный аргумент:** route - {object} - Объект со следующей " +"структурой: {name: name, path: path, component: component}, где name - " +"имя маршрута, path - шаблон пути маршрута, component - компонент, который" +" будет отображаться для текущего маршрута." #: ../../frontend-manual.rst:223 msgid "" "**Description:** This signal will be emitted after route was formed and " "added to routes list." msgstr "" +"**Описание:** Этот сигнал будет генерироваться после формирования " +"маршрута и добавления его в список маршрутов." #: ../../frontend-manual.rst:226 msgid "allRoutes.created" @@ -385,7 +466,7 @@ msgstr "" #: ../../frontend-manual.rst:227 msgid "**Signal name:** \"allRoutes.created\"." -msgstr "" +msgstr "**Имя сигнала:** \"allRoutes.created\"." #: ../../frontend-manual.rst:229 msgid "" @@ -394,12 +475,18 @@ msgid "" "where name - name of route, path - template of route's path, component - " "component, that will be rendered for current route." msgstr "" +"**Контекстный аргумент:** routes - {array} - Массив объектов маршрутов со" +" следующей структурой: {name: name, path: path, component: component}, " +"где name - имя маршрута, path - шаблон пути маршрута, component - " +"компонент, который будет отображаться для текущего маршрута." #: ../../frontend-manual.rst:232 msgid "" "**Description:** This signal is emitted after all routes was formed and " "added to routes list." msgstr "" +"**Описание:** Этот сигнал генерируется после формирования всех маршрутов " +"и добавления их в список маршрутов." #: ../../frontend-manual.rst:235 msgid "<${PATH}>filterActions" @@ -407,17 +494,19 @@ msgstr "" #: ../../frontend-manual.rst:236 msgid "**Signal name:** \"<${PATH}>filterActions\"." -msgstr "" +msgstr "**Имя сигнала:** \"<${PATH}>filterActions\"." #: ../../frontend-manual.rst:238 msgid "" "**Context argument:** obj - {actions: Object[], data} - Actions is array " "of action objects. Data represents current instance's data." msgstr "" +"**Контекстный аргумент:** obj - {actions: Object[], data} - Actions - " +"массив объектов действий. Data представляет данные текущего экземпляра." #: ../../frontend-manual.rst:240 msgid "**Description:** This signal will be executed to filter actions." -msgstr "" +msgstr "**Описание:** Этот сигнал будет выполнен для фильтрации действий." #: ../../frontend-manual.rst:243 msgid "<${PATH}>filterSublinks" @@ -425,17 +514,19 @@ msgstr "" #: ../../frontend-manual.rst:244 msgid "**Signal name:** \"<${PATH}>filterSublinks\"." -msgstr "" +msgstr "**Имя сигнала:** \"<${PATH}>filterSublinks\"." #: ../../frontend-manual.rst:246 msgid "" "**Context argument:** obj - {sublinks: Object[], data} - Actions is array" " of sublink objects. Data represents current instance's data." msgstr "" +"**Контекстный аргумент:** obj - {sublinks: Object[], data} - Sublinks - " +"массив объектов подссылок. Data представляет данные текущего экземпляра." #: ../../frontend-manual.rst:248 msgid "**Description:** This signal will be executed to filter sublinks." -msgstr "" +msgstr "**Описание:** Этот сигнал будет выполнен для фильтрации подссылок." #: ../../frontend-manual.rst:253 msgid "Field Format" @@ -448,7 +539,7 @@ msgid "" "on). Create everytime similar functionality is rather boring and " "ineffective, so we tried ti solve this problem with the help of VST " "Utils." -msgstr "" +msgstr "Файловые форматы" #: ../../frontend-manual.rst:258 msgid "" @@ -458,82 +549,106 @@ msgid "" "value, just set appropriate field format to ``password`` instead of " "``string`` to show stars instead of actual characters." msgstr "" +"Часто при создании нового приложения разработчикам нужно создавать общие " +"поля некоторых базовых типов и форматов (строка, булево, число и так " +"далее). Создание каждый раз подобной функциональности довольно скучно и " +"неэффективно, поэтому мы попытались решить эту проблему с помощью VST " +"Utils." #: ../../frontend-manual.rst:264 msgid "" "Field classes are used in Model Instances as fields and also are used in " "Views Instances of ``list`` type as filters." msgstr "" +"VST Utils содержит набор встроенных полей наиболее распространенных типов" +" и форматов, которые могут использоваться в различных случаях. Например, " +"когда вам нужно добавить какое-то поле в веб-форму, которое должно " +"скрывать значение вставленного значения, просто установите " +"соответствующий формат поля на ``password`` вместо ``string``, чтобы " +"вместо фактических символов отображались звездочки." #: ../../frontend-manual.rst:266 msgid "" "All available fields classes are stored in the ``guiFields`` variable. " "There are 44 fields formats in VST Utils:" msgstr "" +"Все доступные классы полей хранятся в переменной ``guiFields``." +"На данный момент в VST Utils 44 формата полей:" #: ../../frontend-manual.rst:268 msgid "**base** - base field, from which the most other fields are inherited;" msgstr "" - +"**base** - базовое поле, все остальные поля наследуются от этого поля;" #: ../../frontend-manual.rst:269 msgid "" "**string** - string field, for inserting and representation of some short" " 'string' values;" msgstr "" +"**string** - строковое поле для вставки и представления коротких значений 'string';" #: ../../frontend-manual.rst:270 msgid "" "**textarea** - string field, for inserting and representation of some " "long 'string' values;" msgstr "" +"**textarea** - строковое поле для вставки и представления длинных значений 'string';" #: ../../frontend-manual.rst:271 msgid "" "**number** - number field, for inserting and representation of 'number' " "values;" msgstr "" +"**number** - числовое поле для вставки и представления значений 'number';" #: ../../frontend-manual.rst:272 msgid "" "**integer** - number field, for inserting and representation of values of" " 'integer' format;" msgstr "" +"**integer** - числовое поле для вставки и представления значений формата 'integer';" #: ../../frontend-manual.rst:273 msgid "" "**int32** - number field, for inserting and representation of values of " "'int32' format;" msgstr "" +"**int32** - числовое поле для вставки и представления значений формата 'int32';" #: ../../frontend-manual.rst:274 msgid "" "**int64** - number field, for inserting and representation of values of " "'int64' format;" msgstr "" +"**int64** - числовое поле для вставки и представления значений формата 'int64';" #: ../../frontend-manual.rst:275 msgid "" "**double** - number field, for inserting and representation of values of " "'double' format;" msgstr "" +"**double** - числовое поле для вставки и представления значений формата 'double';" #: ../../frontend-manual.rst:276 msgid "" "**float** - number field, for inserting and representation of values of " "'float' format;;" msgstr "" +"**float** - числовое поле для вставки и представления значений формата 'float';" #: ../../frontend-manual.rst:277 msgid "" "**boolean** - boolean field, for inserting and representation of " "'boolean' values;" msgstr "" +"**boolean** - логическое поле для вставки и представления значений 'boolean';" #: ../../frontend-manual.rst:278 msgid "" "**choices** - string field, with strict set of preset values, user can " "only choose one of the available value variants;" msgstr "" +"**choices** - строковое поле с жестким набором предопределенных значений," +" пользователь может выбрать только одно из доступных значений;" #: ../../frontend-manual.rst:279 msgid "" @@ -541,26 +656,33 @@ msgid "" "either choose one of the available value variants or insert his own " "value;" msgstr "" +"**autocomplete** - строковое поле с набором предопределенных значений, " +"пользователь может либо выбрать одно из доступных значений, либо ввести " +"свое собственное значение;" #: ../../frontend-manual.rst:280 msgid "**password** - string field, that hides inserted value by '*' symbols;" -msgstr "" +msgstr "**password** - строковое поле, скрывающее вставленное значение знаками '*'" #: ../../frontend-manual.rst:281 msgid "**file** - string field, that can read content of the file;" -msgstr "" +msgstr "**file** - строковое поле, способное считывать содержимое файла;" #: ../../frontend-manual.rst:282 msgid "" "**secretfile** - string field, that can read content of the file and then" " hide it from representation;" msgstr "" +"**secretfile** - строковое поле, которое может считать содержимое файла, " +"а затем скрыть его при отображении;" #: ../../frontend-manual.rst:283 msgid "" "**binfile** - string field, that can read content of the file and convert" " it to the 'base64' format;" msgstr "" +"**binfile** - строковое поле, которое может считать содержимое файла и " +"преобразовывать его в формат 'base64';" #: ../../frontend-manual.rst:284 msgid "" @@ -568,6 +690,9 @@ msgid "" " 2 properties: name (string) - name of file and content(base64 string) - " "content of file;" msgstr "" +"**namedbinfile** - поле в формате JSON, которое принимает и возвращает " +"JSON с 2 свойствами: name (строка) - имя файла и content (строка base64) " +"- содержание файла;" #: ../../frontend-manual.rst:285 msgid "" @@ -575,6 +700,9 @@ msgid "" "with 2 properties: name (string) - name of image and content(base64 " "string) - content of image;" msgstr "" +"**namedbinimage** - поле в формате JSON, которое принимает и возвращает " +"JSON с 2 свойствами: name (строка) - имя изображения и content (строка " +"base64) - содержание изображения;" #: ../../frontend-manual.rst:286 msgid "" @@ -582,6 +710,9 @@ msgid "" "array with objects, consisting of 2 properties: name (string) - name of " "file and content(base64 string) - content of file;" msgstr "" +"**multiplenamedbinimage** - поле в формате JSON, которое принимает и " +"возвращает массив объектов, состоящих из 2 свойств: name (строка) - имя " +"изображения и content (строка base64) - содержание изображения;" #: ../../frontend-manual.rst:287 msgid "" @@ -589,36 +720,49 @@ msgid "" "array with objects, consisting of 2 properties: name (string) - name of " "image and content(base64 string) - content of image;" msgstr "" +"**multiplenamedbinimage** - поле в формате JSON, которое принимает и " +"возвращает массив объектов, состоящих из 2 свойств: name (строка) - имя " +"изображения и content (строка base64) - содержание изображения;" #: ../../frontend-manual.rst:288 msgid "" "**text_paragraph** - string field, that is represented as text paragraph " "(without any inputs);" msgstr "" +"**text_paragraph** - строковое поле, представленное в виде текстового " +"абзаца (без каких-либо вводов);" #: ../../frontend-manual.rst:289 msgid "" "**plain_text** - string field, that saves all non-printing characters " "during representation;" msgstr "" +"**plain_text** - строковое поле, сохраняющее все непечатные символы во " +"время представления;" #: ../../frontend-manual.rst:290 msgid "" "**html** - string field, that contents different html tags and that " "renders them during representation;" msgstr "" +"**html** - строковое поле, содержащее различные теги HTML и отображающее " +"их во время представления;" #: ../../frontend-manual.rst:291 msgid "" "**date** - date field, for inserting and representation of 'date' values " "in 'YYYY-MM-DD' format;" msgstr "" +"**date** - поле даты для вставки и представления значений 'date' в " +"формате 'YYYY-MM-DD';" #: ../../frontend-manual.rst:292 msgid "" "**date_time** - date field, for inserting and representation of 'date' " "values in 'YYYY-MM-DD HH:mm' format;" msgstr "" +"**date_time** - поле даты для вставки и представления значений 'date' в " +"формате 'YYYY-MM-DD HH:mm';" #: ../../frontend-manual.rst:293 msgid "" @@ -627,24 +771,34 @@ msgid "" "00:00:00 / 01m 30d 00:00:00 / 99y 11m 30d 22:23:24) due to the it's value" " size;" msgstr "" +"**uptime** - строковое поле, которое преобразует продолжительность " +"времени (количество секунд) в один из наиболее подходящих вариантов " +"(23:59:59 / 01d 00:00:00 / 01m 30d 00:00:00 / 99y 11m 30d 22:23:24) в " +"зависимости от его размера;" #: ../../frontend-manual.rst:295 msgid "" "**time_interval** - number field, that converts time from milliseconds " "into seconds;" msgstr "" +"**time_interval** - числовое поле, которое преобразует время из " +"миллисекунд в секунды;" #: ../../frontend-manual.rst:296 msgid "" "**crontab** - string field, that has additional form for creation " "schedule in 'crontab' format;" msgstr "" +"**crontab** - строковое поле, которое имеет дополнительную форму для " +"создания расписания в формате 'crontab';" #: ../../frontend-manual.rst:297 msgid "" "**json** - field of JSON format, during representation it uses another " "guiFields for representation of current field properties;" msgstr "" +"**json** - поле в формате JSON, во время представления использует другие " +"guiFields для представления текущих свойств поля;" #: ../../frontend-manual.rst:298 msgid "" @@ -652,6 +806,9 @@ msgid "" "Instance from API (value of this field is the whole Model Instance data)." " This is read only field;" msgstr "" +"**api_object** - поле, используемое для представления некоторого " +"экземпляра модели из API (значение этого поля - все данные экземпляра " +"модели). Это только для чтения поле;" #: ../../frontend-manual.rst:300 msgid "" @@ -659,6 +816,10 @@ msgid "" "from API (value of this field is the Model Instance Primary Key). During " "edit mode this field has strict set of preset values to choose;" msgstr "" +"**fk** - поле, используемое для представления некоторого экземпляра " +"модели из API (значение этого поля - первичный ключ экземпляра модели). " +"Во время режима редактирования у этого поля есть строгий набор " +"предопределенных значений для выбора;" #: ../../frontend-manual.rst:302 msgid "" @@ -667,6 +828,11 @@ msgid "" "Primary Key or some string). During edit mode user can either choose of " "the preset values from autocomplete list or insert his own value;" msgstr "" +"**fk_autocomplete** - поле, используемое для представления некоторого " +"экземпляра модели из API (значение этого поля - первичный ключ экземпляра" +" модели или некоторая строка). Во время режима редактирования " +"пользователь может либо выбрать одно из предопределенных значений из " +"списка автозаполнения, либо ввести свое собственное значение;" #: ../../frontend-manual.rst:304 msgid "" @@ -675,30 +841,39 @@ msgid "" "Primary Key or some string). During edit mode user can either choose of " "the preset values from modal window or insert his own value;" msgstr "" +"**fk_multi_autocomplete** - поле, используемое для представления " +"некоторого экземпляра модели из API (значение этого поля - первичный ключ" +" экземпляра модели или некоторая строка). Во время режима редактирования " +"пользователь может либо выбрать одно из предопределенных значений из " +"модального окна, либо ввести свое собственное значение;" #: ../../frontend-manual.rst:306 msgid "**color** - string field, that stores HEX code of selected color;" msgstr "" +"**color** - строковое поле, которое сохраняет шестнадцатеричный код " +"выбранного цвета;" #: ../../frontend-manual.rst:307 msgid "" "**inner_api_object** - field, that is linked to the fields of another " "model;" -msgstr "" +msgstr "**inner_api_object** - поле, которое связано с полями другой модели;" #: ../../frontend-manual.rst:308 msgid "**api_data** - field for representing some data from API;" -msgstr "" +msgstr "**api_data** - поле для представления некоторых данных из API;" #: ../../frontend-manual.rst:309 msgid "" "**dynamic** - field, that can change its format depending on the values " "of surrounding fields;" msgstr "" +"**dynamic** - поле, которое может изменять свой формат в зависимости от " +"значений окружающих полей;" #: ../../frontend-manual.rst:310 msgid "**hidden** - field, that is hidden from representation;" -msgstr "" +msgstr "**hidden** - поле, скрытое от представления;" #: ../../frontend-manual.rst:311 msgid "" @@ -706,16 +881,19 @@ msgid "" "values as one JSON, where key - name of form field, value - value of form" " field;" msgstr "" +"**form** - поле, объединяющее несколько других полей и сохраняющее их " +"значения как один JSON, где key - имя поля формы, value - значение поля " +"формы;" #: ../../frontend-manual.rst:313 msgid "**button** - special field for form field, imitates button in form;" -msgstr "" +msgstr "**button** - специальное поле для формы, имитирующее кнопку в форме;" #: ../../frontend-manual.rst:314 msgid "" "**string_array** - field, that converts array with strings into one " "string;" -msgstr "" +msgstr "**string_array** - поле, преобразующее массив строк в одну строку;" #: ../../frontend-manual.rst:315 msgid "" @@ -723,10 +901,13 @@ msgid "" " key. It has additional validation, that checks, that field's value is " "not equal to some other URL keys (new/ edit/ remove)." msgstr "" +"**string_id** - строковое поле, предназначенное для использования в URL " +"как ключ 'id'. Он имеет дополнительную проверку, которая проверяет, что " +"значение поля не равно некоторым другим ключам URL (new/ edit/ remove)." #: ../../frontend-manual.rst:319 msgid "Layout customization with CSS" -msgstr "" +msgstr "Настройка макета с использованием CSS" #: ../../frontend-manual.rst:320 msgid "" @@ -737,37 +918,49 @@ msgid "" " for the fields with \"boolean\" and \"choices\" types. Also classes " "apply to operations buttons and links." msgstr "" +"Если вам нужно настроить элементы с помощью CSS, у нас есть некоторая " +"функциональность для этого. К корневым элементам ``EntityView`` (если он " +"содержит `ModelField`), ``ModelField``, ``ListTableRow`` и " +"``MultiActions`` применяются классы в зависимости от полей, которые они " +"содержат. Классы формируются для полей типов \"boolean\" и \"choices\". " +"Кроме того, классы применяются к кнопкам операций и ссылкам." #: ../../frontend-manual.rst msgid "Classes generation rules" -msgstr "" +msgstr "Правила генерации классов" #: ../../frontend-manual.rst:327 msgid "" "``EntityView, ModelField and ListTableRow`` - *field-[field_name]-[field-" "value]*" msgstr "" +"``EntityView, ModelField и ListTableRow`` - *field-[field_name]-[field-" +"value]*" #: ../../frontend-manual.rst:332 ../../frontend-manual.rst:336 #: ../../frontend-manual.rst:345 msgid "**Example:**" -msgstr "" +msgstr "**Пример:**" #: ../../frontend-manual.rst:330 msgid "" "*\"field-active-true\"* for model that contains \"boolean\" field with " "name \"active\" and value \"true\"" msgstr "" +"*\"field-active-true\"* для модели, содержащей поле \"boolean\" с именем " +"\"active\" и значением \"true\"" #: ../../frontend-manual.rst:331 msgid "" "*\"field-tariff_type-WAREHOUSE\"* for model that contains \"choices\" " "field with name \"tariff_type\" and value \"WAREHOUSE\"" msgstr "" +"*\"field-tariff_type-WAREHOUSE\"* для модели, содержащей поле \"choices\"" +" с именем \"tariff_type\" и значением \"WAREHOUSE\"" #: ../../frontend-manual.rst:336 msgid "``MultiActions`` - *selected__field-[field_name]-[field-value]*" -msgstr "" +msgstr "``MultiActions`` - *selected__field-[field_name]-[field-value]*" #: ../../frontend-manual.rst:336 msgid "" @@ -776,40 +969,51 @@ msgid "" "\"choices\" field with name \"tariff_type\" and values \"WAREHOUSE\" and " "\"SLIDE\" respectively." msgstr "" +"*\"selected__field-tariff_type-WAREHOUSE\"* и *\"selected__field-" +"tariff_type-SLIDE\"*, если выбраны 2 строки ``ListTableRow``, содержащие " +"поле \"choices\" с именем \"tariff_type\" и значениями \"WAREHOUSE\" и " +"\"SLIDE\" соответственно." #: ../../frontend-manual.rst:345 msgid "``Operation`` - *operation__[operation_name]*" -msgstr "" +msgstr "``Operation`` - *operation__[operation_name]*" #: ../../frontend-manual.rst:342 msgid "**Warning**" -msgstr "" +msgstr "**Предупреждение**" #: ../../frontend-manual.rst:340 msgid "" "If you hide operations using CSS classes and for example all actions were" " hidden then Actions dropdown button will still be visible." msgstr "" +"Если вы скрываете операции с использованием классов CSS и, например, все " +"действия были скрыты, то кнопка выпадающего списка действий все равно " +"будет видна." #: ../../frontend-manual.rst:342 msgid "" "For better control over actions and sublinks see :ref:`changing-actions-" "or-sublinks`" msgstr "" +"Для более тщательного контроля над действиями и подссылками см. :ref" +":`changing-actions-or-sublinks`" #: ../../frontend-manual.rst:345 msgid "" "*operation__pickup_point* if operation button or link has name " "*pickup_point*" msgstr "" +"*operation__pickup_point*, если кнопка операции или ссылка имеют имя " +"*pickup_point*" #: ../../frontend-manual.rst:347 msgid "Based on these classes, you can change the styles of various elements." -msgstr "" +msgstr "На основе этих классов вы можете изменять стили различных элементов." #: ../../frontend-manual.rst:373 msgid "A few use cases:" -msgstr "" +msgstr "Несколько вариантов использования:" #: ../../frontend-manual.rst:350 msgid "" @@ -817,18 +1021,25 @@ msgid "" "product detail view when product is not \"active\", you can do so by " "adding a CSS selector:" msgstr "" +"Если вам нужно скрыть кнопку для действия \"change_category\" на странице" +" подробной информации о продукте, когда продукт неактивен, вы можете " +"сделать это, добавив селектор CSS:" #: ../../frontend-manual.rst:358 msgid "" "Hide the button for the \"remove\" action in ``MultiActions`` menu if " "selected at least one product with status \"active\":" msgstr "" +"Скрыть кнопку для действия \"remove\" в меню ``MultiActions``, если " +"выбран хотя бы один продукт со статусом \"active\":" #: ../../frontend-manual.rst:366 msgid "" "If you need to change *background-color* to red for order with status " "\"CANCELLED\" on ``ListView`` component do this:" msgstr "" +"Если вам нужно изменить *цвет фона* на красный для заказа со статусом " +"\"CANCELLED\" на компоненте ``ListView``, сделайте следующее:" #: ../../frontend-manual.rst:374 msgid "" @@ -837,87 +1048,8 @@ msgid "" "selected in the selector, because the class \"field-status-CANCELLED\" is" " added in different places on the page." msgstr "" - -#~ msgid "app.beforeInitStore" -#~ msgstr "" - -#~ msgid "**Signal name:** \"app.beforeInitStore\"." -#~ msgstr "" - -#~ msgid "" -#~ "**Context argument:** obj - {object} -" -#~ " Object with following structure: " -#~ "{storeConstructor: StoreConstructor}, where " -#~ "storeConstructor is an instance of " -#~ "StoreConstructor." -#~ msgstr "" - -#~ msgid "" -#~ "**Description:** This signal is emitted " -#~ "after creation of StoreConstructor instance" -#~ " and before app creation" -#~ msgstr "" - -#~ msgid "GuiCustomizer.beforeInit" -#~ msgstr "" - -#~ msgid "**Signal name:** \"GuiCustomizer.beforeInit\"." -#~ msgstr "" - -#~ msgid "**Context argument:** obj - {object} - Instance of GuiCustomizer class." -#~ msgstr "" - -#~ msgid "" -#~ "**Description:** This signal will be " -#~ "executed before initialization of " -#~ "GuiCustomizer Instance." -#~ msgstr "" - -#~ msgid "GuiCustomizer.skins_custom_settings.reseted" -#~ msgstr "" - -#~ msgid "**Signal name:** \"GuiCustomizer.skins_custom_settings.reseted\"." -#~ msgstr "" - -#~ msgid "" -#~ "**Description:** This signal will be " -#~ "executed after custom settings of " -#~ "GuiCustomizer skin were reset." -#~ msgstr "" - -#~ msgid "GuiCustomizer.skins_custom_settings.saved" -#~ msgstr "" - -#~ msgid "**Signal name:** \"GuiCustomizer.skins_custom_settings.saved\"." -#~ msgstr "" - -#~ msgid "" -#~ "**Description:** This signal will be " -#~ "executed after custom settings of " -#~ "GuiCustomizer skin were saved." -#~ msgstr "" - -#~ msgid "GuiCustomizer.skin.name.changed" -#~ msgstr "" - -#~ msgid "**Signal name:** \"GuiCustomizer.skin.name.changed\"." -#~ msgstr "" - -#~ msgid "" -#~ "**Description:** This signal will be " -#~ "executed after changing of current " -#~ "GuiCustomizer skin." -#~ msgstr "" - -#~ msgid "GuiCustomizer.skin.settings.changed" -#~ msgstr "" - -#~ msgid "**Signal name:** \"GuiCustomizer.skin.settings.changed\"." -#~ msgstr "" - -#~ msgid "" -#~ "**Description:** This signal will be " -#~ "executed after changing of current " -#~ "GuiCustomizer skin's settings." -#~ msgstr "" +"В этом случае вам нужно использовать дополнительный класс \"item-row\" " +"(Используется для напримера, вы можете выбрать другой) для указания элемента," +" который будет выбран в селекторе, потому что класс \"field-status-" +"CANCELLED\" добавляется в различные места на странице." diff --git a/doc/locale/ru/LC_MESSAGES/quickstart-front.po b/doc/locale/ru/LC_MESSAGES/quickstart-front.po index 6a18aaec..c2ed105d 100644 --- a/doc/locale/ru/LC_MESSAGES/quickstart-front.po +++ b/doc/locale/ru/LC_MESSAGES/quickstart-front.po @@ -16,10 +16,9 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.12.1\n" - #: ../../quickstart-front.rst:2 msgid "Frontend Quickstart" -msgstr "" +msgstr "Быстрый старт фронтенда" #: ../../quickstart-front.rst:5 msgid "" @@ -28,50 +27,59 @@ msgid "" "frontend features. App installation and setting up described in - " ":doc:`Backend Section ` of this docs." msgstr "" +"Фреймворк VST utils использует экосистему Vue для отображения фронтенда. " +"Руководство по быстрому старту проведет вас через наиболее важные шаги по " +"настройке функций фронтенда. Установка приложения и настройка описаны в - " +":doc:`разделе Backend ` этой документации." #: ../../quickstart-front.rst:8 msgid "There are several stages in vstutils app:" -msgstr "" +msgstr "Есть несколько этапов в приложении VST utils:" #: ../../quickstart-front.rst:10 msgid "Before app started:" -msgstr "" +msgstr "Перед запуском приложения:" #: ../../quickstart-front.rst:12 msgid "" "`checkCacheVersions()` checks if app version has been changed since last " "visit and cleans all cached data if so;" msgstr "" +"`checkCacheVersions()` проверяет, была ли изменена версия приложения с " +"последнего посещения и очищает все кэшированные данные, если это так;" #: ../../quickstart-front.rst:13 msgid "loading open api schema from backend. Emits 'openapi.loaded' signal;" msgstr "" +"загрузка схемы OpenAPI с бэкенда. Генерирует сигнал 'openapi.loaded';" #: ../../quickstart-front.rst:14 msgid "loading all static files from `SPA_STATIC` in setting.py;" -msgstr "" +msgstr "загрузка всех статических файлов из `SPA_STATIC` в setting.py;" #: ../../quickstart-front.rst:15 msgid "sets `AppConfiguration` from OpenAPI schema;" -msgstr "" +msgstr "устанавливает `AppConfiguration` из схемы OpenAPI;" #: ../../quickstart-front.rst:17 msgid "App started:" -msgstr "" +msgstr "Приложение запущено:" #: ../../quickstart-front.rst:19 msgid "" "if there is centrifugoClient in settings.py connects it. To read more " "about centrifugo configuration check \":ref:`centrifugo`\";" msgstr "" +"если в settings.py есть centrifugoClient, подключается к нему. Дополнительные " +"сведения о конфигурации centrifugo можно найти в разделе \":ref:`centrifugo`\";" #: ../../quickstart-front.rst:20 msgid "downloading a list of available languages and translations;" -msgstr "" +msgstr "загрузка списка доступных языков и переводов;" #: ../../quickstart-front.rst:21 msgid "`api.loadUser()` returns user data;" -msgstr "" +msgstr "`api.loadUser()` возвращает данные пользователя;" #: ../../quickstart-front.rst:22 msgid "" @@ -79,88 +87,104 @@ msgid "" "`models[${modelName}].created` for each created model and " "`allModels.created` when all models created;" msgstr "" +"`ModelsResolver` создает модели из схемы, генерирует сигнал " +"`models[${modelName}].created` для каждой созданной модели и " +"`allModels.created`, когда все модели созданы;" #: ../../quickstart-front.rst:23 msgid "" "`ViewConstructor.generateViews()` inits `View` fieldClasses and " "modelClasses;" msgstr "" +"`ViewConstructor.generateViews()` инициализирует `View` fieldClasses и " +"modelClasses;" #: ../../quickstart-front.rst:24 msgid "" "`QuerySetsResolver` finds appropriate queryset by model name and view " "path;" msgstr "" +"`QuerySetsResolver` находит соответствующий queryset по имени модели и " +"пути представления;" #: ../../quickstart-front.rst:25 msgid "`global_components.registerAll()` registers Vue `global_components`;" -msgstr "" +msgstr "`global_components.registerAll()` регистрирует Vue `global_components`;" #: ../../quickstart-front.rst:26 msgid "`prepare()` emits `app.beforeInit` with { app: this };" -msgstr "" +msgstr "`prepare()` генерирует сигнал `app.beforeInit` с { app: this };" #: ../../quickstart-front.rst:27 msgid "" "initialize model with `LocalSettings`. Find out more about this in the " "section :ref:`localSettings`;" msgstr "" +"инициализация модели с `LocalSettings`. Узнайте больше об этом в разделе :ref:`localSettings`;" #: ../../quickstart-front.rst:28 msgid "" "creates routerConstructor from `this.views`, emits 'app.beforeInitRouter'" " with { routerConstructor } and gets new VueRouter({this.routes});" msgstr "" +"создание routerConstructor из `this.views`, генерация сигнала 'app.beforeInitRouter'" +" с { routerConstructor } и получение нового VueRouter({this.routes});" #: ../../quickstart-front.rst:29 msgid "" "inits application `Vue()` from schema.info, pinia store and emits " "'app.afterInit' with {app: this};" msgstr "" +"инициализация приложения `Vue()` из schema.info, хранилища pinia и " +"генерация сигнала 'app.afterInit' с {app: this};" #: ../../quickstart-front.rst:31 msgid "Application mounted." -msgstr "" +msgstr "Приложение смонтировано." #: ../../quickstart-front.rst:34 msgid "" "There is a flowchart representing application initialization process " "(signal names have red font):" msgstr "" +"Есть схема, представляющая процесс инициализации приложения (названия сигналов красным шрифтом):" #: ../../quickstart-front.rst:75 msgid "Field customization" -msgstr "" +msgstr "Настройка поля" #: ../../quickstart-front.rst:76 msgid "To add custom script to the project, set script name in settings.py" -msgstr "" +msgstr "Чтобы добавить собственный скрипт в проект, укажите его имя в settings.py" #: ../../quickstart-front.rst:85 msgid "and put the script (`main.js`) in `{appName}/static/` directory." -msgstr "" +msgstr "и поместите скрипт (`main.js`) в каталог `{appName}/static/`." #: ../../quickstart-front.rst:87 msgid "" "In `main.js` create new field by extending it from BaseField (or any " "other appropriate field)" msgstr "" +"В `main.js` создайте новое поле, расширив его от BaseField (или любого другого соответствующего поля)" #: ../../quickstart-front.rst:89 msgid "" "For example lets create a field that renders HTML h1 element with 'Hello " "World!` text:" msgstr "" +"Например, создадим поле, которое отображает элемент HTML h1 с текстом 'Привет, мир!'" #: ../../quickstart-front.rst:103 msgid "Or render person's name with some prefix" -msgstr "" +msgstr "Или отобразите имя человека с некоторым префиксом" #: ../../quickstart-front.rst:118 msgid "" "Register this field to `app.fieldsResolver` to provide appropriate field " "format and type to a new field" msgstr "" +"Зарегистрируйте это поле в `app.fieldsResolver`, чтобы предоставить соответствующий формат и тип поля для нового поля" #: ../../quickstart-front.rst:125 msgid "" @@ -168,12 +192,14 @@ msgid "" "`models[ModelWithFieldToChange].fields.beforeInit` signal to change field" " Format" msgstr "" +"Слушайте подходящий сигнал `models[ModelWithFieldToChange].fields.beforeInit` для изменения формата поля" #: ../../quickstart-front.rst:134 msgid "" "List of models and their fields is available during runtime in console " "at `app.modelsClasses`" msgstr "" +"Список моделей и их полей доступен во время выполнения в консоли по адресу `app.modelsClasses`" #: ../../quickstart-front.rst:136 msgid "" @@ -182,12 +208,17 @@ msgid "" "wants to type in number of seconds. A solution would be to override " "field's `toInner` and `toRepresent` methods." msgstr "" +"Чтобы изменить поведение поля, создайте новый класс поля с необходимой логикой. " +"Допустим, вам нужно отправить API количество миллисекунд, но пользователь хочет " +"вводить количество секунд. Решением будет переопределить методы `toInner` и `toRepresent` поля." #: ../../quickstart-front.rst:157 msgid "" "Now you have field that show seconds, but saves/receives data in " "milliseconds on detail view of AllFieldsModel." msgstr "" +"Теперь у вас есть поле, которое отображает секунды, но сохраняет/получает данные в " +"миллисекундах в виде детализированного представления модели AllFieldsModel." #: ../../quickstart-front.rst:160 msgid "" @@ -195,10 +226,13 @@ msgid "" "use field `warn` and `error` methods. You can pass some message and it " "will print it with field type, model name and field name." msgstr "" +"Если вам нужно показать какое-то предупреждение или ошибку в консоли разработчика, " +"вы можете использовать методы `warn` и `error` поля. Вы можете передать сообщение, " +"и оно будет выведено с типом поля, именем модели и именем поля." #: ../../quickstart-front.rst:164 msgid "Change path to FkField" -msgstr "" +msgstr "Изменение пути к полю FkField" #: ../../quickstart-front.rst:165 msgid "" @@ -207,6 +241,10 @@ msgid "" "endpoint on backend and set FkField request path to `famous_author`. " "Listen for `app.beforeInit` signal." msgstr "" +"Иногда вам может потребоваться запросить другой набор объектов для FkField. " +"Например, чтобы выбрать только известных авторов, создайте конечную точку `famous_author` " +"на бэкенде и установите путь запроса FkField в `famous_author`. " +"Слушайте сигнал `app.beforeInit`." #: ../../quickstart-front.rst:175 msgid "" @@ -215,14 +253,17 @@ msgid "" "different set of authors (that may have been previously filtered on " "backend)." msgstr "" +"Теперь, когда мы создаем новый пост на конечной точке `/post/`, Author FkField выполняет GET-запрос " +"к `/famous_author/` вместо `/author/`. Это полезно для получения другого набора авторов (которые могли быть " +"ранее отфильтрованы на бэкенде)." #: ../../quickstart-front.rst:179 msgid "CSS Styling" -msgstr "" +msgstr "Стилизация CSS" #: ../../quickstart-front.rst:181 msgid "Like scripts, css files may be added to SPA_STATIC in setting.py" -msgstr "" +msgstr "Как и скрипты, файлы CSS можно добавить в SPA_STATIC в setting.py" #: ../../quickstart-front.rst:192 msgid "" @@ -230,52 +271,64 @@ msgid "" "format-customField` and generated with `column-format-{Field.format}` " "pattern." msgstr "" +"Давайте проанализируем страницу и найдем класс CSS для нашего пользовательского поля. " +"Это `column-format-customField` и создается с использованием шаблона `column-format-{Field.format}`." #: ../../quickstart-front.rst:195 msgid "Use regular css styling to change appearance of the field." -msgstr "" +msgstr "Используйте обычные стили CSS для изменения внешнего вида поля." #: ../../quickstart-front.rst:205 msgid "" "Other page elements are also available for styling: for example, to hide " "certain column set corresponding field to none." msgstr "" +"Другие элементы страницы также доступны для стилизации: например, чтобы скрыть " +"определенный столбец, установите соответствующее поле в значение none." #: ../../quickstart-front.rst:214 msgid "Show primary key column on list" -msgstr "" +msgstr "Показать столбец первичных ключей в списке" #: ../../quickstart-front.rst:216 msgid "" "Every pk column has `pk-column` CSS class and hidden by default (using " "`display: none;`)." msgstr "" +"Каждый столбец первичного ключа имеет класс CSS `pk-column` и по умолчанию " +"скрыт (с использованием `display: none;`)." #: ../../quickstart-front.rst:218 msgid "" "For example this style will show pk column on all list views of `Order` " "model:" msgstr "" +"Например, этот стиль покажет столбец первичных ключей во всех представлениях " +"списка модели `Order`." #: ../../quickstart-front.rst:228 msgid "View customization" -msgstr "" +msgstr "Настройка представления" #: ../../quickstart-front.rst:230 msgid "" "Listen for signal `\"allViews.created\"` and add new custom mixin to the " "view." msgstr "" +"Слушайте сигнал `\"allViews.created\"` и добавьте новый пользовательский " +"миксин в представление." #: ../../quickstart-front.rst:232 msgid "Next code snippet depicts rendering new view instead of default view." -msgstr "" +msgstr "В следующем фрагменте кода показано отображение нового представления вместо представления по умолчанию." #: ../../quickstart-front.rst:245 msgid "" "Learn more about Vue `render()` function at `Vue documentation " "`_." msgstr "" +"Узнайте больше о функции `render()` Vue в `документации Vue " +"`_." #: ../../quickstart-front.rst:248 msgid "" @@ -283,6 +336,9 @@ msgid "" "properties and methods of existing mixins. For example, override " "breadcrumbs computed property to turn off breadcrumbs on Author list View" msgstr "" +"Также можно настроить представление, переопределив вычисляемые свойства и " +"методы по умолчанию существующих миксинов. Например, переопределите вычисляемое " +"свойство `breadcrumbs` для отключения хлебных крошек в представлении списка авторов." #: ../../quickstart-front.rst:265 msgid "" @@ -291,20 +347,26 @@ msgid "" "you also should listen signal `\"allViews.created\"` and change parameter" " `hidden` from default `false` to `true`, for example:" msgstr "" +"Иногда вам может потребоваться скрыть страницу с деталями по какой-то причине, " +"но при этом сохранить доступ ко всем действиям и подссылкам с страницы списка. " +"Чтобы сделать это, также следует слушать сигнал `\"allViews.created\"` и изменить параметр " +"`hidden` с значения `false` по умолчанию на `true`, например:" #: ../../quickstart-front.rst:277 msgid "Changing title of the view" -msgstr "" +msgstr "Изменение заголовка представления" #: ../../quickstart-front.rst:279 msgid "" "To change title and string displayed in the breadcrumbs change `title` " "property of the view or method `getTitle` for more complex logic." msgstr "" +"Чтобы изменить заголовок и строку, отображаемую в хлебных крошках, измените " +"свойство `title` представления или метод `getTitle` для более сложной логики." #: ../../quickstart-front.rst:293 msgid "Basic Webpack configuration" -msgstr "" +msgstr "Базовая конфигурация Webpack" #: ../../quickstart-front.rst:294 msgid "" @@ -315,12 +377,20 @@ msgid "" "from root dir of your project to build static files. Final step is to add" " built file to `SPA_STATIC` in `settings.py`" msgstr "" +"Чтобы использовать webpack в вашем проекте, переименуйте `webpack.config.js.default` в " +"`webpack.config.js`. Каждый проект, основанный на vst-utils, содержит `index.js`" +" в каталоге `/frontend_src/app/`. Этот файл предназначен для вашего кода. " +"Запустите команду `yarn` для установки всех зависимостей. Затем выполните `yarn devBuild` " +"из корневого каталога вашего проекта для сборки статических файлов. Последним шагом является добавление " +"собранного файла в `SPA_STATIC` в `settings.py`." #: ../../quickstart-front.rst:305 msgid "" "Webpack configuration file allows to add more static files. In " "`webpack.config.js` add more entries" msgstr "" +"Файл конфигурации Webpack позволяет добавлять дополнительные статические файлы. В " +"`webpack.config.js` добавьте дополнительные записи" #: ../../quickstart-front.rst:316 msgid "" @@ -332,24 +402,35 @@ msgid "" "installation trough `pip` frontend code are being build automatically, so" " you may need to add `bundle` directory to `gitignore`." msgstr "" +"Выходные файлы будут собраны в каталоге " +"`frontend_src/{AppName}/static/{AppName}/bundle`. Имя " +"выходного файла соответствует имени записи в `config`. В приведенном выше " +"примере выходные файлы будут называться `app.js` и `myapp.js`. Добавьте все эти " +"файлы в `STATIC_SPA` в `settings.py`. Во время установки vstutils через `pip` " +"фронтенд-код собирается автоматически, поэтому вам может потребоваться добавить " +"каталог `bundle` в `gitignore`." #: ../../quickstart-front.rst:323 msgid "Page store" -msgstr "" +msgstr "Хранилище страницы" #: ../../quickstart-front.rst:324 msgid "" "Every page has store that can be accessed globally `app.store.page` or " "from page component using `this.store`." msgstr "" +"У каждой страницы есть хранилище, которое можно получить глобально `app.store.page` " +"или из компонента страницы с использованием `this.store`." #: ../../quickstart-front.rst:326 msgid "View method `extendStore` can be used to add custom logic to page's store." msgstr "" +"Метод представления `extendStore` можно использовать для добавления пользовательской " +"логики в хранилище страницы." #: ../../quickstart-front.rst:352 msgid "Overriding root component" -msgstr "" +msgstr "Переопределение корневого компонента" #: ../../quickstart-front.rst:353 msgid "" @@ -357,26 +438,33 @@ msgid "" "`app.beforeInit` signal. This can be useful for such things as changing " "layout CSS classes, back button behaviour or main layout components." msgstr "" +"Корневой компонент приложения можно переопределить с использованием сигнала `app.beforeInit`. " +"Это может быть полезно, например, для изменения классов CSS макета, поведения кнопки " +"назад или основных компонентов макета." #: ../../quickstart-front.rst:356 msgid "Example of customizing sidebar component:" -msgstr "" +msgstr "Пример настройки компонента боковой панели:" #: ../../quickstart-front.rst:370 msgid "Translating values of fields" -msgstr "" +msgstr "Перевод значений полей" #: ../../quickstart-front.rst:371 msgid "" "Values tha displayed by `FKField` of `ChoicesField` can be translated " "using standard translations files." msgstr "" +"Значения, отображаемые с использованием `FKField` или `ChoicesField`, могут быть переведены " +"с использованием стандартных файлов перевода." #: ../../quickstart-front.rst:373 msgid "" "Translation key must be defined as " "`:model:::`. For example:" msgstr "" +"Ключ перевода должен быть определен как " +"`:model:::`. Например:" #: ../../quickstart-front.rst:381 msgid "" @@ -385,26 +473,31 @@ msgid "" "`_translate_model = 'Category'` attribute to model on backend. It " "shortens" msgstr "" +"Перевод значений может быть трудоемким, поскольку каждая модель на бэкенде обычно " +"генерирует более одной модели на фронтенде. Для избежания этого добавьте " +"атрибут `_translate_model = 'Category'` к модели на бэкенде. Это сокращает" #: ../../quickstart-front.rst:391 msgid "to" -msgstr "" +msgstr "в" #: ../../quickstart-front.rst:397 msgid "" "For `FKField` name of the related model is used. And `fieldName` should " "be equal to `viewField`." msgstr "" +"Для `FKField` используется имя связанной модели. И `fieldName` должно быть равно `viewField`." #: ../../quickstart-front.rst:403 msgid "Changing actions or sublinks" -msgstr "" +msgstr "Изменение действий или подссылок" #: ../../quickstart-front.rst:405 msgid "" "Sometimes using only schema for defining actions or sublinks is not " "enough." msgstr "" +"Иногда использование только схемы для определения действий или подссылок недостаточно." #: ../../quickstart-front.rst:407 msgid "" @@ -413,14 +506,18 @@ msgid "" " already a superuser (`is_superuser` is `true`). `<${PATH}>filterActions`" " signal can be used to achieve such result." msgstr "" +"Например, у нас есть действие, которое делает пользователя суперпользователем " +"(`/user/{id}/make_superuser/`), и мы хотим скрыть это действие, если пользователь уже " +"является суперпользователем (`is_superuser` равно `true`). Сигнал `<${PATH}>filterActions` " +"может быть использован для достижения такого результата." #: ../../quickstart-front.rst:419 msgid "`<${PATH}>filterActions` recieves {actions, data}" -msgstr "" +msgstr "`<${PATH}>filterActions` получает {actions, data}" #: ../../quickstart-front.rst:420 msgid "`<${PATH}>filterSublinks` recieves {sublinks, data}" -msgstr "" +msgstr "`<${PATH}>filterSublinks` получает {sublinks, data}" #: ../../quickstart-front.rst:422 msgid "" @@ -428,10 +525,13 @@ msgid "" "properties will contain arrays with default items (not hidden action or " "sublinks), it can be changed or replaced completely." msgstr "" +"Свойство `data` будет содержать данные экземпляра. Свойства `actions` и `sublinks` " +"будут содержать массивы с элементами по умолчанию (не скрытыми действиями или " +"подссылками), их можно изменить или полностью заменить." #: ../../quickstart-front.rst:428 msgid "LocalSettings" -msgstr "" +msgstr "Локальные настройки" #: ../../quickstart-front.rst:430 msgid "" @@ -439,14 +539,17 @@ msgid "" " model saves in browser Local Storage. If you want to add another " "options, you can do it using `beforeInit` signal, for example:" msgstr "" +"Поля этой модели отображаются в левой боковой панели. Все данные из этой " +"модели сохраняются в локальном хранилище браузера. Если вы хотите добавить другие " +"варианты, вы можете сделать это, используя сигнал `beforeInit`, например:" #: ../../quickstart-front.rst:444 msgid "Store" -msgstr "" +msgstr "Хранилище" #: ../../quickstart-front.rst:446 msgid "There are three ways to store data:" -msgstr "" +msgstr "Есть три способа сохранения данных:" #: ../../quickstart-front.rst:448 msgid "" @@ -454,6 +557,9 @@ msgid "" "options for changing language and a button to turn on/off the dark mode. " "Data to userSettingsStore comes from schema." msgstr "" +"userSettingsStore - сохраняет данные на сервере. По умолчанию есть " +"варианты изменения языка и кнопка включения/выключения темного режима. " +"Данные для userSettingsStore поступают из схемы." #: ../../quickstart-front.rst:450 msgid "" @@ -461,26 +567,32 @@ msgid "" "where you can store your own fields, as described in " ":ref:`localSettings`." msgstr "" +"localSettingsStore - сохраняет данные в локальном хранилище браузера. Здесь вы можете " +"хранить свои собственные поля, как описано в :ref:`localSettings`." #: ../../quickstart-front.rst:451 msgid "store - stores current page data." -msgstr "" +msgstr "store - хранит данные текущей страницы." #: ../../quickstart-front.rst:453 msgid "" "To use any of this stores you need to run the following command: " ":code:`app.[storeName]`, for example: :code:`app.userSettingsStore`." msgstr "" +"Чтобы использовать любое из этих хранилищ, вам нужно выполнить следующую команду: " +":code:`app.[storeName]`, например: :code:`app.userSettingsStore`." #: ../../quickstart-front.rst:455 msgid "" "If you are accessing the userSettingsStore from within the component then" " you need to use :code:`this.$app` instead :code:`app`." msgstr "" +"Если вы обращаетесь к userSettingsStore изнутри компонента, тогда " +"вам нужно использовать :code:`this.$app` вместо :code:`app`." #: ../../quickstart-front.rst:457 msgid "From `app.store` you may need:" -msgstr "" +msgstr "Из `app.store` вам может потребоваться:" #: ../../quickstart-front.rst:459 msgid "" @@ -489,80 +601,15 @@ msgid "" "between them is only in the way information is stored: `viewItems` is an " "Array of Objects and `viewItemsMap` is a Map." msgstr "" +"`vewsItems` и `viewItemsMap` - хранят информацию о родительских представлениях " +"для этой страницы. Они используются, например, в хлебных крошках. Различие " +"между ними заключается только в способе хранения информации: `viewItems` - это " +"массив объектов, а `viewItemsMap` - это карта." #: ../../quickstart-front.rst:461 msgid "`page` - saves all information about current page." -msgstr "" +msgstr "`page` - сохраняет всю информацию о текущей странице." #: ../../quickstart-front.rst:462 msgid "`title` - title of current page." -msgstr "" - -#~ msgid "sets `AppConfiguration` from openapi schema;" -#~ msgstr "" - -#~ msgid "gets schema from backend and loads it. Emits 'openapi.loaded' signal;" -#~ msgstr "" - -#~ msgid "if there is centrifugoClient in settings.py connects it;" -#~ msgstr "" - -#~ msgid "loads translations;" -#~ msgstr "" - -#~ msgid "`prepareFieldsClasses()` used to customize field class;" -#~ msgstr "" - -#~ msgid "" -#~ "creates StoreConstructor from `this.views` and" -#~ " emits 'app.beforeInitStore' with { " -#~ "storeConstructor };" -#~ msgstr "" - -#~ msgid "gets translations;" -#~ msgstr "" - -#~ msgid "" -#~ "inits application `Vue()` from schema.info," -#~ " this.router, Vuex.Store args and emits " -#~ "'app.afterInit' with {app: this};" -#~ msgstr "" - -#~ msgid "" -#~ "There is a flowchart representing " -#~ "application initialization process(signal names " -#~ "have red font):" -#~ msgstr "" - -#~ msgid "Basic data components" -#~ msgstr "" - -#~ msgid "" -#~ "If you want to add on page " -#~ "some component that gets data from " -#~ "API and displays it in some way," -#~ " you can use " -#~ "`spa.components.mixins.InstanceComponent` and " -#~ "`spa.components.mixins.InstancesComponent`. Both mixins" -#~ " expect you to define at least " -#~ "`getQuerySet` method that will be called" -#~ " on component creation before data " -#~ "fetching." -#~ msgstr "" - -#~ msgid "" -#~ "Components based on mixins will have " -#~ "computed property `instance` or `instances`" -#~ " respectively. It will be refreshed " -#~ "on every auto update." -#~ msgstr "" - -#~ msgid "Example component:" -#~ msgstr "" - -#~ msgid "" -#~ "In given example store module with " -#~ "name `usersCounter` will be registered " -#~ "so data can be accessed globally." -#~ msgstr "" - +msgstr "`title` - заголовок текущей страницы." diff --git a/tox.ini b/tox.ini index 3939c66f..1b6dfd7e 100644 --- a/tox.ini +++ b/tox.ini @@ -141,7 +141,7 @@ setenv = commands = make trans_update make -e SPHINXOPTS="-D language='ru'" html -; make latexpdf + # make -e SPHINXOPTS="-D language='ru'" latexpdf deps = -rrequirements-rtd.txt diff --git a/vstutils/api/fields.py b/vstutils/api/fields.py index efa0ec55..c0d20cb8 100644 --- a/vstutils/api/fields.py +++ b/vstutils/api/fields.py @@ -164,24 +164,53 @@ def __init__(self, items=None, min_column_width=200, **kwargs): class AutoCompletionField(VSTCharField): """ - Field that provides autocompletion on frontend, using specified list of objects. + Serializer field that provides autocompletion on the frontend, using a specified list of objects. - :param autocomplete: Autocompletion reference. You can set list/tuple with - values or set OpenAPI schema definition name. - For definition name GUI will find optimal link and - will show values based on ``autocomplete_property`` and + :param autocomplete: Autocompletion reference. You can set a list or tuple with + values or specify the name of an OpenAPI schema definition. + For a definition name, the GUI will find the optimal link and + display values based on the ``autocomplete_property`` and ``autocomplete_represent`` arguments. - :type autocomplete: list,tuple,str - :param autocomplete_property: this argument indicates which attribute will be - get from OpenAPI schema definition model as value. + :type autocomplete: list, tuple, str + :param autocomplete_property: Specifies which attribute to retrieve from the + OpenAPI schema definition model as the value. + Default is 'id'. :type autocomplete_property: str - :param autocomplete_represent: this argument indicates which attribute will be - get from OpenAPI schema definition model as represent value. - :param use_prefetch: prefetch values on frontend at list-view. Default is ``True``. + :param autocomplete_represent: Specifies which attribute to retrieve from the + OpenAPI schema definition model as the representational value. + Default is 'name'. + :param use_prefetch: Prefetch values on the frontend in list view. Default is ``True``. :type use_prefetch: bool .. note:: - Effective only in GUI. Works similar to :class:`.VSTCharField` in API. + This functionality is effective only in the GUI. In the API, it behaves similarly to :class:`.VSTCharField`. + + Usage: + This field is designed to be used in serializers where a user needs to input a value, and autocompletion + based on a predefined list or an OpenAPI schema definition is desired. If an OpenAPI schema is specified, + two additional parameters, ``autocomplete_property`` and ``autocomplete_represent``, can be configured to + customize the appearance of the dropdown list. + + Example: + + .. sourcecode:: python + + from vstutils.api import serializers + from vstutils.api.fields import AutoCompletionField + + + class MyModelSerializer(serializers.BaseSerializer): + name = AutoCompletionField(autocomplete=['Option 1', 'Option 2', 'Option 3']) + + # or + + class MyModelSerializer(serializers.BaseSerializer): + name = AutoCompletionField( + autocomplete='MyModelSchema', + autocomplete_property='custom_property', + autocomplete_represent='display_name' + ) + """ autocomplete: _t.Text @@ -201,29 +230,50 @@ def __init__(self, **kwargs): class CommaMultiSelect(VSTCharField): """ - Field containing a list of values with specified separator (default: ","). - Gets list of values from another model or custom list. Provides autocompletion as :class:`.AutoCompletionField`, - but with comma-lists. - Suited for property-fields in model where main logic is already implemented or - with :class:`model.CharField`. - - :param select: OpenAPI schema definition name or list with values. - :type select: str,tuple,list - :param select_separator: separator of values. Default is comma. + Field that allows users to input multiple values, separated by a specified delimiter (default: ","). + It retrieves a list of values from another model or a custom list and provides autocompletion similar to :class:`.AutoCompletionField`. + This field is suitable for property fields in a model where the main logic is already implemented or for use with :class:`model.CharField`. + + :param select: OpenAPI schema definition name or a list with values. + :type select: str, tuple, list + :param select_separator: The separator for values. The default is a comma. :type select_separator: str - :param select_property,select_represent: work as ``autocomplete_property`` and ``autocomplete_represent``. - Default is ``name``. - :param use_prefetch: prefetch values on frontend at list-view. Default is ``False``. - :param make_link: Show value as link to model. Default is ``True``. - :param dependence: Dictionary, where keys are name of field from the same model, and values are name of query - filter. If at least one of the fields that we depend on is non nullable, required and set to - null, autocompletion list will be empty and field will be disabled. + :param select_property, select_represent: These parameters function similarly to ``autocomplete_property`` and ``autocomplete_represent``. + The default is ``name``. + :param use_prefetch: Prefetch values on the frontend in list view. The default is ``False``. + :type use_prefetch: bool + :param make_link: Show values as links to the model. The default is ``True``. + :type make_link: bool + :param dependence: A dictionary where keys are the names of fields from the same model, and values are the names of query + filters. If at least one of the fields we depend on is non-nullable, required, and set to + null, the autocompletion list will be empty, and the field will be disabled. :type dependence: dict + Example: + + .. sourcecode:: python + + from vstutils.api import serializers + from vstutils.api.fields import CommaMultiSelect + + class MyModelSerializer(serializers.BaseSerializer): + tags = CommaMultiSelect( + select="TagsReferenceSchema", + select_property='slug', + select_represent='slug', + use_prefetch=True, + make_link=False, + dependence={'some_field': 'value'}, + ) + + # or + + class MyModelSerializer(serializers.BaseSerializer): + tags = CommaMultiSelect(select=['tag1', 'tag2', 'tag3']) .. note:: - Effective only in GUI. Works similar to :class:`.VSTCharField` in API. - """ + This functionality is effective only in the GUI and works similarly to :class:`.VSTCharField` in the API. + """ # noqa: E501 select_model: _t.Text select_separator: _t.Text @@ -256,29 +306,48 @@ def to_representation(self, value: _t.Union[_t.Text, _t.Sequence, _t.Iterator]) class DynamicJsonTypeField(VSTCharField): """ - Field which type is based on another field. It converts value to internal string - and represent field as json object. + A versatile serializer field that dynamically adapts its type based on the value of another field in the model. + It facilitates complex scenarios where the type of data to be serialized depends on the value of a related field. - :param field: field in model which value change will change type of current value. + :param field: The field in the model whose value change will dynamically determine the type of the current field. :type field: str - :param types: key-value mapping where key is value of subscribed field and - value is type (in OpenAPI format) of current field. + :param types: A key-value mapping where the key is the value of the subscribed field, and + the value is the type (in OpenAPI format) of the current field. :type types: dict - :param choices: variants of choices for different subscribed field values. - Uses mapping where key is value of subscribed field and - value is list with values to choice. + :param choices: Variants of choices for different subscribed field values. + Uses a mapping where the key is the value of the subscribed field, and + the value is a list with values to choose from. :type choices: dict - :param source_view: Allows to use parent views data as source for field creation. - Exact view path (`/user/{id}/`) or relative parent specifier - (`<>.<>.<>`) can be provided. For example if current page is - `/user/1/role/2/` and `source_view` is `<>.<>` then data - from `/user/1/` will be used. Only detail views if supported. + :param source_view: Allows using parent views data as a source for field creation. + The exact view path (`/user/{id}/`) or relative parent specifier + (`<>.<>.<>`) can be provided. + For example, if the current page is `/user/1/role/2/` + and `source_view` is `<>.<>`, + then data from `/user/1/` will be used. Only detail views are supported. :type source_view: str + Example: + Suppose you have a serializer `MySerializer` with a `field_type` (e.g., a `ChoiceField`) + and a corresponding `object_data`. + The `object_data` field can have different types based on the value of `field_type`. + Here's an example configuration: + + .. code-block:: python + + class MySerializer(VSTSerializer): + field_type = serializers.ChoiceField(choices=['serializer', 'integer', 'boolean']) + object_data = DynamicJsonTypeField( + field='field_type', + types={ + 'serializer': SomeSerializer(), + 'integer': IntegerField(max_value=1337), + 'boolean': 'boolean', + }, + ) - .. note:: - Effective only in GUI. In API works similar to :class:`.VSTCharField` without value modifications. - """ + In this example, the `object_data` field dynamically adapts its type based on the selected value of `field_type`. + The `types` argument defines different types for each possible value of `field_type`, allowing for flexible and dynamic serialization. + """ # noqa: E501 field: _t.Text choices: _t.Dict @@ -361,26 +430,61 @@ def is_json(self, real_field): class DependFromFkField(DynamicJsonTypeField): """ - Field extends :class:`DynamicJsonTypeField`. Validates field data by :attr:`.field_attribute` - chosen in related model. By default, any value of :attr:`.field_attribute` validates as :class:`.VSTCharField`. - To override this behavior set dict attribute ``{field_attribute value}_fields_mapping`` in related model where: + A specialized field that extends :class:`DynamicJsonTypeField` and + validates field data based on a :attr:`.field_attribute` + chosen in a related model. The field data is validated against + the type defined by the chosen value of :attr:`.field_attribute`. - - **key** - string representation of value type which is received from related instance :attr:`.field_attribute`. - - **value** - :class:`rest_framework.Field` instance for validation. - - :param field: field in model which value change changes type of current value. - Field must be :class:`.FkModelField`. + .. note:: + By default, any value of :attr:`.field_attribute` validates as :class:`.VSTCharField`. + To override this behavior, set the class attribute ``{field_attribute}_fields_mapping`` in the related model. + The attribute should be a dictionary where keys are string representations + of the values of :attr:`.field_attribute`, + and values are instances of :class:`rest_framework.Field` for validation. + If a value is not found in the dictionary, the default type will be :class:`.VSTCharField`. + + :param field: The field in the model whose value change determines the type of the current value. + The field must be of type :class:`.FkModelField`. :type field: str - :param field_attribute: attribute of related model instance with name of type. + :param field_attribute: The attribute of the related model instance containing the name of the type. :type field_attribute: str - :param types: key-value mapping where key is value of subscribed field and - value is type (in OpenAPI format) of current field. + :param types: A key-value mapping where the key is the value of the subscribed field, and + the value is the type (in OpenAPI format) of the current field. :type types: dict .. warning:: - ``field_attribute`` in related model must be :class:`rest_framework.fields.ChoicesField` or - GUI will show field as simple text. + The ``field_attribute`` in the related model must be of type :class:`rest_framework.fields.ChoicesField` + to ensure proper functioning in the GUI; otherwise, the field will be displayed as simple text. + + + Example: + Suppose you have a model with a ForeignKey field `related_model` and a field `type_attribute` in `RelatedModel` + that determines the type of data. You can use `DependFromFkField` to dynamically adapt the serialization + based on the value of `type_attribute`: + + .. code-block:: python + + class RelatedModel(BModel): + # ... other fields ... + type_attribute = models.CharField(max_length=20, choices=[('type1', 'Type 1'), ('type2', 'Type 2')]) + type_attribute_fields_mapping = { + 'type1': SomeSerializer(), + 'type2': IntegerField(max_value=1337), + } + + class MyModel(BModel): + related_model = models.ForeignKey(RelatedModel, on_delete=models.CASCADE) + + class MySerializer(VSTSerializer): + dynamic_field = DependFromFkField( + field='related_model', + field_attribute='type_attribute' + ) + + class Meta: + model = MyModel + fields = '__all__' """ default_related_field = VSTCharField(allow_null=True, allow_blank=True, default='') @@ -431,22 +535,58 @@ def get_real_field(self, data) -> _t.Optional[Field]: class TextareaField(VSTCharField): """ - Field containing multiline string. + A specialized field that allows the input and display of multiline text. .. note:: - Effective only in GUI. Works similar to :class:`.VSTCharField` in API. + This field is designed for use in the graphical user interface (GUI) and functions similarly to + :class:`.VSTCharField` in the API. + + Example: + Suppose you have a model with a `description` field that can contain multiline text. + You can use `TextareaField` in your serializer to enable users to input and view multiline text in the GUI: + + .. code-block:: python + + class MyModel(BModel): + description = models.TextField() + + class MySerializer(VSTSerializer): + multiline_description = TextareaField(source='description') + + class Meta: + model = MyModel + fields = '__all__' """ class HtmlField(VSTCharField): """ - Field contains html text and marked as format:html. The field does not validate whether its content is HTML. + A specialized field for handling HTML text content, marked with the format:html. - .. warning:: - To avoid vulnerability, do not allow users to modify this data because users ate able to execute their scripts. + .. warning: + Exercise caution when using this field, as it does not validate whether the content is valid HTML. + Be aware of the potential security risks associated with allowing users to modify HTML content, as + they may execute scripts. .. note:: - Effective only in GUI. Works similar to :class:`.VSTCharField` in API. + This field is designed for use in the graphical user interface (GUI) and functions similarly to + :class:`.VSTCharField` in the API. + + Example: + If you have a model with an `html_content` field that stores HTML-formatted text, you can use `HtmlField` + in your serializer to handle this content in the GUI: + + .. code-block:: python + + class MyModel(BModel): + html_content = models.TextField() + + class MySerializer(VSTSerializer): + formatted_html_content = HtmlField(source='html_content') + + class Meta: + model = MyModel + fields = '__all__' """ @@ -473,23 +613,64 @@ def to_internal_value(self, data): class QrCodeField(_BaseBarcodeField): """ - Simple string field. - The field is going to represent as QrCode in user interface. + A versatile field for encoding various types of data into QR codes. + + This field can encode a wide range of data into a QR code representation, making it useful for displaying + QR codes in the user interface. It works by serializing or deserializing data using the specified child field. - :param child: original data field for serialization or deserialization. + :param child: The original data field for serialization or deserialization. Default: :class:`rest_framework.fields.CharField` :type child: rest_framework.fields.Field + + Example: + Suppose you have a model with a `data` field, and you want to display its QR code representation in the GUI. + You can use `QrCodeField` in your serializer: + + .. code-block:: python + + class MyModel(BModel): + data = models.CharField(max_length=255) + + class MySerializer(VSTSerializer): + qr_code_data = QrCodeField(child=serializers.CharField(source='data')) + + class Meta: + model = MyModel + fields = '__all__' + + In this example, the qr_code_data field will represent the QR code generated from the data field in the GUI. + Users can interact with this QR code, and if their device supports it, + they can scan the code for further actions. """ class Barcode128Field(_BaseBarcodeField): """ - Simple string field. Value must always be a valid ASCII-string. - The field is going to represent as Barcode (Code 128) in user interface. + A field for representing data as a Barcode (Code 128) in the user interface. + + This field accepts and validates data in the form of a valid ASCII string. It is designed to display the data + as a Code 128 barcode in the graphical user interface. The underlying data is serialized or deserialized + using the specified child field. - :param child: original data field for serialization or deserialization. + :param child: The original data field for serialization or deserialization. Default: :class:`rest_framework.fields.CharField` :type child: rest_framework.fields.Field + + Example: + Suppose you have a model with a `product_code` field, and you want to display its Code 128 barcode + representation in the GUI. You can use `Barcode128Field` in your serializer: + + .. code-block:: python + + class Product(BModel): + product_code = models.CharField(max_length=20) + + class ProductSerializer(VSTSerializer): + barcode = Barcode128Field(child=serializers.CharField(source='product_code')) + + class Meta: + model = Product + fields = '__all__' """ def __init__(self, **kwargs): @@ -499,44 +680,37 @@ def __init__(self, **kwargs): class FkField(IntegerField): """ - Implementation of ForeignKeyField. You can specify which field of a related model will be - stored in field (default: "id"), and which will represent field on frontend. + An implementation of ForeignKeyField, designed for use in serializers. This field allows you to specify + which field of a related model will be stored in the field (default: "id"), and which field will represent + the value on the frontend. :param select: OpenAPI schema definition name. :type select: str - :param autocomplete_property: this argument indicates which attribute will be - get from OpenAPI schema definition model as value. + :param autocomplete_property: Specifies which attribute will be retrieved from the OpenAPI schema definition model as the value. Default is ``id``. :type autocomplete_property: str - :param autocomplete_represent: this argument indicates which attribute will be - get from OpenAPI schema definition model as represent value. + :param autocomplete_represent: Specifies which attribute will be retrieved from the OpenAPI schema definition model as the representational value. Default is ``name``. - :param field_type: defines the autocomplete_property type for further definition in the schema - and casting to the type from the api. Default is passthroughs but require `int` or `str` objects. + :param field_type: Defines the type of the autocomplete_property for further definition in the schema + and casting to the type from the API. Default is passthrough but requires `int` or `str` objects. :type field_type: type - :param use_prefetch: prefetch values on frontend at list-view. Default is ``True``. + :param use_prefetch: Prefetch values on the frontend at list-view. Default is ``True``. :type use_prefetch: bool - :param make_link: show value as link to model. Default is ``True``. + :param make_link: Show the value as a link to the related model. Default is ``True``. :type make_link: bool - :param dependence: dictionary, where keys are names of a field from the same model, - and values are names of query filter. - If at least one of the fields that we depend on is non nullable, required and set to null, - autocompletion list will be empty and field will be disabled. - - There are some special keys for dependence dictionary to get data that is stored - on frontend without additional database query: - - ``'<>'`` gets primary key of current instance, - - ``'<>'`` gets view name from Vue component, - - ``'<>'`` gets parent view name from Vue component, - - ``'<>'`` gets view level, - - ``'<>'`` gets operation_id, - - ``'<>`` gets parent_operation_id. + :param dependence: A dictionary where keys are names of fields from the same model, + and values are names of query filters. If at least one of the fields that we depend on is non-nullable, + required, and set to null, the autocompletion list will be empty, and the field will be disabled. + + There are some special keys for the dependence dictionary to get data that is stored + on the frontend without additional database query: + + - ``'<>'`` gets the primary key of the current instance, + - ``'<>'`` gets the view name from the Vue component, + - ``'<>'`` gets the parent view name from the Vue component, + - ``'<>'`` gets the view level, + - ``'<>'`` gets the operation_id, + - ``'<>`` gets the parent_operation_id. :type dependence: dict Examples: @@ -544,19 +718,19 @@ class FkField(IntegerField): field = FkField(select=Category, dependence={'<>': 'my_filter'}) - This filter will get pk of current object and make query on frontend '/category?my_filter=3' - where '3' is primary key of current instance. + This filter will get the primary key of the current object and + make a query on the frontend ``/category?my_filter=3`` + where ``3`` is the primary key of the current instance. - - :param filters: dictionary, where keys are names of a field from a related (by this FkField) model, + :param filters: A dictionary where keys are names of fields from a related model (specified by this FkField), and values are values of that field. :type filters: dict .. note:: - Intersection of `dependence.values()` and `filters.keys()` will throw error to prevent ambiguous filtering. + The intersection of `dependence.values()` and `filters.keys()` will throw an error to prevent ambiguous filtering. .. note:: - Effective only in GUI. Works similar to :class:`rest_framework.fields.IntegerField` in API. - """ + Effective only in the GUI. Works similarly to :class:`rest_framework.fields.IntegerField` in the API. + """ # noqa: E501 select_model: _t.Text autocomplete_property: _t.Text @@ -680,17 +854,50 @@ def to_representation(self, value: _t.Union[int, models.Model]) -> _t.Any: class DeepFkField(FkModelField): """ - Extends :class:`.FkModelField`, but displays as tree on frontend. + Extends :class:`.FkModelField`, specifically designed for hierarchical relationships in the frontend. + + This field is intended for handling ForeignKey relationships within a hierarchical or tree-like structure. + It displays as a tree in the frontend, offering a clear visual representation of parent-child relationships. .. warning:: - This field does not support ``dependence``. - Use ``filters`` at your own risk, as it would rather break the tree structure. + This field intentionally does not support the ``dependence`` parameter, as it operates in a tree structure. + Usage of ``filters`` should be approached with caution, as inappropriate filters may disrupt the tree hierarchy. - :param only_last_child: if True then only allows a value to be selected if it has no children. Default is `False` + :param only_last_child: If True, the field restricts the selection to nodes without children. Default is `False`. + Useful when you want to enforce selection of leaf nodes. :type only_last_child: bool - :param parent_field_name: name of parent field in model. Default is `parent` + :param parent_field_name: The name of the parent field in the related model. Default is `parent`. + Should be set to the ForeignKey field in the related model, + representing the parent-child relationship. + For example, if your related model has a ForeignKey like + `parent = models.ForeignKey('self', ...)`, + then `parent_field_name` should be set to `'parent'`. :type parent_field_name: str + + Examples: + Consider a related model with a ForeignKey field representing parent-child relationships: + + .. code-block:: python + + class Category(BModel): + name = models.CharField(max_length=255) + parent = models.ForeignKey('self', null=True, default=None, on_delete=models.CASCADE) + + To use the DeepFkField with this related model in a serializer, you would set the parent_field_name to 'parent': + + .. code-block:: python + + class MySerializer(VSTSerializer): + category = DeepFkField(select=Category, parent_field_name='parent') + + This example assumes a Category related model with a ForeignKey 'parent' field. + The DeepFkField will then display the categories as a tree structure in the frontend, + providing an intuitive selection mechanism for hierarchical relationships. + + .. note:: + Effective only in GUI. Works similarly to :class:`rest_framework.fields.IntegerField` in API. """ + def __init__(self, only_last_child: bool = False, parent_field_name='parent', **kwargs): super().__init__(**kwargs) self.only_last_child = only_last_child @@ -699,11 +906,32 @@ def __init__(self, only_last_child: bool = False, parent_field_name='parent', ** class UptimeField(IntegerField): """ - Time duration field, in seconds. May be used to compute some uptime. + Time duration field, in seconds, specifically designed for computing and displaying system uptime. - .. note:: - Effective only in GUI. Works similar to :class:`rest_framework.fields.IntegerField` in API. + This field is effective only in the GUI and behaves similarly + to :class:`rest_framework.fields.IntegerField` in the API. + + The `UptimeField` class transforms time in seconds into a user-friendly representation on the frontend. + It intelligently selects the most appropriate pattern from the following templates: + + - ``HH:mm:ss`` (e.g., 23:59:59) + - ``dd HH:mm:ss`` (e.g., 01d 00:00:00) + - ``mm dd HH:mm:ss`` (e.g., 01m 30d 00:00:00) + - ``yy mm dd HH:mm:ss`` (e.g., 99y 11m 30d 22:23:24) + + Example: + .. code-block:: python + + class MySerializer(serializers.ModelSerializer): + uptime = UptimeField() + + This example assumes a serializer where the `uptime` field represents a time duration in seconds. + The `UptimeField` will then display the duration in a human-readable format on the frontend, + making it convenient for users to interpret and input values. + + .. note:: + Effective only in GUI. Works similarly to :class:`rest_framework.fields.IntegerField` in API. """ @@ -750,25 +978,63 @@ class RedirectCharField(RedirectFieldMixin, CharField): class NamedBinaryFileInJsonField(VSTCharField): """ - Field that takes JSON with properties: - * name - string - name of file; - * mediaType - string - MIME type of file; - * content - base64 string - content of file. + Field that represents a binary file in JSON format. - This field is useful for saving binary files with their names in :class:`django.db.models.CharField` - or :class:`django.db.models.TextField` model fields. All manipulations with decoding and encoding - binary content data executes on client. This imposes reasonable limits on file size. + :param file: If True, accept only subclasses of File as input. + If False, accept only string input. Default: False. + :type file: bool + :param post_handlers: Functions to process the file after validation. + Each function takes two arguments: ``binary_data`` (file bytes) + and ``original_data`` (reference to the original JSON object). + The function should return the processed ``binary_data``. + :type post_handlers: tuple,list + :param pre_handlers: Functions to process the file before validation. + Each function takes two arguments: ``binary_data`` (file bytes) + and ``original_data`` (reference to the original JSON object). + The function should return the processed ``binary_data``. + :type pre_handlers: tuple,list + :param int max_content_size: Maximum allowed size of the file content in bytes. + :param int min_content_size: Minimum allowed size of the file content in bytes. + :param int min_length: Minimum length of the file name. Only applicable when ``file=True``. + :param int max_length: Maximum length of the file name. Only applicable when ``file=True``. - Additionally, this field can construct :class:`django.core.files.uploadedfile.SimpleUploadedFile` - from incoming JSON and store it as file in :class:`django.db.models.FileField` if `file` argument is set to `True` - Attrs: - :attr:`NamedBinaryInJsonField.file`: if True, accept only subclasses of File as input. If False, accept only string - input. Default: False. + This field is designed for storing binary files alongside their names in + :class:`django.db.models.CharField` or :class:`django.db.models.TextField` + model fields. All manipulations involving decoding and encoding binary content data + occur on the client, imposing reasonable limits on file size. - .. note:: - Effective only in GUI. Works similar to :class:`.VSTCharField` in API. + Additionally, this field can construct a + :class:`django.core.files.uploadedfile.SimpleUploadedFile` from incoming JSON + and store it as a file in :class:`django.db.models.FileField` if the + `file` attribute is set to `True`. + + Example: + In a serializer, include this field to handle binary files: + + .. code-block:: python + + class MySerializer(serializers.ModelSerializer): + binary_data = NamedBinaryFileInJsonField(file=True) + + This example assumes a serializer where the ``binary_data`` field represents + binary file information in JSON format. The ``NamedBinaryFileInJsonField`` + will then handle the storage and retrieval of binary files in a + user-friendly manner. + + The binary file is represented in JSON with the following properties: + + - **name** (str): Name of the file. + - **mediaType** (str): MIME type of the file. + - **content** (str or File): Content of the file. If `file` is True, it will be a + reference to the file; otherwise, it will be base64-encoded content. + + .. warning:: + The client application will display the content as a download link. + Users will interact with the binary file through the application, + with the exchange between the Rest API and the client occurring through + the presented JSON object. """ __valid_keys = ('name', 'content', 'mediaType') @@ -880,14 +1146,37 @@ def to_internal_value(self, data) -> _t.Any: class NamedBinaryImageInJsonField(NamedBinaryFileInJsonField): """ - Extends :class:`.NamedBinaryFileInJsonField` to represent image on frontend - (if binary image is valid). Validate this field with - :class:`vstutils.api.validators.ImageValidator`. + Field that represents an image in JSON format, extending :class:`.NamedBinaryFileInJsonField`. - :param background_fill_color: Color to fill area that is not covered by image after cropping. - Transparent by default but will be black if image format is not supporting transparency. + :param background_fill_color: Color to fill the area not covered by the image after cropping. + Transparent by default but will be black if the image format does not support transparency. Can be any valid CSS color. :type background_fill_color: str + + This field is designed for handling image files alongside their names in + :class:`django.db.models.CharField` or :class:`django.db.models.TextField` model fields. + It extends the capabilities of :class:`.NamedBinaryFileInJsonField` to specifically handle images. + + Additionally, this field validates the image using the following validators: + - :class:`vstutils.api.validators.ImageValidator` + - :class:`vstutils.api.validators.ImageResolutionValidator` + - :class:`vstutils.api.validators.ImageHeightValidator` + + When saving and with the added validators, the field will display a corresponding window for adjusting + the image to the specified parameters, providing a user-friendly interface for managing images. + + The image file is represented in JSON with the following properties: + + - **name** (str): Name of the image file. + - **mediaType** (str): MIME type of the image file. + - **content** (str or File): Content of the image file. If `file` is True, it will be a + reference to the image file; otherwise, it will be base64-encoded content. + + .. warning:: + The client application will display the content as an image. + Users will interact with the image through the application, + with the exchange between the Rest API and the client occurring through + the presented JSON object. """ def __init__(self, *args, **kwargs): @@ -993,27 +1282,28 @@ def wrapper(self, value, default, *args, **kwargs): class RelatedListField(VSTCharField): """ - Extends class :class:`.VSTCharField`. With this field you can output reverse ForeignKey relation - as a list of related instances. + Extends :class:`.VSTCharField` to represent a reverse ForeignKey relation as a list of related instances. - To use it, you specify 'related_name' kwarg (related_manager for reverse ForeignKey) - and 'fields' kwarg (list or tuple of fields from related model, which needs to be included). + This field allows you to output the reverse ForeignKey relation as a list of related instances. + To use it, specify the ``related_name`` kwarg (related manager for reverse ForeignKey) and the ``fields`` + kwarg (list or tuple of fields from the related model to be included). - By default :class:`.VSTCharField` used to serialize all field values and represent it on - frontend. You can specify `serializer_class` and override fields as you need. For example title, description + By default, :class:`.VSTCharField` is used to serialize all field values and represent them on the frontend. + You can specify the `serializer_class` and override fields as needed. For example, title, description, and other field properties can be set to customize frontend behavior. - :param related_name: name of a related manager for reverse foreign key + :param related_name: Name of a related manager for reverse ForeignKey. :type related_name: str - :param fields: list of related model fields. + :param fields: List of related model fields. :type fields: list[str], tuple[str] - :param view_type: determines how field are represented on frontend. Must be either 'list' or 'table'. + :param view_type: Determines how fields are represented on the frontend. Must be either ``list`` or ``table``. :type view_type: str - :param fields_custom_handlers_mapping: includes custom handlers, where key: field_name, value: callable_obj that - takes params: instance[dict], fields_mapping[dict], model, field_name[str] + :param fields_custom_handlers_mapping: Custom handlers mapping, where key: field_name, value: callable_obj + that takes params: + instance[dict], fields_mapping[dict], model, field_name[str]. :type fields_custom_handlers_mapping: dict - :param serializer_class: Serializer to customize types of fields, if no serializer provided :class:`.VSTCharField` - will be used for every field in `fields` list + :param serializer_class: Serializer to customize types of fields. If no serializer is provided, + :class:`.VSTCharField` will be used for every field in the `fields` list. :type serializer_class: type """ @@ -1211,8 +1501,28 @@ def is_all_digits_validator(value): class PhoneField(CharField): """ - Extends class 'rest_framework.serializers.CharField'. - Field for phone in international format + Extends the 'rest_framework.serializers.CharField' class. + Field for representing a phone number in international format. + + This field is designed for capturing and validating phone numbers in international format. + It extends the 'rest_framework.serializers.CharField' and adds custom validation to ensure + that the phone number contains only digits. + + Example: + In a serializer, include this field to handle phone numbers: + + .. code-block:: python + + class MySerializer(VSTSerializer): + phone_number = PhoneField() + + This example assumes a serializer where the ``phone_number`` field represents + a phone number in international format. The ``PhoneField`` will then handle the + validation and representation of phone numbers, making it convenient for users to + input standardized phone numbers. + + The field will be displayed in the client application as an input field for entering + a phone number, including the country code. """ def __init__(self, **kwargs): @@ -1225,15 +1535,33 @@ def __init__(self, **kwargs): class MaskedField(CharField): """ - Extends class 'rest_framework.serializers.CharField'. - Field that applies mask to value. + Extends the 'rest_framework.serializers.CharField' class. + Field that applies a mask to the input value. - :param mask: `IMask `_ + This field is designed for applying a mask to the input value on the frontend. + It extends the 'rest_framework.serializers.CharField' and allows the use of the + `IMask `_ library for defining masks. + + :param mask: The mask to be applied to the value. It can be either a dictionary or a string + following the `IMask` library format. :type mask: dict, str + Example: + In a serializer, include this field to apply a mask to a value: + + .. code-block:: python + + class MySerializer(serializers.Serializer): + masked_value = MaskedField(mask='000-000') + + This example assumes a serializer where the ``masked_value`` field represents + a value with a predefined mask. The ``MaskedField`` will apply the specified mask + on the frontend, providing a masked input for users. + .. note:: - Effective only on frontend. + The effectiveness of this field is limited to the frontend, and the mask is applied during user input. """ + def __init__(self, mask, **kwargs): super().__init__(**kwargs) self.mask = mask @@ -1241,11 +1569,31 @@ def __init__(self, mask, **kwargs): class WYSIWYGField(TextareaField): """ - On frontend renders https://ui.toast.com/tui-editor. - Saves data as markdown and escapes all html tags. + Extends the :class:`.TextareaField` class to render the https://ui.toast.com/tui-editor on the frontend. - :param escape: html-escape input. Enabled by default. + This field is specifically designed for rendering a WYSIWYG editor on the frontend, + using the https://ui.toast.com/tui-editor. It saves data as markdown and escapes all HTML tags. + + :param escape: HTML-escape input. Enabled by default to prevent HTML injection vulnerabilities. :type escape: bool + + Example: + In a serializer, include this field to render a WYSIWYG editor on the frontend: + + .. code-block:: python + + class MySerializer(serializers.Serializer): + wysiwyg_content = WYSIWYGField() + + This example assumes a serializer where the ``wysiwyg_content`` field represents + data to be rendered in a WYSIWYG editor on the frontend. The ``WYSIWYGField`` ensures + that the content is saved as markdown and HTML tags are escaped to enhance security. + + .. warning:: + Enabling the ``escape`` option is recommended to prevent potential HTML injection vulnerabilities. + + .. note:: + The rendering on the frontend is achieved using the https://ui.toast.com/tui-editor. """ def __init__(self, *args, **kwargs): diff --git a/vstutils/api/serializers.py b/vstutils/api/serializers.py index 686b77c8..b9f68e40 100644 --- a/vstutils/api/serializers.py +++ b/vstutils/api/serializers.py @@ -94,14 +94,27 @@ def _get_declared_fields( class BaseSerializer(DependFromFkSerializerMixin, serializers.Serializer, metaclass=SerializerMetaClass): """ Default serializer with logic to work with objects. - Read more in `DRF serializer's documentation - `_ - how to create Serializers and work with them. + + This serializer serves as a base class for creating serializers to work with non-model objects. + It extends the 'rest_framework.serializers.Serializer' class and includes additional logic + for handling object creation and updating. .. note:: - You can also setup ``generated_fields`` in class attribute ``Meta`` to get serializer - with default CharField fields. Setup attribute ``generated_field_factory`` to change default fabric method. - """ + You can set the ``generated_fields`` attribute in the ``Meta`` class to automatically include + default CharField fields. You can also customize the field creation using the ``generated_field_factory`` + attribute. + + Example: + + .. code-block:: python + + class MySerializer(BaseSerializer): + class Meta: + generated_fields = ['additional_field'] + generated_field_factory = lambda f: drf_fields.IntegerField() + + In this example, the ``MySerializer`` class extends ``BaseSerializer`` and includes an additional generated field. + """ # noqa: E501 def create(self, validated_data): # nocv return validated_data @@ -121,9 +134,24 @@ class VSTSerializer(DependFromFkSerializerMixin, serializers.ModelSerializer, me Read more in `DRF documentation `_ how to create Model Serializers. This serializer matches model fields to extended set of serializer fields. + List of available pairs specified in `VSTSerializer.serializer_field_mapping`. For example, to set :class:`vstutils.api.fields.FkModelField` in serializer use :class:`vstutils.models.fields.FkModelField` in a model. + + Example: + + .. code-block:: python + + class MyModel(models.Model): + name = models.CharField(max_length=255) + + class MySerializer(VSTSerializer): + class Meta: + model = MyModel + + In this example, the ``MySerializer`` class extends ``VSTSerializer`` and + is associated with the ``MyModel`` model. """ # pylint: disable=abstract-method From 1fb0cff4a87bad92bd08fad235afa3b88defc1c3 Mon Sep 17 00:00:00 2001 From: Dmitriy Ovcharenko Date: Thu, 1 Feb 2024 17:50:41 +1000 Subject: [PATCH 14/20] Feature: WebPush notifications --- doc/backend.rst | 50 ++ doc/config.rst | 20 + doc/locale/ru/LC_MESSAGES/backend.po | 107 ++++- doc/locale/ru/LC_MESSAGES/config.po | 259 +++++++---- frontend_src/vstutils/AppConfiguration.ts | 24 +- frontend_src/vstutils/api/ApiConnector.ts | 3 +- frontend_src/vstutils/app.ts | 3 + .../components/common/OperationButton.vue | 2 +- .../vstutils/components/page/ModelFields.vue | 4 +- frontend_src/vstutils/composables.ts | 17 +- .../vstutils/fields/boolean/BooleanField.js | 1 - frontend_src/vstutils/models/Model.ts | 8 + .../vstutils/models/ModelsResolver.ts | 1 + .../data-based-fields-visibility.test.ts | 149 ++++++ frontend_src/vstutils/signals.ts | 37 +- frontend_src/vstutils/webpush.ts | 295 ++++++++++++ pyproject.toml | 1 + requirements-prod.txt | 1 + test_src/test_proj/settings.py | 3 + test_src/test_proj/test_settings.ini | 7 + test_src/test_proj/tests.py | 429 ++++++++++++++++++ test_src/test_proj/webpushes.py | 53 +++ tox.ini | 2 +- vstutils/api/auth.py | 18 + vstutils/api/endpoint.py | 1 + vstutils/api/models.py | 11 +- vstutils/api/schema/info.py | 10 + vstutils/settings.py | 23 + vstutils/tasks.py | 3 + vstutils/templates/auth/base.html | 6 + vstutils/templates/gui/service-worker.js | 3 + vstutils/templatetags/sw.py | 14 + vstutils/webpush/__init__.py | 0 vstutils/webpush/api.py | 136 ++++++ vstutils/webpush/apps.py | 56 +++ vstutils/webpush/autodiscovery.py | 27 ++ vstutils/webpush/base.py | 137 ++++++ vstutils/webpush/migrations/0001_initial.py | 49 ++ vstutils/webpush/migrations/__init__.py | 0 vstutils/webpush/models.py | 61 +++ vstutils/webpush/tasks.py | 14 + .../templates/webpush/notification-handler.js | 79 ++++ vstutils/webpush/test_utils.py | 117 +++++ vstutils/webpush/translations/__init__.py | 0 vstutils/webpush/translations/cn.py | 10 + vstutils/webpush/translations/ru.py | 10 + vstutils/webpush/translations/vi.py | 10 + vstutils/webpush/utils.py | 63 +++ 48 files changed, 2233 insertions(+), 101 deletions(-) create mode 100644 frontend_src/vstutils/models/__tests__/data-based-fields-visibility.test.ts create mode 100644 frontend_src/vstutils/webpush.ts create mode 100644 test_src/test_proj/webpushes.py create mode 100644 vstutils/templatetags/sw.py create mode 100644 vstutils/webpush/__init__.py create mode 100644 vstutils/webpush/api.py create mode 100644 vstutils/webpush/apps.py create mode 100644 vstutils/webpush/autodiscovery.py create mode 100644 vstutils/webpush/base.py create mode 100644 vstutils/webpush/migrations/0001_initial.py create mode 100644 vstutils/webpush/migrations/__init__.py create mode 100644 vstutils/webpush/models.py create mode 100644 vstutils/webpush/tasks.py create mode 100644 vstutils/webpush/templates/webpush/notification-handler.js create mode 100644 vstutils/webpush/test_utils.py create mode 100644 vstutils/webpush/translations/__init__.py create mode 100644 vstutils/webpush/translations/cn.py create mode 100644 vstutils/webpush/translations/ru.py create mode 100644 vstutils/webpush/translations/vi.py create mode 100644 vstutils/webpush/utils.py diff --git a/doc/backend.rst b/doc/backend.rst index bbce1d2e..4eae0a89 100644 --- a/doc/backend.rst +++ b/doc/backend.rst @@ -406,3 +406,53 @@ Vstutils uses mosts of these functions under the hood. .. automodule:: vstutils.utils :members: + + +.. _webpush-manual: + +Integrating Web Push Notifications +---------------------------------- + +Web push notifications are an effective way to engage users with real-time messaging. +To integrate web push notifications in your VSTUtils project, follow these steps: + +1. **Configuration**: First, include the ``vstutils.webpush`` module in the ``INSTALLED_APPS`` section of your ``settings.py`` file. + This enables the web push functionality provided by VSTUtils. Additionally, + configure the necessary settings as described in the web push settings section (see :ref:`here` for details). +2. **Creating Notifications**: To create a web push notification, you need to define a class that inherits from + either :class:`vstutils.webpush.BaseWebPush` or :class:`vstutils.webpush.BaseWebPushNotification`. + VSTUtils automatically detects and utilizes web push classes defined in the ``webpushes`` module of all ``INSTALLED_APPS``. + Below is an example that illustrates how to implement custom web push classes: + + + .. literalinclude:: ../test_src/test_proj/webpushes.py + :language: python + :linenos: + + This example contains three classes: + + - `TestWebPush`: Sends notifications to all subscribed users. + - `TestNotification`: Targets notifications to specific users. + - `StaffOnlyNotification`: Restricts notifications to staff users only. Sometimes you may want to allow only some users to subscribe on specific notifications. + +3. **Sending Notifications**: To dispatch a web push notification, invoke the ``send`` or ``send_in_task`` + method on an instance of your web push class. For instance, to send a notification using `TestNotification`, + you can do the following: + + .. code-block:: python + + from test_proj.webpushes import TestNotification + + # Sending a notification immediately (synchronously) + TestNotification(name='Some user', user_id=1).send() + + # Sending a notification as a background task (asynchronously) + TestNotification.send_in_task(name='Some user', user_id=1) + +.. warning:: + The asynchronous sending of web push notifications (using methods like ``send_in_task``) requires a configured Celery setup + in your project, as it relies on Celery tasks "under the hood". + Ensure that Celery is properly set up and running to utilize asynchronous notification dispatching. + + +By following these steps, you can fast integrate and utilize web push notifications in projects with VSTUtils. diff --git a/doc/config.rst b/doc/config.rst index d7bc8645..933b1af9 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -572,6 +572,26 @@ For example, if you want to apply throttle to ``api/v1/author``: More on throttling at `DRF Throttle docs `_. +.. _webpush-settings: + +Web Push settings +----------------- + +Section ``[webpuwsh]``. + +* **enabled**: A boolean flag that enables or disables web push notifications. Set to `true` to activate web push notifications, and `false` to deactivate them. Default: `false`. If set to false then notifications settings on user page will be hidden and `send` method of notification class will do nothing. + +* **vapid_private_key**, **vapid_public_key**: These are the application server keys used for sending push notifications. The VAPID (Voluntary Application Server Identification) keys consist of a public and a private key. These keys are essential for secure communication between your server and the push service. For generating a VAPID key pair and understanding their usage, refer to the detailed guide available here: `Creating VAPID Keys `_. + +* **vapid_admin_email**: This setting specifies the email address of the administrator or the person responsible for the server. It is a contact point for the push service to get in touch in case of any issues or policy violations. + +* **default_notification_icon**: URL of the default icon image to be used for web push notifications, to avoid confusion absolute URL is preferred. This icon will be displayed in the notifications if no other icon is specified at the notification level. More information about icon can be found `here `_. + +For more detailed guidance on using and implementing web push notifications in VSTUtils, refer to the Web Push manual provided :ref:`here`. + +Remember, these settings are crucial for the proper functioning and reliability of web push notifications in your application. Ensure that they are configured accurately for optimal performance. + + Production web settings ----------------------- diff --git a/doc/locale/ru/LC_MESSAGES/backend.po b/doc/locale/ru/LC_MESSAGES/backend.po index 3dc603b2..ffea63d0 100644 --- a/doc/locale/ru/LC_MESSAGES/backend.po +++ b/doc/locale/ru/LC_MESSAGES/backend.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-12 06:04+0000\n" +"POT-Creation-Date: 2024-02-01 05:26+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -4078,6 +4078,7 @@ msgstr "" msgid "The body of the task executed by workers." msgstr "Тело задачи выполняется worker'ами." + #: ../../backend.rst:162 msgid "Endpoint" msgstr "Endpoint" @@ -5503,6 +5504,109 @@ msgstr "" "Использует функцию :func:`django.utils.translation.get_language` для " "получения кода языка и пытается получить перевод из списка доступных." +#: ../../backend.rst:414 +msgid "Integrating Web Push Notifications" +msgstr "Интеграция Web Push-уведомлений" + +#: ../../backend.rst:416 +msgid "" +"Web push notifications are an effective way to engage users with real-" +"time messaging. To integrate web push notifications in your VSTUtils " +"project, follow these steps:" +msgstr "" +"Web-уведомления - это эффективный способ взаимодействия с пользователями " +"с помощью реального времени. Чтобы интегрировать web-уведомления в ваш " +"проект VSTUtils, выполните следующие шаги:" + +#: ../../backend.rst:419 +msgid "" +"**Configuration**: First, include the ``vstutils.webpush`` module in the " +"``INSTALLED_APPS`` section of your ``settings.py`` file. This enables the" +" web push functionality provided by VSTUtils. Additionally, configure the" +" necessary settings as described in the web push settings section (see " +":ref:`here` for details)." +msgstr "" +"**Конфигурация**: Во-первых, включите модуль ``vstutils.webpush`` в " +"разделе ``INSTALLED_APPS`` вашего файла ``settings.py``. Это позволяет " +"использовать функциональность web-уведомлений, предоставляемую VSTUtils. " +"Кроме того, настройте необходимые параметры, как описано в разделе " +"настроек web-уведомлений (см. :ref:`здесь` для " +"подробностей)." + +#: ../../backend.rst:422 +msgid "" +"**Creating Notifications**: To create a web push notification, you need " +"to define a class that inherits from either " +":class:`vstutils.webpush.BaseWebPush` or " +":class:`vstutils.webpush.BaseWebPushNotification`. VSTUtils automatically" +" detects and utilizes web push classes defined in the ``webpushes`` " +"module of all ``INSTALLED_APPS``. Below is an example that illustrates " +"how to implement custom web push classes:" +msgstr "" +"**Создание уведомлений**: Чтобы создать web-уведомление, вам нужно " +"определить класс, который наследуется от " +":class:`vstutils.webpush.BaseWebPush` или " +":class:`vstutils.webpush.BaseWebPushNotification`. VSTUtils автоматически" +" обнаруживает и использует классы web-уведомлений, определенные в модуле " +"``webpushes`` всех ``INSTALLED_APPS``. Ниже приведен пример, " +"иллюстрирующий, как реализовать пользовательские классы web-уведомлений:" + +#: ../../backend.rst:432 +msgid "This example contains three classes:" +msgstr "Этот пример содержит три класса:" + +#: ../../backend.rst:434 +msgid "`TestWebPush`: Sends notifications to all subscribed users." +msgstr "`TestWebPush`: Отправляет уведомления всем подписанным пользователям." + +#: ../../backend.rst:435 +msgid "`TestNotification`: Targets notifications to specific users." +msgstr "`TestNotification`: Направляет уведомления конкретным пользователям." + +#: ../../backend.rst:436 +msgid "" +"`StaffOnlyNotification`: Restricts notifications to staff users only. " +"Sometimes you may want to allow only some users to subscribe on specific " +"notifications." +msgstr "" +"`StaffOnlyNotification`: Ограничивает уведомления только для сотрудников." +" Иногда вы можете хотеть разрешить подписку на конкретные уведомления " +"только некоторым пользователям." + +#: ../../backend.rst:438 +msgid "" +"**Sending Notifications**: To dispatch a web push notification, invoke " +"the ``send`` or ``send_in_task`` method on an instance of your web push " +"class. For instance, to send a notification using `TestNotification`, you" +" can do the following:" +msgstr "" +"**Отправка уведомлений**: Чтобы отправить web-уведомление, вызовите метод" +" ``send`` или ``send_in_task`` на экземпляре вашего класса " +"web-уведомления. Например, чтобы отправить уведомление с использованием " +"`TestNotification`, вы можете сделать следующее:" + +#: ../../backend.rst:453 +msgid "" +"The asynchronous sending of web push notifications (using methods like " +"``send_in_task``) requires a configured Celery setup in your project, as " +"it relies on Celery tasks \"under the hood\". Ensure that Celery is " +"properly set up and running to utilize asynchronous notification " +"dispatching." +msgstr "" +"Асинхронная отправка web-уведомлений (с использованием методов, таких как" +" ``send_in_task``) требует настроенной конфигурации Celery в вашем " +"проекте, поскольку она полагается на задачи Celery \"под капотом\". " +"Убедитесь, что Celery правильно настроен и работает, чтобы использовать " +"асинхронную отправку уведомлений." + +#: ../../backend.rst:458 +msgid "" +"By following these steps, you can fast integrate and utilize web push " +"notifications in projects with VSTUtils." +msgstr "" +"Следуя этим шагам, вы быстро сможете интегрировать и использовать " +"web-уведомления в проектах с VSTUtils." + #~ msgid "prefetch values on frontend at list-view. Default is ``False``." #~ msgstr "" #~ "делает prefetch значений на фронтенде в" @@ -5830,4 +5934,3 @@ msgstr "" #~ "rest-framework.org/api-guide/serializers/#serializers>`_ " #~ "как создавать сериализаторы и работать с" #~ " ними." - diff --git a/doc/locale/ru/LC_MESSAGES/config.po b/doc/locale/ru/LC_MESSAGES/config.po index 83667a25..7a059dd8 100644 --- a/doc/locale/ru/LC_MESSAGES/config.po +++ b/doc/locale/ru/LC_MESSAGES/config.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VST Utils 5.0.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-11 03:28+0000\n" +"POT-Creation-Date: 2024-02-01 04:55+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -1598,15 +1598,105 @@ msgstr "" "Подробнее об ограничении количества запросов в `документации DRF Throttle" " `_." -#: ../../config.rst:576 +#: ../../config.rst:578 +msgid "Web Push settings" +msgstr "Настройки веб-уведомлений" + +#: ../../config.rst:580 +msgid "Section ``[webpuwsh]``." +msgstr "Раздел ``[webpuwsh]``." + +#: ../../config.rst:582 +msgid "" +"**enabled**: A boolean flag that enables or disables web push " +"notifications. Set to `true` to activate web push notifications, and " +"`false` to deactivate them. Default: `false`. If set to false then " +"notifications settings on user page will be hidden and `send` method of " +"notification class will do nothing." +msgstr "" +"**enabled**: Булевый флаг, который включает или отключает веб-уведомления." +" Установите `true`, чтобы активировать веб-уведомления, и `false`, чтобы " +"деактивировать их. По умолчанию: `false`. Если установлено значение false, " +"то настройки уведомлений на странице пользователя будут скрыты, и метод " +"`send` класса уведомлений ничего не будет делать." + +#: ../../config.rst:584 +msgid "" +"**vapid_private_key**, **vapid_public_key**: These are the application " +"server keys used for sending push notifications. The VAPID (Voluntary " +"Application Server Identification) keys consist of a public and a private" +" key. These keys are essential for secure communication between your " +"server and the push service. For generating a VAPID key pair and " +"understanding their usage, refer to the detailed guide available here: " +"`Creating VAPID Keys `_." +msgstr "" +"**vapid_private_key**, **vapid_public_key**: Это ключи сервера приложений, " +"используемые для отправки push-уведомлений. Ключи VAPID (Voluntary " +"Application Server Identification) состоят из публичного и приватного " +"ключей. Эти ключи являются необходимыми для безопасной связи между вашим " +"сервером и службой push. Для генерации пары ключей VAPID и понимания их " +"использования смотрите подробное руководство, доступное здесь: `Создание " +"ключей VAPID `_." + +#: ../../config.rst:586 +msgid "" +"**vapid_admin_email**: This setting specifies the email address of the " +"administrator or the person responsible for the server. It is a contact " +"point for the push service to get in touch in case of any issues or " +"policy violations." +msgstr "" +"**vapid_admin_email**: Эта настройка указывает адрес электронной почты " +"администратора или ответственного за сервер. Это контактная точка для " +"службы push, чтобы связаться в случае каких-либо проблем или нарушений " +"политики." + +#: ../../config.rst:588 +msgid "" +"**default_notification_icon**: URL of the default icon image to be used " +"for web push notifications, to avoid confusion absolute URL is preferred." +" This icon will be displayed in the notifications if no other icon is " +"specified at the notification level. More information about icon can be " +"found `here `_." +msgstr "" +"**default_notification_icon**: URL изображения иконки по умолчанию, " +"которая будет использоваться для веб-уведомлений. Для избежания " +"путаницы предпочтительно использовать абсолютный URL. Эта иконка будет " +"отображаться в уведомлениях, если на уровне уведомления не указана другая " +"иконка. Более подробную информацию об иконке можно найти `здесь `_." + +#: ../../config.rst:590 +msgid "" +"For more detailed guidance on using and implementing web push " +"notifications in VSTUtils, refer to the Web Push manual provided " +":ref:`here`." +msgstr "" +"Для более подробного руководства по использованию и реализации веб-" +"уведомлений в VSTUtils смотрите руководство по веб-уведомлениям, " +":ref:`here`." + +#: ../../config.rst:592 +msgid "" +"Remember, these settings are crucial for the proper functioning and " +"reliability of web push notifications in your application. Ensure that " +"they are configured accurately for optimal performance." +msgstr "" +"Помните, что эти настройки критически важны для правильной работы и " +"надежности веб-уведомлений в вашем приложении. Убедитесь, что они " +"настроены правильно для оптимальной производительности." + +#: ../../config.rst:596 msgid "Production web settings" msgstr "Настройки для продакшн-сервера" -#: ../../config.rst:578 +#: ../../config.rst:598 msgid "Section ``[uwsgi]``." msgstr "Раздел ``[uwsgi]``." -#: ../../config.rst:580 +#: ../../config.rst:600 msgid "" "Settings related to web-server used by vstutils-based application in " "production (for deb and rpm packages by default). Most of them related to" @@ -1619,7 +1709,7 @@ msgstr "" "т. д.). Дополнительные настройки смотрите в `документации uWSGI. `_." -#: ../../config.rst:586 +#: ../../config.rst:606 msgid "" "But keep in mind that uWSGI is deprecated and may be removed in future " "releases. Use the uvicorn settings to manage your app server." @@ -1627,60 +1717,63 @@ msgstr "" "Однако имейте в виду, что uWSGI устарел и может быть удален в будущих " "версиях. Используйте настройки uvicorn для управления сервером вашего " "приложения." -#: ../../config.rst:591 + +#: ../../config.rst:611 msgid "Working behind the proxy server with TLS" msgstr "Работа за прокси-сервером с поддержкой TLS" -#: ../../config.rst:594 +#: ../../config.rst:614 msgid "Nginx" msgstr "Nginx" -#: ../../config.rst:596 +#: ../../config.rst:616 msgid "" "To configure vstutils for operation behind Nginx with TLS, follow these " "steps:" msgstr "" -"Чтобы настроить vstutils для работы через Nginx с поддержкой TLS, выполните следующие шаги:" +"Чтобы настроить vstutils для работы через Nginx с поддержкой TLS, " +"выполните следующие шаги:" -#: ../../config.rst:598 +#: ../../config.rst:618 msgid "**Install Nginx:**" msgstr "**Установка Nginx:**" -#: ../../config.rst:600 +#: ../../config.rst:620 msgid "" "Ensure that Nginx is installed on your server. You can install it using " "the package manager specific to your operating system." msgstr "" -"Убедитесь, что Nginx установлен на вашем сервере. Вы можете установить его с помощью " -"менеджера пакетов, специфичного для вашей операционной системы." +"Убедитесь, что Nginx установлен на вашем сервере. Вы можете установить " +"его с помощью менеджера пакетов, специфичного для вашей операционной " +"системы." -#: ../../config.rst:602 +#: ../../config.rst:622 msgid "**Configure Nginx:**" msgstr "**Настройка Nginx:**" -#: ../../config.rst:604 +#: ../../config.rst:624 msgid "" "Create an Nginx configuration file for your vstutils application. Below " "is a basic example of an Nginx configuration. Adjust the values based on " "your specific setup." msgstr "" "Создайте файл конфигурации Nginx для вашего приложения vstutils. Ниже " -"приведен базовый пример конфигурации Nginx. Измените значения в соответствии с " -"вашей конкретной настройкой." +"приведен базовый пример конфигурации Nginx. Измените значения в " +"соответствии с вашей конкретной настройкой." -#: ../../config.rst:643 +#: ../../config.rst:663 msgid "" "Replace ``your_domain.com`` with your actual domain, and update the paths" " for SSL certificates." msgstr "" -"Замените ``your_domain.com`` на ваш реальный домен и обновите пути " -"к SSL-сертификатам." +"Замените ``your_domain.com`` на ваш реальный домен и обновите пути к " +"SSL-сертификатам." -#: ../../config.rst:645 ../../config.rst:728 ../../config.rst:779 +#: ../../config.rst:665 ../../config.rst:748 ../../config.rst:800 msgid "**Update vstutils settings:**" msgstr "**Обновление настроек vstutils:**" -#: ../../config.rst:647 ../../config.rst:730 ../../config.rst:781 +#: ../../config.rst:667 ../../config.rst:750 ../../config.rst:802 msgid "" "Ensure that your vstutils settings have the correct configurations for " "HTTPS. In your ``/etc/vstutils/settings.ini`` (or project " @@ -1690,162 +1783,166 @@ msgstr "" "HTTPS. В вашем ``/etc/vstutils/settings.ini`` (или в проекте " "``settings.ini``):" -#: ../../config.rst:655 +#: ../../config.rst:675 msgid "This ensures that vstutils recognizes the HTTPS connection." msgstr "Это гарантирует, что vstutils распознает соединение по протоколу HTTPS." -#: ../../config.rst:657 +#: ../../config.rst:677 msgid "**Restart Nginx:**" msgstr "**Перезапустите Nginx:**" -#: ../../config.rst:659 +#: ../../config.rst:679 msgid "After making these changes, restart Nginx to apply the new configurations:" -msgstr "После внесения этих изменений перезапустите Nginx, чтобы применить новые конфигурации:" +msgstr "" +"После внесения этих изменений перезапустите Nginx, чтобы применить новые " +"конфигурации:" -#: ../../config.rst:665 +#: ../../config.rst:685 msgid "" "Now, your vstutils application should be accessible via HTTPS through " "Nginx. Adjust these instructions based on your specific environment and " "security considerations." msgstr "" "Теперь ваше приложение vstutils должно быть доступно через HTTPS через " -"Nginx. Измените эти инструкции в зависимости от вашего конкретного окружения и " -"требований к безопасности." +"Nginx. Измените эти инструкции в зависимости от вашего конкретного " +"окружения и требований к безопасности." -#: ../../config.rst:669 +#: ../../config.rst:689 msgid "Traefik" msgstr "Traefik" -#: ../../config.rst:671 +#: ../../config.rst:691 msgid "" "To configure vstutils for operation behind Traefik with TLS, follow these" " steps:" msgstr "" -"Чтобы настроить vstutils для работы через Traefik с поддержкой TLS, выполните следующие шаги:" +"Чтобы настроить vstutils для работы через Traefik с поддержкой TLS, " +"выполните следующие шаги:" -#: ../../config.rst:673 +#: ../../config.rst:693 msgid "**Install Traefik:**" msgstr "**Установка Traefik:**" -#: ../../config.rst:675 +#: ../../config.rst:695 msgid "" "Ensure that Traefik is installed on your server. You can download the " "binary from the official website or use a package manager specific to " "your operating system." msgstr "" -"Убедитесь, что Traefik установлен на вашем сервере. Вы можете скачать двоичный файл с " -"официального сайта или использовать менеджер пакетов, специфичный для вашей " -"операционной системы." +"Убедитесь, что Traefik установлен на вашем сервере. Вы можете скачать " +"двоичный файл с официального сайта или использовать менеджер пакетов, " +"специфичный для вашей операционной системы." -#: ../../config.rst:677 +#: ../../config.rst:697 msgid "**Configure Traefik:**" msgstr "**Настройка Traefik:**" -#: ../../config.rst:679 +#: ../../config.rst:699 msgid "" "Create a Traefik configuration file ``/path/to/traefik.toml``. Here's a " "basic example:" msgstr "" -"Создайте файл конфигурации Traefik ``/path/to/traefik.toml``. Вот базовый пример:" +"Создайте файл конфигурации Traefik ``/path/to/traefik.toml``. Вот базовый" +" пример:" -#: ../../config.rst:701 +#: ../../config.rst:721 msgid "**Create Traefik Toml Configuration:**" msgstr "**Создайте конфигурацию Traefik Toml:**" -#: ../../config.rst:703 +#: ../../config.rst:723 msgid "" "Create the ``/path/to/traefik_config.toml`` file with the following " "content:" -msgstr "" -"Создайте файл ``/path/to/traefik_config.toml`` с следующим " -"содержимым:" +msgstr "Создайте файл ``/path/to/traefik_config.toml`` с следующим содержимым:" -#: ../../config.rst:726 +#: ../../config.rst:746 msgid "Make sure to replace ``your_domain.com`` with your actual domain." msgstr "Обязательно замените ``your_domain.com`` на ваш реальный домен." -#: ../../config.rst:737 +#: ../../config.rst:757 msgid "**Start Traefik:**" msgstr "**Запустите Traefik:**" -#: ../../config.rst:739 +#: ../../config.rst:759 msgid "Start Traefik with the following command:" msgstr "Запустите Traefik следующей командой:" -#: ../../config.rst:745 +#: ../../config.rst:765 msgid "" "Now, your vstutils application should be accessible via HTTPS through " "Traefik. Adjust these instructions based on your specific environment and" " requirements." msgstr "" "Теперь ваше приложение vstutils должно быть доступно через HTTPS через " -"Traefik. Измените эти инструкции в зависимости от вашего конкретного окружения и " -"требований." +"Traefik. Измените эти инструкции в зависимости от вашего конкретного " +"окружения и требований." -#: ../../config.rst:749 +#: ../../config.rst:769 msgid "HAProxy" msgstr "HAProxy" -#: ../../config.rst:751 +#: ../../config.rst:771 msgid "**Install HAProxy:**" msgstr "**Установка HAProxy:**" -#: ../../config.rst:753 +#: ../../config.rst:773 msgid "" "Ensure that HAProxy is installed on your server. You can install it using" " the package manager specific to your operating system." msgstr "" -"Убедитесь, что HAProxy установлен на вашем сервере. Вы можете установить его с помощью" -" менеджера пакетов, специфичного для вашей операционной системы." +"Убедитесь, что HAProxy установлен на вашем сервере. Вы можете установить " +"его с помощью менеджера пакетов, специфичного для вашей операционной " +"системы." -#: ../../config.rst:755 +#: ../../config.rst:775 msgid "**Configure HAProxy:**" msgstr "**Настройка HAProxy:**" -#: ../../config.rst:757 +#: ../../config.rst:777 msgid "" "Create an HAProxy configuration file for your vstutils application. Below" " is a basic example of an HAProxy configuration. Adjust the values based " "on your specific setup." msgstr "" -"Создайте файл конфигурации HAProxy для вашего приложения vstutils. Вот базовый пример конфигурации" -" HAProxy. Измените значения в соответствии с вашей конкретной настройкой." +"Создайте файл конфигурации HAProxy для вашего приложения vstutils. Вот " +"базовый пример конфигурации HAProxy. Измените значения в соответствии с " +"вашей конкретной настройкой." -#: ../../config.rst:777 +#: ../../config.rst:798 msgid "" "Replace ``your_domain.com`` with your actual domain and update the paths " "for SSL certificates." msgstr "" -"Замените ``your_domain.com`` на ваш реальный домен и обновите пути " -"к SSL-сертификатам." +"Замените ``your_domain.com`` на ваш реальный домен и обновите пути к " +"SSL-сертификатам." -#: ../../config.rst:788 +#: ../../config.rst:809 msgid "**Restart HAProxy:**" msgstr "**Перезапуск HAProxy:**" -#: ../../config.rst:790 +#: ../../config.rst:811 msgid "" "After making these changes, restart HAProxy to apply the new " "configurations:" msgstr "" -"После внесения этих изменений перезапустите HAProxy, чтобы применить новые " -"конфигурации." +"После внесения этих изменений перезапустите HAProxy, чтобы применить " +"новые конфигурации." -#: ../../config.rst:796 +#: ../../config.rst:817 msgid "" "Now, your vstutils application should be accessible via HTTPS through " "HAProxy. Adjust these instructions based on your specific environment and" " security considerations." msgstr "" "Теперь ваше приложение vstutils должно быть доступно через HTTPS через " -"HAProxy. Измените эти инструкции в зависимости от вашего конкретного окружения и " -"требований к безопасности." +"HAProxy. Измените эти инструкции в зависимости от вашего конкретного " +"окружения и требований к безопасности." -#: ../../config.rst:800 +#: ../../config.rst:821 msgid "Configuration options" msgstr "Параметры конфигурации" -#: ../../config.rst:802 +#: ../../config.rst:823 msgid "" "This section contains additional information for configure additional " "elements." @@ -1853,7 +1950,7 @@ msgstr "" "В этом разделе содержится дополнительная информация для настройки " "дополнительных элементов." -#: ../../config.rst:804 +#: ../../config.rst:825 msgid "" "If you need set ``https`` for your web settings, you can do it using " "HAProxy, Nginx, Traefik or configure it in ``settings.ini``." @@ -1862,7 +1959,7 @@ msgstr "" " сделать это с помощью HAProxy, Nginx, Traefik или настроить в файле " "``settings.ini``." -#: ../../config.rst:816 +#: ../../config.rst:837 msgid "" "We strictly do not recommend running the web server from root. Use HTTP " "proxy to run on privileged ports." @@ -1870,7 +1967,7 @@ msgstr "" "Мы настоятельно не рекомендуем запускать веб-сервер от имени root. " "Используйте HTTP-прокси, чтобы работать на привилегированных портах." -#: ../../config.rst:818 +#: ../../config.rst:839 msgid "" "You can use `{ENV[HOME:-value]}` (where `HOME` is environment variable, " "`value` is default value) in configuration values." @@ -1878,7 +1975,7 @@ msgstr "" "Вы можете использовать `{ENV[HOME:-value]}` (где `HOME` - переменная " "окружения, `value` - значение по умолчанию) в значениях конфигурации." -#: ../../config.rst:821 +#: ../../config.rst:842 msgid "" "You can use environment variables for setup important settings. But " "config variables has more priority then env. Available settings are: " @@ -1891,7 +1988,7 @@ msgstr "" "``DJANGO_LOG_LEVEL``, ``TIMEZONE`` и некоторые настройки с префиксом " "``[ENV_NAME]``." -#: ../../config.rst:824 +#: ../../config.rst:845 msgid "" "For project without special settings and project levels named ``project``" " these variables will start with ``PROJECT_`` prefix. There is a list of " @@ -1915,7 +2012,7 @@ msgstr "" "``{ENV_NAME}_GLOBAL_THROTTLE_RATE``, и " "``{ENV_NAME}_GLOBAL_THROTTLE_ACTIONS``." -#: ../../config.rst:831 +#: ../../config.rst:852 msgid "" "There are also URI-specific variables for connecting to various services " "such as databases and caches. There are ``DATABASE_URL``, ``CACHE_URL``, " @@ -1929,7 +2026,7 @@ msgstr "" "``SESSIONS_CACHE_URL`` и ``ETAG_CACHE_URL``. Как видно из названий, они " "тесно связаны с ключами и именами соответствующих секций конфигурации." -#: ../../config.rst:835 +#: ../../config.rst:856 msgid "" "We recommend to install ``uvloop`` to your environment and setup ``loop =" " uvloop`` in ``[uvicorn]`` section for performance reasons." @@ -1937,7 +2034,7 @@ msgstr "" "Мы рекомендуем установить ``uvloop`` в ваше окружение и настроить ``loop " "= uvloop`` в разделе ``[uvicorn]`` для повышения производительности." -#: ../../config.rst:837 +#: ../../config.rst:858 msgid "" "In the context of vstutils, the adoption of ``uvloop`` is paramount for " "optimizing the performance of the application, especially because " @@ -1955,7 +2052,7 @@ msgstr "" "библиотеки высокопроизводительного цикла событий, и специально разработан" " для оптимизации скорости выполнения асинхронного кода." -#: ../../config.rst:841 +#: ../../config.rst:862 msgid "" "By leveraging ``uvloop``, developers can achieve substantial performance " "improvements in terms of reduced latency and increased throughput. This " diff --git a/frontend_src/vstutils/AppConfiguration.ts b/frontend_src/vstutils/AppConfiguration.ts index 8229b3ec..cf9d0b25 100644 --- a/frontend_src/vstutils/AppConfiguration.ts +++ b/frontend_src/vstutils/AppConfiguration.ts @@ -1,6 +1,6 @@ -import type { Spec, Info, Schema } from 'swagger-schema-official'; +import type * as swagger from 'swagger-schema-official'; import type { FieldDefinition } from './fields/FieldsResolver'; -import type { HttpMethod } from './utils'; +import { type HttpMethod } from './utils'; declare global { interface Window { @@ -26,7 +26,7 @@ export interface XMenuItem { export type XMenu = XMenuItem[]; -export interface AppInfo extends Info { +export interface AppInfo extends swagger.Info { 'x-settings': { static_path: string; login_url: string; @@ -42,12 +42,16 @@ export interface AppInfo extends Info { 'x-centrifugo-address'?: string; 'x-centrifugo-token'?: string; 'x-subscriptions-prefix': string; + 'x-webpush'?: { + public_key: string; + user_settings_subpath: string | null; + }; [key: string]: any; } export const MODEL_MODES = ['DEFAULT', 'STEP'] as const; -export type ModelDefinition = Schema & { +export type ModelDefinition = swagger.Schema & { properties?: Record; 'x-properties-groups'?: Record; 'x-view-field-name'?: string; @@ -55,11 +59,21 @@ export type ModelDefinition = Schema & { 'x-translate-model'?: string; 'x-hide-not-required'?: boolean; 'x-display-mode'?: typeof MODEL_MODES[number]; + 'x-visibility-data-field-name'?: string; +}; + +export interface Operation extends swagger.Operation { + 'x-hidden'?: boolean; +} + +export type Path = swagger.Path & { + [key in HttpMethod]?: Operation | undefined; }; -export interface AppSchema extends Spec { +export interface AppSchema extends swagger.Spec { info: AppInfo; definitions: Record; + paths: Record; [key: string]: any; } diff --git a/frontend_src/vstutils/api/ApiConnector.ts b/frontend_src/vstutils/api/ApiConnector.ts index 1926c62a..ee8da51f 100644 --- a/frontend_src/vstutils/api/ApiConnector.ts +++ b/frontend_src/vstutils/api/ApiConnector.ts @@ -137,6 +137,7 @@ export class ApiConnector { headers: Record; bulkCollector: BulkCollector = { bulkParts: [] }; baseURL: string | null = null; + disableBulk = false; private _etagsCachePrefix: string | null = null; private _etagsCacheName: string | null = null; @@ -187,7 +188,7 @@ export class ApiConnector { ): Promise>; async makeRequest(req: MakeRequestParamsFetchRaw): Promise; async makeRequest(req: MakeRequestParams): Promise | Response> { - if (req.useBulk) { + if (req.useBulk && !this.disableBulk) { const realBulk: RealBulkRequest = { method: req.method, path: req.path, diff --git a/frontend_src/vstutils/app.ts b/frontend_src/vstutils/app.ts index 92697967..7c13684b 100644 --- a/frontend_src/vstutils/app.ts +++ b/frontend_src/vstutils/app.ts @@ -36,6 +36,7 @@ import * as utils from '@/vstutils/utils'; import type { IView, BaseView } from '@/vstutils/views'; import { ListView, PageNewView, PageView, ViewsTree } from '@/vstutils/views'; import ViewConstructor from '@/vstutils/views/ViewConstructor.js'; +import { setupPushNotifications } from '@/vstutils/webpush'; import type { Cache } from '@/cache'; import type { InnerData } from '@/vstutils/utils'; @@ -223,6 +224,8 @@ export class App implements IApp { this.actions = new ActionsManager(this); + setupPushNotifications(this); + signals.emit(APP_CREATED, this); } diff --git a/frontend_src/vstutils/components/common/OperationButton.vue b/frontend_src/vstutils/components/common/OperationButton.vue index a5f342d4..25c6c6f0 100644 --- a/frontend_src/vstutils/components/common/OperationButton.vue +++ b/frontend_src/vstutils/components/common/OperationButton.vue @@ -1,5 +1,5 @@