This commit is contained in:
Kseninia Mikhaylova 2024-08-06 15:34:23 +03:00
parent 5f027e2066
commit a415ab31f4
14 changed files with 444 additions and 133 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
back/.venv/

View File

@ -4,19 +4,33 @@ FROM ci.svs-tech.pro/library/node:22-bookworm-slim
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
# Configure Poetry
ENV POETRY_VERSION=1.8.3
ENV POETRY_HOME=/opt/poetry
ENV POETRY_VENV=/opt/poetry-venv
ENV POETRY_CACHE_DIR=/opt/.cache
ENV WORKING_DIR=/app
WORKDIR ${WORKING_DIR}
RUN apt-get update
RUN apt-get install curl -y
RUN apt-get install python3-pip -y
RUN apt-get install python3-poetry -y
RUN apt-get install python3-venv -y
ENV PATH="${PATH}:${POETRY_VENV}/bin"
# Install poetry separated from system interpreter
RUN python3 -m venv $POETRY_VENV \
&& $POETRY_VENV/bin/pip install -U pip setuptools \
&& $POETRY_VENV/bin/pip install poetry==${POETRY_VERSION}
RUN poetry -vvv --version
COPY pyproject.toml poetry.lock ./
COPY pyproject.toml ./
RUN poetry install
COPY . ${WORKING_DIR}
CMD ["poetry", "run", "python", "manage.py", "runserver", "0.0.0.0:8000"]
# CMD ["poetry", "run", "/app/.venv/bin/celery"]

13
back/api/celery.py Normal file
View File

@ -0,0 +1,13 @@
import os
from django.conf import settings
from celery import Celery
from datetime import timedelta
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings")
celery_app = Celery("api")
celery_app.config_from_object(f'django.conf:settings', namespace='CELERY')
celery_app.autodiscover_tasks()

View File

@ -118,6 +118,8 @@ DATABASES = {
}
}
CELERY_BROKER_URL = f'redis://{os.getenv("REDIS_HOST")}:6379/0'
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators

264
back/poetry.lock generated
View File

