celery
This commit is contained in:
parent
5f027e2066
commit
a415ab31f4
|
@ -0,0 +1 @@
|
||||||
|
back/.venv/
|
|
@ -4,19 +4,33 @@ FROM ci.svs-tech.pro/library/node:22-bookworm-slim
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
ENV PYTHONDONTWRITEBYTECODE 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
|
ENV WORKING_DIR=/app
|
||||||
WORKDIR ${WORKING_DIR}
|
WORKDIR ${WORKING_DIR}
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install curl -y
|
RUN apt-get install curl -y
|
||||||
RUN apt-get install python3-pip -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
|
RUN poetry -vvv --version
|
||||||
|
|
||||||
COPY pyproject.toml poetry.lock ./
|
COPY pyproject.toml ./
|
||||||
RUN poetry install
|
RUN poetry install
|
||||||
|
|
||||||
|
|
||||||
COPY . ${WORKING_DIR}
|
COPY . ${WORKING_DIR}
|
||||||
|
|
||||||
CMD ["poetry", "run", "python", "manage.py", "runserver", "0.0.0.0:8000"]
|
# CMD ["poetry", "run", "/app/.venv/bin/celery"]
|
||||||
|
|
|
@ -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()
|
|
@ -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
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||||
|
|
|
@ -1,5 +1,19 @@
|
||||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
# 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]]
|
[[package]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
version = "4.4.0"
|
version = "4.4.0"
|
||||||
|
@ -62,6 +76,85 @@ files = [
|
||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
|
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]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2024.7.4"
|
version = "2024.7.4"
|
||||||
|
@ -172,6 +265,69 @@ files = [
|
||||||
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
|
{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]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
@ -220,13 +376,13 @@ django = ">=3.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-filter"
|
name = "django-filter"
|
||||||
version = "24.2"
|
version = "24.3"
|
||||||
description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
|
description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "django-filter-24.2.tar.gz", hash = "sha256:48e5fc1da3ccd6ca0d5f9bb550973518ce977a4edde9d2a8a154a7f4f0b9f96e"},
|
{file = "django_filter-24.3-py3-none-any.whl", hash = "sha256:c4852822928ce17fb699bcfccd644b3574f1a2d80aeb2b4ff4f16b02dd49dc64"},
|
||||||
{file = "django_filter-24.2-py3-none-any.whl", hash = "sha256:df2ee9857e18d38bed203c8745f62a803fa0f31688c9fe6f8e868120b1848e48"},
|
{file = "django_filter-24.3.tar.gz", hash = "sha256:d8ccaf6732afd21ca0542f6733b11591030fa98669f8d15599b358e24a2cd9c3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -341,6 +497,38 @@ files = [
|
||||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
{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]]
|
[[package]]
|
||||||
name = "markdown"
|
name = "markdown"
|
||||||
version = "3.6"
|
version = "3.6"
|
||||||
|
@ -475,6 +663,20 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
|
||||||
typing = ["typing-extensions"]
|
typing = ["typing-extensions"]
|
||||||
xmp = ["defusedxml"]
|
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]]
|
[[package]]
|
||||||
name = "psutil"
|
name = "psutil"
|
||||||
version = "5.9.8"
|
version = "5.9.8"
|
||||||
|
@ -584,6 +786,20 @@ files = [
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"},
|
{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]]
|
[[package]]
|
||||||
name = "python-dotenv"
|
name = "python-dotenv"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -636,6 +852,24 @@ files = [
|
||||||
{file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
|
{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]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.32.3"
|
version = "2.32.3"
|
||||||
|
@ -767,7 +1001,29 @@ h2 = ["h2 (>=4,<5)"]
|
||||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||||
zstd = ["zstandard (>=0.18.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]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "e032592fbe04dbdaffbb40512f6dfa13bcd6f0673c9923e79ba0d852205e2c19"
|
content-hash = "6362300a16177d23be8b5603dfd1ba3cf5ce477bb51962661c8bcf818433cf26"
|
||||||
|
|
|
@ -19,6 +19,7 @@ django-cors-headers = "^4.3.1"
|
||||||
python-telegram-bot = { extras = ["job-queue"], version = "^21.3" }
|
python-telegram-bot = { extras = ["job-queue"], version = "^21.3" }
|
||||||
more-itertools = "^10.3.0"
|
more-itertools = "^10.3.0"
|
||||||
djangoviz = "^0.1.1"
|
djangoviz = "^0.1.1"
|
||||||
|
celery = { extras = ["redis"], version = "^5.4.0" }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
taskipy = "^1.12.2"
|
taskipy = "^1.12.2"
|
||||||
|
|
|
@ -1,90 +1,20 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
import asyncio
|
|
||||||
import threading
|
import threading
|
||||||
import queue
|
|
||||||
import os
|
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):
|
class TgbotConfig(AppConfig):
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
name = "tgbot"
|
name = "tgbot"
|
||||||
|
|
||||||
def ready(self):
|
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(
|
threading.Thread(
|
||||||
target=(TgBotUpdater().run_func),
|
target=(tg_bot_updater_instance.run_func),
|
||||||
name="tg_updater_thread",
|
name="tg_updater_thread",
|
||||||
daemon=True,
|
daemon=True,
|
||||||
).start()
|
).start()
|
||||||
|
tg_bot_updater_instance.is_run = True
|
||||||
|
|
||||||
return super().ready()
|
return super().ready()
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from .apps import TgBotUpdater
|
|
||||||
from .models import TgItem, TmcElement, TmcField
|
from .models import TgItem, TmcElement, TmcField
|
||||||
from tmc.models import CustomTable, BaseCustomField, Territory, TerritoryItem
|
from tmc.models import CustomTable, BaseCustomField, Territory, TerritoryItem
|
||||||
|
|
||||||
|
@ -65,20 +64,21 @@ class TgBot:
|
||||||
await TgBot.app.initialize()
|
await TgBot.app.initialize()
|
||||||
|
|
||||||
async def admin_action(self, name, queryset):
|
async def admin_action(self, name, queryset):
|
||||||
|
from .updater import tg_bot_updater_instance
|
||||||
if name == "admin_get_image":
|
if name == "admin_get_image":
|
||||||
item = queryset
|
item = queryset
|
||||||
try:
|
try:
|
||||||
result = await TgBot.app.bot.get_file(item)
|
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:
|
except Exception as e:
|
||||||
TgBotUpdater.return_values[item] = None
|
tg_bot_updater_instance.return_values[item] = None
|
||||||
if name == "admin_get_name":
|
if name == "admin_get_name":
|
||||||
item = queryset
|
item = queryset
|
||||||
try:
|
try:
|
||||||
result = await TgBot.app.bot.get_chat(item)
|
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:
|
except Exception as e:
|
||||||
TgBotUpdater.return_values[item] = None
|
tg_bot_updater_instance.return_values[item] = None
|
||||||
|
|
||||||
async def set_handlers(self):
|
async def set_handlers(self):
|
||||||
TgBot.app.add_handler(
|
TgBot.app.add_handler(
|
||||||
|
|
|
@ -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()
|
|
@ -13,7 +13,7 @@ from rest_framework.decorators import action
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
|
||||||
from .tgbot import TgBot
|
from .tgbot import TgBot
|
||||||
from .apps import TgBotUpdater
|
from .updater import tg_bot_updater_instance
|
||||||
from .models import TgItem, TmcField
|
from .models import TgItem, TmcField
|
||||||
from .serializers import TgItemSerializer, TmcFieldSerializer
|
from .serializers import TgItemSerializer, TmcFieldSerializer
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class TgItemViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
@action(detail=False, methods=["post"], url_path=settings.TGBOT["WEBHOOK_URL"])
|
@action(detail=False, methods=["post"], url_path=settings.TGBOT["WEBHOOK_URL"])
|
||||||
def send_tg_data(self, request):
|
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)
|
Update.de_json(data=json.loads(request.body), bot=TgBot.app.bot)
|
||||||
)
|
)
|
||||||
return Response({"result": "ok"})
|
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>[^/.]+)")
|
@action(detail=False, methods=["get"], url_path=r"get_name/(?P<chat_id>[^/.]+)")
|
||||||
def get_name(self, request, 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 = []
|
response = []
|
||||||
timer = 30
|
timer = 30
|
||||||
while timer > 0:
|
while timer > 0:
|
||||||
sleeping = 1
|
sleeping = 1
|
||||||
timer -= sleeping
|
timer -= sleeping
|
||||||
time.sleep(sleeping)
|
time.sleep(sleeping)
|
||||||
if chat_id in TgBotUpdater.return_values:
|
if chat_id in tg_bot_updater_instance.return_values:
|
||||||
response.append(TgBotUpdater.return_values[chat_id])
|
response.append(tg_bot_updater_instance.return_values[chat_id])
|
||||||
del TgBotUpdater.return_values[chat_id]
|
del tg_bot_updater_instance.return_values[chat_id]
|
||||||
break
|
break
|
||||||
return Response(response)
|
return Response(response)
|
||||||
|
|
||||||
@action(detail=False, methods=["get"], url_path=r"get_image/(?P<field_id>[^/.]+)")
|
@action(detail=False, methods=["get"], url_path=r"get_image/(?P<field_id>[^/.]+)")
|
||||||
def get_image(self, request, 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 = []
|
response = []
|
||||||
timer = 30
|
timer = 30
|
||||||
while timer > 0:
|
while timer > 0:
|
||||||
sleeping = 1
|
sleeping = 1
|
||||||
timer -= sleeping
|
timer -= sleeping
|
||||||
time.sleep(sleeping)
|
time.sleep(sleeping)
|
||||||
if field_id in TgBotUpdater.return_values:
|
if field_id in tg_bot_updater_instance.return_values:
|
||||||
response.append(TgBotUpdater.return_values[field_id])
|
response.append(tg_bot_updater_instance.return_values[field_id])
|
||||||
del TgBotUpdater.return_values[field_id]
|
del tg_bot_updater_instance.return_values[field_id]
|
||||||
break
|
break
|
||||||
return Response(response)
|
return Response(response)
|
||||||
|
|
|
@ -1,28 +1,48 @@
|
||||||
services:
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:7
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli","ping"]
|
||||||
|
|
||||||
back:
|
back:
|
||||||
build:
|
build:
|
||||||
context: ./back
|
context: ./back
|
||||||
dockerfile: Dockerfile
|
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
|
restart: always
|
||||||
expose:
|
expose:
|
||||||
- "8000"
|
- "8000"
|
||||||
healthcheck:
|
|
||||||
test: curl -f http://localhost:8000 || exit 1
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 20
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./env/back.env:/app/.env
|
- ./env/back.env:/app/.env
|
||||||
networks:
|
networks:
|
||||||
- toinv-network
|
- 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
|
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:
|
front:
|
||||||
build:
|
build:
|
||||||
context: ./front
|
context: ./front
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: toinv-front
|
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./env/front.env:/app/.env
|
- ./env/front.env:/app/.env
|
||||||
|
@ -37,7 +57,6 @@ services:
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: ci.svs-tech.pro/nginx:1.25
|
image: ci.svs-tech.pro/nginx:1.25
|
||||||
container_name: toinv-nginx
|
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "${WEB_PORT:-80}:80"
|
- "${WEB_PORT:-80}:80"
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
WEB_PORT=80
|
WEB_PORT=80
|
||||||
|
REDIS_HOST=redis
|
|
@ -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'
|
NUXT_PUBLIC_TGBOT='svstech_inventory_bot'
|
|
@ -51,22 +51,24 @@ const patchItem = async () => {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<UForm :state="state" v-if="item">
|
<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" />
|
<UInput v-model="state.name" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
<UButton @click="patchItem">Сохранить</UButton>
|
<UButton @click="patchItem">Сохранить</UButton>
|
||||||
<UFormGroup label="Локация">
|
<UFormGroup label="Локация" class="col-span-3">
|
||||||
<USelectMenu v-model="state.location" :options="terdeep" :searchable="search" value-attribute="id"
|
<USelectMenu v-model="state.location" :options="terdeep" :searchable="search" value-attribute="id"
|
||||||
option-attribute="name" />
|
option-attribute="name" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
<UFormGroup label="ТМЦ" v-if="state.tmc">
|
<UFormGroup label="ТМЦ" v-if="state.tmc" class="col-span-4">
|
||||||
<div v-for="el in state.tmc">
|
<div v-for="el in state.tmc" class="ml-4">
|
||||||
<strong>{{ el.name }}</strong>
|
<strong>{{ el.name }}</strong>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="field in el.fields" class="grid grid-cols-3 gap-4">
|
<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">
|
<span class="col-span-1 flex flex-col gap-2 items-start">
|
||||||
{{ field.name }}
|
{{ field.name }}
|
||||||
<UInput v-model="field.text" :placeholder="`Введите ${field.comment || 'с изображения'}`" />
|
<UInput v-model="field.text"
|
||||||
|
:placeholder="`Введите ${field.comment || 'с изображения'}`" />
|
||||||
<UButton @click="patchField(field)">Сохранить</UButton>
|
<UButton @click="patchField(field)">Сохранить</UButton>
|
||||||
</span>
|
</span>
|
||||||
<span class="col-span-2">
|
<span class="col-span-2">
|
||||||
|
@ -76,5 +78,6 @@ const patchItem = async () => {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
</div>
|
||||||
</UForm>
|
</UForm>
|
||||||
</template>
|
</template>
|
Loading…
Reference in New Issue