django-yadialogs 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. django_yadialogs-0.1.0/.gitignore +11 -0
  2. django_yadialogs-0.1.0/LICENSE +28 -0
  3. django_yadialogs-0.1.0/PKG-INFO +47 -0
  4. django_yadialogs-0.1.0/README.md +19 -0
  5. django_yadialogs-0.1.0/pyproject.toml +93 -0
  6. django_yadialogs-0.1.0/src/yadialogs/__init__.py +4 -0
  7. django_yadialogs-0.1.0/src/yadialogs/api.py +97 -0
  8. django_yadialogs-0.1.0/src/yadialogs/apps.py +14 -0
  9. django_yadialogs-0.1.0/src/yadialogs/dialog.py +104 -0
  10. django_yadialogs-0.1.0/src/yadialogs/exceptions.py +7 -0
  11. django_yadialogs-0.1.0/src/yadialogs/http.py +38 -0
  12. django_yadialogs-0.1.0/src/yadialogs/locale/empty +0 -0
  13. django_yadialogs-0.1.0/src/yadialogs/migrations/0001_initial.py +54 -0
  14. django_yadialogs-0.1.0/src/yadialogs/migrations/__init__.py +0 -0
  15. django_yadialogs-0.1.0/src/yadialogs/models.py +71 -0
  16. django_yadialogs-0.1.0/src/yadialogs/resources/__init__.py +0 -0
  17. django_yadialogs-0.1.0/src/yadialogs/resources/api/__init__.py +0 -0
  18. django_yadialogs-0.1.0/src/yadialogs/resources/api/assets.py +58 -0
  19. django_yadialogs-0.1.0/src/yadialogs/resources/api/status.py +65 -0
  20. django_yadialogs-0.1.0/src/yadialogs/resources/base.py +68 -0
  21. django_yadialogs-0.1.0/src/yadialogs/resources/request/__init__.py +3 -0
  22. django_yadialogs-0.1.0/src/yadialogs/resources/request/entities.py +122 -0
  23. django_yadialogs-0.1.0/src/yadialogs/resources/request/intents.py +86 -0
  24. django_yadialogs-0.1.0/src/yadialogs/resources/request/misc.py +141 -0
  25. django_yadialogs-0.1.0/src/yadialogs/resources/request/reqdata.py +211 -0
  26. django_yadialogs-0.1.0/src/yadialogs/resources/request/request.py +27 -0
  27. django_yadialogs-0.1.0/src/yadialogs/resources/response/__init__.py +4 -0
  28. django_yadialogs-0.1.0/src/yadialogs/resources/response/analytics.py +20 -0
  29. django_yadialogs-0.1.0/src/yadialogs/resources/response/button.py +42 -0
  30. django_yadialogs-0.1.0/src/yadialogs/resources/response/cards.py +111 -0
  31. django_yadialogs-0.1.0/src/yadialogs/resources/response/directives.py +103 -0
  32. django_yadialogs-0.1.0/src/yadialogs/resources/response/respdata.py +84 -0
  33. django_yadialogs-0.1.0/src/yadialogs/resources/response/response.py +32 -0
  34. django_yadialogs-0.1.0/src/yadialogs/settings.py +18 -0
  35. django_yadialogs-0.1.0/src/yadialogs/toolbox.py +5 -0
  36. django_yadialogs-0.1.0/src/yadialogs/urls.py +3 -0
  37. django_yadialogs-0.1.0/src/yadialogs/utils.py +42 -0
  38. django_yadialogs-0.1.0/src/yadialogs/views.py +52 -0