@ -1,5 +1,19 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "amqp"
version = "5.2.0"
description = "Low-level AMQP client for Python (fork of amqplib)."
optional = false
python-versions = ">=3.6"
files = [
{file = "amqp-5.2.0-py3-none-any.whl", hash = "sha256:827cb12fb0baa892aad844fd95258143bce4027fdac4fccddbc43330fd281637"},
{file = "amqp-5.2.0.tar.gz", hash = "sha256:a1ecff425ad063ad42a486c902807d1482311481c8ad95a72694b2975e75f7fd"},
]
[package.dependencies]
vine = ">=5.0.0,<6.0.0"
[[package]]
name = "anyio"
version = "4.4.0"
@ -62,6 +76,85 @@ files = [
[package.extras]
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
[[package]]
name = "async-timeout"
version = "4.0.3"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.7"
files = [
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
]
[[package]]
name = "billiard"
version = "4.2.0"
description = "Python multiprocessing fork with improvements and bugfixes"
optional = false
python-versions = ">=3.7"
files = [
{file = "billiard-4.2.0-py3-none-any.whl", hash = "sha256:07aa978b308f334ff8282bd4a746e681b3513db5c9a514cbdd810cbbdc19714d"},
{file = "billiard-4.2.0.tar.gz", hash = "sha256:9a3c3184cb275aa17a732f93f65b20c525d3d9f253722d26a82194803ade5a2c"},
]
[[package]]
name = "celery"
version = "5.4.0"
description = "Distributed Task Queue."
optional = false
python-versions = ">=3.8"
files = [
{file = "celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64"},
{file = "celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706"},
]
[package.dependencies]
billiard = ">=4.2.0,<5.0"
click = ">=8.1.2,<9.0"
click-didyoumean = ">=0.3.0"
click-plugins = ">=1.1.1"
click-repl = ">=0.2.0"
kombu = ">=5.3.4,<6.0"
python-dateutil = ">=2.8.2"
redis = {version = ">=4.5.2,<4.5.5 || >4.5.5,<6.0.0", optional = true, markers = "extra == \"redis\""}
tzdata = ">=2022.7"
vine = ">=5.1.0,<6.0"
[package.extras]
arangodb = ["pyArango (>=2.0.2)"]
auth = ["cryptography (==42.0.5)"]
azureblockblob = ["azure-storage-blob (>=12.15.0)"]
brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"]
cassandra = ["cassandra-driver (>=3.25.0,<4)"]
consul = ["python-consul2 (==0.1.5)"]
cosmosdbsql = ["pydocumentdb (==2.3.5)"]
couchbase = ["couchbase (>=3.0.0)"]
couchdb = ["pycouchdb (==1.14.2)"]
django = ["Django (>=2.2.28)"]
dynamodb = ["boto3 (>=1.26.143)"]
elasticsearch = ["elastic-transport (<=8.13.0)", "elasticsearch (<=8.13.0)"]
eventlet = ["eventlet (>=0.32.0)"]
gcs = ["google-cloud-storage (>=2.10.0)"]
gevent = ["gevent (>=1.5.0)"]
librabbitmq = ["librabbitmq (>=2.0.0)"]
memcache = ["pylibmc (==1.6.3)"]
mongodb = ["pymongo[srv] (>=4.0.2)"]
msgpack = ["msgpack (==1.0.8)"]
pymemcache = ["python-memcached (>=1.61)"]
pyro = ["pyro4 (==4.82)"]
pytest = ["pytest-celery[all] (>=1.0.0)"]
redis = ["redis (>=4.5.2,!=4.5.5,<6.0.0)"]
s3 = ["boto3 (>=1.26.143)"]
slmq = ["softlayer-messaging (>=1.0.3)"]
solar = ["ephem (==4.1.5)"]
sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"]
sqs = ["boto3 (>=1.26.143)", "kombu[sqs] (>=5.3.4)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"]
tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"]
yaml = ["PyYAML (>=3.10)"]
zookeeper = ["kazoo (>=1.3.1)"]
zstd = ["zstandard (==0.22.0)"]
[[package]]
name = "certifi"
version = "2024.7.4"
@ -172,6 +265,69 @@ files = [
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
]
[[package]]
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "click-didyoumean"
version = "0.3.1"
description = "Enables git-like *did-you-mean* feature in click"
optional = false
python-versions = ">=3.6.2"
files = [
{file = "click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c"},
{file = "click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463"},
]
[package.dependencies]
click = ">=7"
[[package]]
name = "click-plugins"
version = "1.1.1"
description = "An extension module for click to enable registering CLI commands via setuptools entry-points."
optional = false
python-versions = "*"
files = [
{file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"},
{file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"},
]
[package.dependencies]
click = ">=4.0"
[package.extras]
dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"]
[[package]]
name = "click-repl"
version = "0.3.0"
description = "REPL plugin for Click"
optional = false
python-versions = ">=3.6"
files = [
{file = "click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"},
{file = "click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"},
]
[package.dependencies]
click = ">=7.0"
prompt-toolkit = ">=3.0.36"
[package.extras]
testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"]
[[package]]
name = "colorama"
version = "0.4.6"
@ -220,13 +376,13 @@ django = ">=3.2"
[[package]]
name = "django-filter"
version = "24.2"
version = "24.3"
description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
optional = false
python-versions = ">=3.8"
files = [
{file = "django-filter-24.2.tar.gz", hash = "sha256:48e5fc1da3ccd6ca0d5f9bb550973518ce977a4edde9d2a8a154a7f4f0b9f96e"},
{file = "django_filter-24.2-py3-none-any.whl", hash = "sha256:df2ee9857e18d38bed203c8745f62a803fa0f31688c9fe6f8e868120b1848e48"},
{file = "django_filter-24.3-py3-none-any.whl", hash = "sha256:c4852822928ce17fb699bcfccd644b3574f1a2d80aeb2b4ff4f16b02dd49dc64"},
{file = "django_filter-24.3.tar.gz", hash = "sha256:d8ccaf6732afd21ca0542f6733b11591030fa98669f8d15599b358e24a2cd9c3"},
]
[package.dependencies]
@ -341,6 +497,38 @@ files = [
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
]
[[package]]
name = "kombu"
version = "5.3.7"
description = "Messaging library for Python."
optional = false
python-versions = ">=3.8"
files = [
{file = "kombu-5.3.7-py3-none-any.whl", hash = "sha256:5634c511926309c7f9789f1433e9ed402616b56836ef9878f01bd59267b4c7a9"},
{file = "kombu-5.3.7.tar.gz", hash = "sha256:011c4cd9a355c14a1de8d35d257314a1d2456d52b7140388561acac3cf1a97bf"},
]
[package.dependencies]
amqp = ">=5.1.1,<6.0.0"
vine = "*"
[package.extras]
azureservicebus = ["azure-servicebus (>=7.10.0)"]
azurestoragequeues = ["azure-identity (>=1.12.0)", "azure-storage-queue (>=12.6.0)"]
confluentkafka = ["confluent-kafka (>=2.2.0)"]
consul = ["python-consul2"]
librabbitmq = ["librabbitmq (>=2.0.0)"]
mongodb = ["pymongo (>=4.1.1)"]
msgpack = ["msgpack"]
pyro = ["pyro4"]
qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"]
redis = ["redis (>=4.5.2,!=4.5.5,!=5.0.2)"]
slmq = ["softlayer-messaging (>=1.0.3)"]
sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"]
sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"]
yaml = ["PyYAML (>=3.10)"]
zookeeper = ["kazoo (>=2.8.0)"]
[[package]]
name = "markdown"
version = "3.6"
@ -475,6 +663,20 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
typing = ["typing-extensions"]
xmp = ["defusedxml"]
[[package]]
name = "prompt-toolkit"
version = "3.0.47"
description = "Library for building powerful interactive command lines in Python"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"},
{file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"},
]
[package.dependencies]
wcwidth = "*"
[[package]]
name = "psutil"
version = "5.9.8"
@ -584,6 +786,20 @@ files = [
{file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"},
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
]
[package.dependencies]
six = ">=1.5"
[[package]]
name = "python-dotenv"
version = "1.0.1"
@ -636,6 +852,24 @@ files = [
{file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
]
[[package]]
name = "redis"
version = "5.0.8"
description = "Python client for Redis database and key-value store"
optional = false
python-versions = ">=3.7"
files = [
{file = "redis-5.0.8-py3-none-any.whl", hash = "sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4"},
{file = "redis-5.0.8.tar.gz", hash = "sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870"},
]
[package.dependencies]
async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""}
[package.extras]
hiredis = ["hiredis (>1.0.0)"]
ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"]
[[package]]
name = "requests"
version = "2.32.3"
@ -767,7 +1001,29 @@ h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "vine"
version = "5.1.0"
description = "Python promises."
optional = false
python-versions = ">=3.6"
files = [
{file = "vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc"},
{file = "vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0"},
]
[[package]]
name = "wcwidth"
version = "0.2.13"
description = "Measures the displayed width of unicode strings in a terminal"
optional = false
python-versions = "*"
files = [
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "e032592fbe04dbdaffbb40512f6dfa13bcd6f0673c9923e79ba0d852205e2c19"
content-hash = "6362300a16177d23be8b5603dfd1ba3cf5ce477bb51962661c8bcf818433cf26"

View File

@ -19,6 +19,7 @@ django-cors-headers = "^4.3.1"
python-telegram-bot = { extras = ["job-queue"], version = "^21.3" }
more-itertools = "^10.3.0"
djangoviz = "^0.1.1"
celery = { extras = ["redis"], version = "^5.4.0" }
[tool.poetry.group.dev.dependencies]
taskipy = "^1.12.2"

View File

@ -1,90 +1,20 @@
from django.apps import AppConfig
import asyncio
import threading
import queue
import os
class TgBotUpdater:
is_run = False
app = None
tgbot_class = None
my_queue = queue.Queue()
return_values = dict()
import logging
logger = logging.getLogger(__name__)
def __init__(self) -> None:
self.loop = asyncio.get_event_loop()
try:
from .tgbot import TgBot
self.tgbot_class = TgBot()
except Exception as e:
self.logger.error(e)
TgBotUpdater.is_run = True
async def _set_webhook(self):
await self.tgbot_class.set_webhook()
async def _start_app(self):
await self.tgbot_class.start_app()
async def _set_hadlers(self):
await self.tgbot_class.set_handlers()
async def _run_func(self):
from .tgbot import TgBot
while hasattr(TgBot, "app"):
# self.logger.info(f"check updates in {await TgBot.app.bot.get_webhook_info()}")
if not TgBotUpdater.my_queue.empty():
item = TgBotUpdater.my_queue.get()
if (
isinstance(item, dict)
and "name" in item
and item["name"].startswith("admin_")
):
await self.tgbot_class.admin_action(item["name"], item["queryset"])
else:
try:
await TgBot.app.process_update(item)
except Exception as e:
print(f"Error in tg thread {e}")
await TgBot.app.process_update(item)
TgBotUpdater.my_queue.task_done()
await asyncio.sleep(1)
async def main(self):
await asyncio.gather(
self._set_webhook(),
self._set_hadlers(),
self._start_app(),
self._run_func(),
)
def run_func(self):
asyncio.set_event_loop(self.loop)
self.loop.run_until_complete(self.main())
self.loop.close()
class TgbotConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "tgbot"
def ready(self):
if not TgBotUpdater.is_run and os.environ.get("RUN_MAIN", None) == "true":
from .updater import tg_bot_updater_instance
if not tg_bot_updater_instance.is_run and os.environ.get("RUN_MAIN", None) == "true":
threading.Thread(
target=(TgBotUpdater().run_func),
target=(tg_bot_updater_instance.run_func),
name="tg_updater_thread",
daemon=True,
).start()
tg_bot_updater_instance.is_run = True
return super().ready()

View File

@ -29,7 +29,6 @@ from django.conf import settings
from django.urls import reverse
from django.db import models
from .apps import TgBotUpdater
from .models import TgItem, TmcElement, TmcField
from tmc.models import CustomTable, BaseCustomField, Territory, TerritoryItem
@ -65,20 +64,21 @@ class TgBot:
await TgBot.app.initialize()
async def admin_action(self, name, queryset):
from .updater import tg_bot_updater_instance
if name == "admin_get_image":
item = queryset
try:
result = await TgBot.app.bot.get_file(item)
TgBotUpdater.return_values[item] = result.file_path
tg_bot_updater_instance.return_values[item] = result.file_path
except Exception as e:
TgBotUpdater.return_values[item] = None
tg_bot_updater_instance.return_values[item] = None
if name == "admin_get_name":
item = queryset
try:
result = await TgBot.app.bot.get_chat(item)
TgBotUpdater.return_values[item] = result.effective_name
tg_bot_updater_instance.return_values[item] = result.effective_name
except Exception as e:
TgBotUpdater.return_values[item] = None
tg_bot_updater_instance.return_values[item] = None
async def set_handlers(self):
TgBot.app.add_handler(

71
back/tgbot/updater.py Normal file
View File

@ -0,0 +1,71 @@
import asyncio
import queue
from .tgbot import TgBot
class TgBotUpdater:
is_run = False
app = None
tgbot_class = None
my_queue = queue.Queue()
return_values = dict()
import logging
logger = logging.getLogger(__name__)
def __init__(self) -> None:
self.loop = asyncio.get_event_loop()
try:
self.tgbot_class = TgBot()
except Exception as e:
self.logger.error(e)
async def _set_webhook(self):
await self.tgbot_class.set_webhook()
async def _start_app(self):
await self.tgbot_class.start_app()
async def _set_hadlers(self):
await self.tgbot_class.set_handlers()
async def _run_func(self):
from .tgbot import TgBot
while hasattr(TgBot, "app"):
# self.logger.info(f"check updates in {await TgBot.app.bot.get_webhook_info()}")
if not self.my_queue.empty():
item = self.my_queue.get()
if (
isinstance(item, dict)
and "name" in item
and item["name"].startswith("admin_")
):
await self.tgbot_class.admin_action(item["name"], item["queryset"])
else:
try:
await TgBot.app.process_update(item)
except Exception as e:
print(f"Error in tg thread {e}")
await TgBot.app.process_update(item)
self.my_queue.task_done()
await asyncio.sleep(1)
async def main(self):
await asyncio.gather(
self._set_webhook(),
self._set_hadlers(),
self._start_app(),
self._run_func(),
)
def run_func(self):
asyncio.set_event_loop(self.loop)
self.loop.run_until_complete(self.main())
self.loop.close()
tg_bot_updater_instance = TgBotUpdater()

View File

@ -13,7 +13,7 @@ from rest_framework.decorators import action
from django_filters.rest_framework import DjangoFilterBackend
from .tgbot import TgBot
from .apps import TgBotUpdater
from .updater import tg_bot_updater_instance
from .models import TgItem, TmcField
from .serializers import TgItemSerializer, TmcFieldSerializer
@ -50,7 +50,7 @@ class TgItemViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=["post"], url_path=settings.TGBOT["WEBHOOK_URL"])
def send_tg_data(self, request):
TgBotUpdater.my_queue.put(
tg_bot_updater_instance.my_queue.put(
Update.de_json(data=json.loads(request.body), bot=TgBot.app.bot)
)
return Response({"result": "ok"})
@ -69,30 +69,30 @@ class TmcFieldViewset(viewsets.ModelViewSet):
@action(detail=False, methods=["get"], url_path=r"get_name/(?P<chat_id>[^/.]+)")
def get_name(self, request, chat_id):
TgBotUpdater.my_queue.put({"name": "admin_get_name", "queryset": chat_id})
tg_bot_updater_instance.my_queue.put({"name": "admin_get_name", "queryset": chat_id})
response = []
timer = 30
while timer > 0:
sleeping = 1
timer -= sleeping
time.sleep(sleeping)
if chat_id in TgBotUpdater.return_values:
response.append(TgBotUpdater.return_values[chat_id])
del TgBotUpdater.return_values[chat_id]
if chat_id in tg_bot_updater_instance.return_values:
response.append(tg_bot_updater_instance.return_values[chat_id])
del tg_bot_updater_instance.return_values[chat_id]
break
return Response(response)
@action(detail=False, methods=["get"], url_path=r"get_image/(?P<field_id>[^/.]+)")
def get_image(self, request, field_id):
TgBotUpdater.my_queue.put({"name": "admin_get_image", "queryset": field_id})
tg_bot_updater_instance.my_queue.put({"name": "admin_get_image", "queryset": field_id})
response = []
timer = 30
while timer > 0:
sleeping = 1
timer -= sleeping
time.sleep(sleeping)
if field_id in TgBotUpdater.return_values:
response.append(TgBotUpdater.return_values[field_id])
del TgBotUpdater.return_values[field_id]
if field_id in tg_bot_updater_instance.return_values:
response.append(tg_bot_updater_instance.return_values[field_id])
del tg_bot_updater_instance.return_values[field_id]
break
return Response(response)

View File

@ -1,28 +1,48 @@
services:
redis:
image: redis:7
restart: always
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli","ping"]
back:
build:
context: ./back
dockerfile: Dockerfile
container_name: toinv-back
image: ci.svs-tech.pro/toinv_back:latest
command: poetry run python manage.py runserver 0.0.0.0:8000
restart: always
expose:
- "8000"
healthcheck:
test: curl -f http://localhost:8000 || exit 1
interval: 5s
timeout: 3s
retries: 20
volumes:
- ./env/back.env:/app/.env
networks:
- toinv-network
healthcheck:
test: curl -f http://localhost:8000/ || exit 1
interval: 2s
timeout: 3s
retries: 20
celery:
build:
context: ./back
dockerfile: Dockerfile
image: ci.svs-tech.pro/toinv_back:latest
command: poetry run celery worker --loglevel=info
restart: always
volumes:
- ./env/back.env:/app/.env
depends_on:
back:
condition: service_healthy
front:
build:
context: ./front
dockerfile: Dockerfile
container_name: toinv-front
restart: always
volumes:
- ./env/front.env:/app/.env
@ -37,7 +57,6 @@ services:
nginx:
image: ci.svs-tech.pro/nginx:1.25
container_name: toinv-nginx
restart: always
ports:
- "${WEB_PORT:-80}:80"

View File

@ -1 +1,2 @@
WEB_PORT=80
REDIS_HOST=redis

View File

@ -1,2 +1,2 @@
NUXT_PUBLIC_API_BASE='http://localhost:8000/api'
NUXT_PUBLIC_API_BASE='http://localhost/api'
NUXT_PUBLIC_TGBOT='svstech_inventory_bot'

View File

@ -51,22 +51,24 @@ const patchItem = async () => {
</script>
<template>
<UForm :state="state" v-if="item">
<UFormGroup label="Название">
<div class="grid grid-cols-4 gap-2 items-end w-1/2">
<UFormGroup label="Название" class="col-span-3">
<UInput v-model="state.name" />
</UFormGroup>
<UButton @click="patchItem">Сохранить</UButton>
<UFormGroup label="Локация">
<UFormGroup label="Локация" class="col-span-3">
<USelectMenu v-model="state.location" :options="terdeep" :searchable="search" value-attribute="id"
option-attribute="name" />
</UFormGroup>
<UFormGroup label="ТМЦ" v-if="state.tmc">
<div v-for="el in state.tmc">
<UFormGroup label="ТМЦ" v-if="state.tmc" class="col-span-4">
<div v-for="el in state.tmc" class="ml-4">
<strong>{{ el.name }}</strong>
<ul>
<li v-for="field in el.fields" class="grid grid-cols-3 gap-4">
<span class="col-span-1 flex flex-col gap-2 items-start">
{{ field.name }}
<UInput v-model="field.text" :placeholder="`Введите ${field.comment || 'с изображения'}`" />
<UInput v-model="field.text"
:placeholder="`Введите ${field.comment || 'с изображения'}`" />
<UButton @click="patchField(field)">Сохранить</UButton>
</span>
<span class="col-span-2">
@ -76,5 +78,6 @@ const patchItem = async () => {
</ul>
</div>
</UFormGroup>
</div>
</UForm>
</template>