@@ -0,0 +1,11 @@
1
+ .project
2
+ .pydevproject
3
+ .idea
4
+ .tox
5
+ .venv
6
+ __pycache__
7
+ *.pyc
8
+ *.pyo
9
+ *.egg-info
10
+ *.lock
11
+ site/
@@ -0,0 +1,28 @@
1
+ Copyright (c) 2020-2026, Igor `idle sign` Starikov
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the django-yadialogs nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.5
2
+ Name: django-yadialogs
3
+ Version: 0.1.0
4
+ Summary: Yandex Dialogs integration for Django
5
+ Project-URL: Homepage, https://github.com/idlesign/django-yadialogs
6
+ Project-URL: Documentation, https://django-yadialogs.readthedocs.io/
7
+ Author-email: Igor Starikov <idlesign@yandex.ru>
8
+ License-Expression: BSD-3-Clause
9
+ License-File: LICENSE
10
+ Keywords: Django,dialogs,yandex
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: Django
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: BSD License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: django-etc>=1.2.0
26
+ Requires-Dist: requests
27
+ Description-Content-Type: text/markdown
28
+
29
+ # django-yadialogs
30
+
31
+ <https://github.com/idlesign/django-yadialogs>
32
+
33
+ [![release](https://img.shields.io/pypi/v/django-yadialogs.svg)](https://pypi.python.org/pypi/django-yadialogs)
34
+ [![lic](https://img.shields.io/pypi/l/django-yadialogs.svg)](https://pypi.python.org/pypi/django-yadialogs)
35
+ [![coverage](https://img.shields.io/coveralls/idlesign/django-yadialogs/master.svg)](https://coveralls.io/r/idlesign/django-yadialogs)
36
+
37
+ **Work in progress. Stay tuned.**
38
+
39
+ ## Description
40
+
41
+ *Yandex Dialogs integration for Django*
42
+
43
+ Here will be an introductory description.
44
+
45
+ ## Documentation
46
+
47
+ <https://django-yadialogs.readthedocs.io/>
@@ -0,0 +1,19 @@
1
+ # django-yadialogs
2
+
3
+ <https://github.com/idlesign/django-yadialogs>
4
+
5
+ [![release](https://img.shields.io/pypi/v/django-yadialogs.svg)](https://pypi.python.org/pypi/django-yadialogs)
6
+ [![lic](https://img.shields.io/pypi/l/django-yadialogs.svg)](https://pypi.python.org/pypi/django-yadialogs)
7
+ [![coverage](https://img.shields.io/coveralls/idlesign/django-yadialogs/master.svg)](https://coveralls.io/r/idlesign/django-yadialogs)
8
+
9
+ **Work in progress. Stay tuned.**
10
+
11
+ ## Description
12
+
13
+ *Yandex Dialogs integration for Django*
14
+
15
+ Here will be an introductory description.
16
+
17
+ ## Documentation
18
+
19
+ <https://django-yadialogs.readthedocs.io/>
@@ -0,0 +1,93 @@
1
+ [project]
2
+ name = "django-yadialogs"
3
+ dynamic = ["version"]
4
+ description = "Yandex Dialogs integration for Django"
5
+ authors = [
6
+ { name = "Igor Starikov", email = "idlesign@yandex.ru" },
7
+ ]
8
+ readme = "README.md"
9
+ license = "BSD-3-Clause"
10
+ license-files = ["LICENSE"]
11
+ requires-python = ">=3.10"
12
+ keywords = ["Django", "yandex", "dialogs"]
13
+ dependencies = [
14
+ "django-etc>=1.2.0",
15
+ "requests",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 2 - Pre-Alpha",
19
+ "Framework :: Django",
20
+ "Environment :: Web Environment",
21
+ "Intended Audience :: Developers",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
30
+ "License :: OSI Approved :: BSD License",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/idlesign/django-yadialogs"
35
+ Documentation = "https://django-yadialogs.readthedocs.io/"
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ { include-group = "docs" },
40
+ { include-group = "linters" },
41
+ { include-group = "tests" },
42
+ { include-group = "runtime" },
43
+ ]
44
+ docs = [
45
+ "mkdocs-apidescribed-plugin",
46
+ "mkdocs-material",
47
+ "mkdocs-navsorted-plugin",
48
+ ]
49
+ linters = [
50
+ "ruff==0.13.1",
51
+ ]
52
+ tests = [
53
+ "coverage",
54
+ "pytest",
55
+ "pytest-datafixtures",
56
+ "pytest-djangoapp>=1.6.0",
57
+ "pytest-responsemock",
58
+ ]
59
+ runtime = [
60
+ "Django>=3.2.9",
61
+ ]
62
+
63
+ [build-system]
64
+ requires = ["hatchling"]
65
+ build-backend = "hatchling.build"
66
+
67
+ [tool.hatch.version]
68
+ path = "src/yadialogs/__init__.py"
69
+
70
+ [tool.hatch.build.targets.wheel]
71
+ packages = ["src/yadialogs"]
72
+
73
+ [tool.hatch.build.targets.sdist]
74
+ packages = ["src/"]
75
+
76
+ [tool.pytest.ini_options]
77
+ testpaths = [
78
+ "tests",
79
+ ]
80
+ addopts = "--pyargs"
81
+
82
+ [tool.coverage.run]
83
+ source = [
84
+ "src/yadialogs",
85
+ ]
86
+ omit = [
87
+ "src/yadialogs/migrations/*",
88
+ ]
89
+
90
+ [tool.makeapp.tests]
91
+ deps = [
92
+ "django~=${{ django-version }}.0",
93
+ ]
@@ -0,0 +1,4 @@
1
+ VERSION = '0.1.0'
2
+
3
+
4
+ default_app_config = 'yadialogs.apps.YadialogsConfig'
@@ -0,0 +1,97 @@
1
+ from pathlib import Path
2
+ from typing import Union, Type, List
3
+
4
+ from .http import HttpClient
5
+ from .resources.api.assets import Image, Sound
6
+ from .resources.api.status import Status, ImagesInfo, SoundsInfo
7
+ from .settings import API_KEY
8
+
9
+
10
+ class _ApiRealm:
11
+
12
+ _name: str = ''
13
+ _asset_cls: Union[Type[Image], Type[Sound]]
14
+
15
+ def __init__(self, *, api: 'DialogsApi'):
16
+ self._api = api
17
+
18
+ def _get_url_base(self, *, dialog_id: str) -> str:
19
+ return f'{self._api._url_base}/skills/{dialog_id}/{self._name}'
20
+
21
+ def get_info(self) -> Union[ImagesInfo, SoundsInfo]:
22
+ """Возвращает информацию о хранилище ресурсов (изображений или звукозаписей)."""
23
+ return getattr(self._api.get_status(), self._name)
24
+
25
+ def upload(self, *, dialog_id: str, fpath: Union[Path, str]):
26
+ """Загружает указанный файл ресурса (изображения или звукозаписи.)
27
+
28
+ :param dialog_id: Идентификатор диалога.
29
+ :param fpath: Путь до файла.
30
+
31
+ """
32
+ response = self._api._client.upload(self._get_url_base(dialog_id=dialog_id), fpath=fpath)
33
+ return self._asset_cls(dialog_id=dialog_id, raw=response[self._name.rstrip('s')])
34
+
35
+ def list(self, *, dialog_id: str) -> Union[List[Image], List[Sound]]:
36
+ """Возвращает список с объектами ресурсов (изображений или звукозаписей).
37
+
38
+ :param dialog_id: Идентификатор диалога.
39
+
40
+ """
41
+ response = self._api._client.get(self._get_url_base(dialog_id=dialog_id))
42
+ asset_cls = self._asset_cls
43
+ return [asset_cls(dialog_id=dialog_id, raw=asset) for asset in response[self._name]]
44
+
45
+ def delete(self, *, asset: Union[str, Image, Sound], dialog_id: str = '') -> bool:
46
+ """Удаляет указанный ресурс.
47
+
48
+ :param asset: Объект ресурса или его идентификатор.
49
+ :param dialog_id: Идентификатор диалога.
50
+
51
+ """
52
+ dialog_id = dialog_id or asset.dialog_id
53
+ return self._api._client.delete(f'{self._get_url_base(dialog_id=dialog_id)}/{asset}')['result'] == 'ok'
54
+
55
+
56
+ class _ImagesApi(_ApiRealm):
57
+ """Интерфейс для доступа к данным хранилища изображений."""
58
+
59
+ _name: str = 'images'
60
+ _asset_cls: Type[Image] = Image
61
+
62
+
63
+ class _SoundsApi(_ApiRealm):
64
+ """Интерфейс для доступа к данным хранилища звукозаписей."""
65
+
66
+ _name: str = 'sounds'
67
+ _asset_cls: Type[Sound] = Sound
68
+
69
+ def get(self, *, dialog_id: str, asset_id: str) -> Union[Image, Sound]:
70
+ """Возвращает объект ресурса с информацией о нём.
71
+
72
+ :param dialog_id: Идентификатор диалога.
73
+ :param asset_id: Идентификатор ресурса.
74
+
75
+ """
76
+ response = self._api._client.get(f'{self._get_url_base(dialog_id=dialog_id)}/{asset_id}')
77
+ return self._asset_cls(dialog_id=dialog_id, raw=response[self._name.rstrip('s')])
78
+
79
+
80
+ class DialogsApi:
81
+ """Предоставляет интерфейс для взаимодействия с API Диалогов."""
82
+
83
+ _url_base: str = 'https://dialogs.yandex.net/api/v1'
84
+
85
+ def __init__(self, *, token: str = ''):
86
+ self._client = HttpClient(token=token or API_KEY)
87
+
88
+ self.images = _ImagesApi(api=self)
89
+ """Интерфейс для доступа к хранилищу изображений."""
90
+
91
+ self.sounds = _SoundsApi(api=self)
92
+ """Интерфейс для доступа к хранилищу звукозаписей."""
93
+
94
+ def get_status(self) -> Status:
95
+ """Возвращает объект со статусной информацией."""
96
+ response = self._client.get(f'{self._url_base}/status')
97
+ return Status(response)
@@ -0,0 +1,14 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class YadialogsConfig(AppConfig):
5
+ """Конфигурация приложения."""
6
+
7
+ name = 'yadialogs'
8
+ verbose_name = 'Яндекс.Диалоги'
9
+ default_auto_field = 'django.db.models.AutoField'
10
+
11
+ def ready(self):
12
+ """Инициализирует приложение."""
13
+ from .toolbox import autodiscover_dialogs
14
+ autodiscover_dialogs()
@@ -0,0 +1,104 @@
1
+ from typing import Dict, Type, Optional, Union
2
+
3
+ from django.http import HttpRequest
4
+
5
+ from .exceptions import HandledRequestException
6
+ from .resources.request import Request, TypeRequestData
7
+ from .resources.response import Response
8
+
9
+
10
+ class Dialog:
11
+ """Base for dialogs."""
12
+
13
+ id: str = ''
14
+ """Identifier given to a dialog after its registration."""
15
+
16
+ alias: str = ''
17
+ """Convenience alias for a dialog."""
18
+
19
+ dialogs: Dict[str, Dict[str, Type['Dialog']]] = {
20
+ 'alias': {},
21
+ 'id': {},
22
+ }
23
+ """All registered dialogs."""
24
+
25
+ verify_id: bool = True
26
+ """Perform an additional check verifying
27
+ that a dialog ID in request matches the dialog ID from this class.
28
+ """
29
+
30
+ debug: bool = False
31
+
32
+ def __init__(self, data: dict, *, http_request: HttpRequest):
33
+ """
34
+ :param data: Data from Dialogs server.
35
+ :param http_request: Source Django request object.
36
+
37
+ """
38
+ self.request = Request(raw=data)
39
+ self.http_request = http_request
40
+
41
+ def __init_subclass__(cls):
42
+
43
+ super().__init_subclass__()
44
+
45
+ if cls.id and cls.alias:
46
+ cls.dialogs['alias'][cls.alias] = cls
47
+ cls.dialogs['id'][cls.id] = cls
48
+
49
+ @classmethod
50
+ def handle_request(cls, data: dict, *, http_request: HttpRequest) -> dict:
51
+ """Handles request from Dialogs server.
52
+
53
+ :param data: Data from server.
54
+ :param http_request: Source Django request object.
55
+
56
+ """
57
+ dialog = cls(data, http_request=http_request)
58
+
59
+ try:
60
+
61
+ if cls.verify_id and (dialog_id := dialog.request.session.dialog_id) != dialog.id:
62
+ raise HandledRequestException(
63
+ f"Requested dialog ID \"{dialog_id}\" doesn't match the ID from \"{dialog.alias}\"."
64
+ )
65
+
66
+ response = dialog.process(data=dialog.request.data)
67
+
68
+ if isinstance(response, str):
69
+ response = dialog.response_from_request(text=response)
70
+
71
+ except HandledRequestException as e:
72
+ response = dialog.response_for_exception(e)
73
+
74
+ return response.serialize()
75
+
76
+ @classmethod
77
+ def get(cls, dialog: str, by_id: bool = False) -> Type['Dialog'] | None:
78
+ """Get dialog type by its alias or id.
79
+
80
+ :param dialog: Dialog alias or id.
81
+
82
+ :param by_id: Get by id instead of an alias.
83
+
84
+ """
85
+ return cls.dialogs['id' if by_id else 'alias'].get(dialog)
86
+
87
+ def process(self, *, data: TypeRequestData, **kwargs) -> Union[Response, str]:
88
+ """Should implement server request processing and return a response.
89
+ Response could be a new Response object
90
+ or just a string to generate Response object for.
91
+ """
92
+ raise HandledRequestException(f'Request processing is not implemented for "{self.alias}" dialog.')
93
+
94
+ def response_for_exception(self, e: Exception) -> Response:
95
+ return Response(f'Error: {e}', end_session=True)
96
+
97
+ def response_from_request(self, *, text: str = '', end_session: bool = False) -> Response:
98
+
99
+ response = Response(text, end_session=end_session)
100
+
101
+ if states := self.request.states:
102
+ response.states = states
103
+
104
+ return response
@@ -0,0 +1,7 @@
1
+
2
+ class YaDialogsException(Exception):
3
+ """База для исключений yadialogs."""
4
+
5
+
6
+ class HandledRequestException(YaDialogsException):
7
+ """Обработанное исключение для запросов от Диалогов."""
@@ -0,0 +1,38 @@
1
+ from pathlib import Path
2
+ from typing import Callable, Union
3
+
4
+ from requests import Session
5
+
6
+ from . import VERSION as VERSION_STR
7
+ from .settings import API_TIMEOUT
8
+
9
+
10
+ class HttpClient:
11
+ """Клиент для соверщения HTTP запросов."""
12
+
13
+ timeout: int = API_TIMEOUT
14
+
15
+ user_agent: str = ''
16
+
17
+ def __init__(self, *, token: str):
18
+ session = Session()
19
+ session.headers.update({
20
+ 'Authorization': f'OAuth {token}',
21
+ 'User-Agent': f'django-yadialogs/{VERSION_STR}',
22
+ })
23
+ self.session = session
24
+
25
+ def _request(self, *, method: Callable, url, **kwargs) -> dict:
26
+ response = method(url, **{'timeout': self.timeout, **kwargs})
27
+ response.raise_for_status()
28
+ data = response.json()
29
+ return data
30
+
31
+ def delete(self, url: str) -> dict:
32
+ return self._request(method=self.session.delete, url=url)
33
+
34
+ def upload(self, url: str, *, fpath: Union[Path, str]) -> dict:
35
+ return self._request(method=self.session.post, url=url, files={'file': Path(fpath).open('rb')})
36
+
37
+ def get(self, url: str) -> dict:
38
+ return self._request(method=self.session.get, url=url)
File without changes
@@ -0,0 +1,54 @@
1
+ # Generated by Django 6.1.3 on 2026-11-10 08:13
2
+
3
+ import django.core.files.storage
4
+ import yadialogs.models
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ initial = True
11
+
12
+ dependencies = [
13
+ ]
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name='Image',
18
+ fields=[
19
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
+ ('rid', models.CharField(help_text='Идентификатор ресурса', max_length=50, verbose_name='ID')),
21
+ ('dt_uploaded', models.DateTimeField(db_column='db_upl', verbose_name='Загружен')),
22
+ ('status', models.IntegerField(choices=[(1, 'Загружен'), (2, 'Ошибка')], default=1, verbose_name='Статус')),
23
+ ('status_hint', models.TextField(help_text='Подсказка для состояния.', verbose_name='Подсказка')),
24
+ ('dialog_alias', models.CharField(db_column='dalias', db_index=True, help_text='Псевдоним диалога', max_length=36, verbose_name='Диалог')),
25
+ ('alias', models.CharField(default=None, help_text='Краткое имя для удобной адресации ресурса', max_length=50, null=True, unique=True, verbose_name='Псевдоним')),
26
+ ('group', models.UUIDField(db_column='grp', default=None, help_text='Идентификатор, объединяющий группу ресурсов', null=True, verbose_name='Группа')),
27
+ ('file', models.FileField(storage=django.core.files.storage.FileSystemStorage(location='/tmp'), upload_to=yadialogs.models.get_upload, verbose_name='Файл')),
28
+ ('size', models.IntegerField(verbose_name='Размер файла')),
29
+ ],
30
+ options={
31
+ 'verbose_name': 'Изображение',
32
+ 'verbose_name_plural': 'Изображения',
33
+ },
34
+ ),
35
+ migrations.CreateModel(
36
+ name='Sound',
37
+ fields=[
38
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
39
+ ('rid', models.CharField(help_text='Идентификатор ресурса', max_length=50, verbose_name='ID')),
40
+ ('dt_uploaded', models.DateTimeField(db_column='db_upl', verbose_name='Загружен')),
41
+ ('status', models.IntegerField(choices=[(1, 'Загружен'), (2, 'Ошибка')], default=1, verbose_name='Статус')),
42
+ ('status_hint', models.TextField(help_text='Подсказка для состояния.', verbose_name='Подсказка')),
43
+ ('dialog_alias', models.CharField(db_column='dalias', db_index=True, help_text='Псевдоним диалога', max_length=36, verbose_name='Диалог')),
44
+ ('alias', models.CharField(default=None, help_text='Краткое имя для удобной адресации ресурса', max_length=50, null=True, unique=True, verbose_name='Псевдоним')),
45
+ ('group', models.UUIDField(db_column='grp', default=None, help_text='Идентификатор, объединяющий группу ресурсов', null=True, verbose_name='Группа')),
46
+ ('file', models.FileField(storage=django.core.files.storage.FileSystemStorage(location='/tmp'), upload_to=yadialogs.models.get_upload, verbose_name='Файл')),
47
+ ('size', models.IntegerField(verbose_name='Размер файла')),
48
+ ],
49
+ options={
50
+ 'verbose_name': 'Звук',
51
+ 'verbose_name_plural': 'Звуки',
52
+ },
53
+ ),
54
+ ]
@@ -0,0 +1,71 @@
1
+ from django.core.files.storage import FileSystemStorage
2
+ from django.db import models
3
+
4
+
5
+ TmpStorage = FileSystemStorage(location='/tmp')
6
+
7
+
8
+ def get_upload(instance: 'Asset', filename):
9
+ return 'some'
10
+
11
+
12
+ class Asset(models.Model):
13
+ """Ресурс. Базовая модель."""
14
+
15
+ _api_realm: str = ''
16
+
17
+ class Statuses(models.IntegerChoices):
18
+ """Статусы ресурсов."""
19
+
20
+ UPLOADED = 1, 'Загружен'
21
+ ERROR = 2, 'Ошибка'
22
+
23
+ rid = models.CharField('ID', max_length=50, help_text='Идентификатор ресурса')
24
+
25
+ dt_uploaded = models.DateTimeField('Загружен', db_column='db_upl')
26
+
27
+ status = models.IntegerField('Статус', choices=Statuses.choices, default=Statuses.UPLOADED)
28
+ status_hint = models.TextField('Подсказка', help_text='Подсказка для состояния.')
29
+
30
+ dialog_alias = models.CharField(
31
+ 'Диалог', max_length=36, db_index=True, db_column='dalias',
32
+ help_text='Псевдоним диалога')
33
+
34
+ alias = models.CharField(
35
+ 'Псевдоним', max_length=50, null=True, default=None, unique=True,
36
+ help_text='Краткое имя для удобной адресации ресурса')
37
+
38
+ group = models.UUIDField(
39
+ 'Группа', null=True, default=None, db_column='grp',
40
+ help_text='Идентификатор, объединяющий группу ресурсов')
41
+
42
+ file = models.FileField(
43
+ 'Файл', upload_to=get_upload, storage=TmpStorage)
44
+
45
+ size = models.IntegerField('Размер файла')
46
+
47
+ class Meta:
48
+ abstract = True
49
+
50
+ def __str__(self):
51
+ return f'{self.rid} [{self.alias}]'
52
+
53
+
54
+ class Image(Asset):
55
+ """Изображение."""
56
+
57
+ _api_realm: str = 'images'
58
+
59
+ class Meta:
60
+ verbose_name = 'Изображение'
61
+ verbose_name_plural = 'Изображения'
62
+
63
+
64
+ class Sound(Asset):
65
+ """Звукозапись."""
66
+
67
+ _api_realm: str = 'sounds'
68
+
69
+ class Meta:
70
+ verbose_name = 'Звук'
71
+ verbose_name_plural = 'Звуки'
@@ -0,0 +1,58 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from django.utils.dateparse import parse_datetime
4
+
5
+ if TYPE_CHECKING:
6
+ from datetime import datetime
7
+
8
+ from ..base import Container
9
+
10
+
11
+ class _Asset(Container):
12
+
13
+ def __init__(self, *, dialog_id: str, raw: dict):
14
+
15
+ super().__init__(raw)
16
+
17
+ self.dialog_id = dialog_id
18
+ """Идентификатор диалога."""
19
+
20
+ self.id: str = raw['id']
21
+ """Идентификатор ресурса."""
22
+
23
+ self.size: int = raw['size'] or 0
24
+ """Размер ресурса, байтов."""
25
+
26
+ self.uploaded: datetime = parse_datetime(raw['createdAt'])
27
+ """Дата и время загрузки ресурса."""
28
+
29
+ def __str__(self) -> str:
30
+ return self.id
31
+
32
+
33
+ class Image(_Asset):
34
+ """Изображение."""
35
+
36
+ def __init__(self, *, dialog_id: str, raw: dict):
37
+
38
+ super().__init__(dialog_id=dialog_id, raw=raw)
39
+
40
+ self.orig_url: str = raw.get('origUrl') or ''
41
+ """URL изображения (если оно было загружено используя URL)."""
42
+
43
+
44
+ class Sound(_Asset):
45
+ """Звукозапись."""
46
+
47
+ def __init__(self, *, dialog_id: str, raw: dict):
48
+
49
+ super().__init__(dialog_id=dialog_id, raw=raw)
50
+
51
+ self.orig_name: str = raw['originalName']
52
+ """Оригинальное имя файла."""
53
+
54
+ self.is_processed: bool = raw['isProcessed']
55
+ """Флаг законченности обработки."""
56
+
57
+ self.error: str = raw['error'] or ''
58
+ """Описание ошибки обработки."""