Merge branch 'bx-581-startproject' of https://git.svs-tech.pro/ksenia_mikhailova/to_inventory into bx-581-startproject

This commit is contained in:
commit 2a41108ced
30 changed files with 615 additions and 82 deletions

View File

@ -2,4 +2,5 @@ DB_NAME=
DB_USER=
DB_PASSWORD=
DB_HOST=
DB_PORT=
DB_PORT=
ODATA_AUTH=

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea/
.vscode/
.venv/
.venv/
.env

17
back/Dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM ci.svs-tech.pro/library/node:22-bookworm-slim
ENV WORKING_DIR=/app
WORKDIR ${WORKING_DIR}
RUN apt-get update
RUN apt-get install python3-pip -y
RUN apt-get install python3-poetry -y
RUN poetry -vvv --version
COPY pyproject.toml poetry.lock ./
RUN poetry install
COPY . ${WORKING_DIR}
CMD ["poetry", "run", "python", "manage.py", "runserver", "0.0.0.0:8000"]

View File

@ -27,10 +27,16 @@ SECRET_KEY = "django-insecure-ruo!wst&sb8(f9)j5u4rda-w!673lj_-c0a%gx_t@)ff*q*2ze
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
NGROK_TEMP = '357e-193-228-134-167.ngrok-free.app'
ALLOWED_HOSTS = [
"localhost",
NGROK_TEMP,
"192.168.103.159",
"back",
]
CORS_ALLOWED_ORIGINS = [
'http://localhost:3000',
"http://localhost:3000",
"http://192.168.103.159:3000",
]
# Application definition
@ -39,6 +45,7 @@ INSTALLED_APPS = [
"corsheaders",
"rest_framework",
"inventory",
"tgbot",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
@ -173,3 +180,9 @@ LOGGING = {
}
ODATA_AUTH = os.environ.get("ODATA_AUTH")
TGBOT = {
"token": os.environ.get("TG_TOKEN"),
"base_url": NGROK_TEMP,
"webhook": "webhook",
}

View File

@ -19,13 +19,16 @@ from django.urls import include, path
from rest_framework import routers
from inventory import views
from tgbot import views as tgbot_views
router = routers.DefaultRouter()
router.register(r'partner', views.PartnerViewSet)
router.register(r'element', views.ElementViewSet)
router.register(r'inventory', views.InventoryItemViewSet)
router.register(r'api/partner', views.PartnerViewSet)
router.register(r'api/element', views.ElementViewSet)
router.register(r'api/inventory', views.InventoryItemViewSet)
router.register(r'api/tgbot', tgbot_views.ItemViewSet)
urlpatterns = [
path('', include(router.urls)),
path('admin/', admin.site.urls),
]

View File

@ -34,8 +34,8 @@ class Element(models.Model):
id = models.AutoField(primary_key=True)
external_id = models.CharField()
element_id = models.CharField(max_length=100)
photo = models.ImageField(upload_to=".")
additional_text = models.TextField()
photo = models.ImageField(upload_to=".", null=True)
additional_text = models.TextField(null=True, blank=True, default="")
created_at = models.DateTimeField(auto_now_add=True)
inventory = models.ForeignKey(

View File

@ -1,4 +1,6 @@
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from .models import Element, InventoryItem, Partner
import logging
@ -37,7 +39,10 @@ class InventorySerializer(serializers.ModelSerializer):
class ElementSerializer(serializers.ModelSerializer):
inventory_name = serializers.CharField(source="inventory.name", read_only=True)
element_id = serializers.CharField(
max_length=100,
validators=[UniqueValidator(queryset=Element.objects.all())]
)
class Meta:
model = Element
fields = [

View File

@ -158,21 +158,28 @@ class ElementViewSet(viewsets.ModelViewSet):
)
inventory_serializer = InventorySerializer(inventory_object, many=False)
element_item = Element.objects.create(
inventory=inventory_object,
external_id=data["element"],
element_id=data["element_id"],
additional_text=data["element_additional_data"],
)
element_serializer = ElementSerializer(element_item, many=False)
return Response(
{
"partner": partner_serializer.data,
"inventory": inventory_serializer.data,
"element": element_serializer.data,
}
)
element_data = {
"inventory": inventory_object.id,
"external_id": data["element"],
"element_id": data["element_id"],
}
if "element_additional_data" in data:
element_data["additional_text"] = data["element_additional_data"]
element_serializer = ElementSerializer(data=element_data, many=False)
if element_serializer.is_valid():
element_data["inventory"] = inventory_object
Element.objects.create(**element_data)
return Response(
{
"partner": partner_serializer.data,
"inventory": inventory_serializer.data,
"element": element_serializer.data,
}
)
else:
return Response(
element_serializer.errors, status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@action(
detail=False,
@ -183,6 +190,7 @@ class ElementViewSet(viewsets.ModelViewSet):
try:
categories = get_depth_cat("87e91e07-7e10-11ee-ab5a-a47a2bd811cb")
categories.extend(get_depth_cat("20e1e6f6-a575-11ee-ab60-ec3c37e2e642"))
categories.extend(get_depth_cat('1f89290b-ffaf-11ed-ab4e-e3e667c628bd'))
return Response(categories)
except Exception as e:
logger.error(e)

View File

@ -1,5 +1,27 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "anyio"
version = "4.4.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.8"
files = [
{file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
]
[package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8"
sniffio = ">=1.1"
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (>=0.23)"]
[[package]]
name = "asgiref"
version = "3.8.1"
@ -201,6 +223,76 @@ files = [
[package.dependencies]
django = ">=3.0"
[[package]]
name = "exceptiongroup"
version = "1.2.1"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "httpcore"
version = "1.0.5"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
]
[package.dependencies]
certifi = "*"
h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<0.26.0)"]
[[package]]
name = "httpx"
version = "0.27.0"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
]
[package.dependencies]
anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
sniffio = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
[[package]]
name = "idna"
version = "3.7"
@ -353,25 +445,84 @@ files = [
test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
[[package]]
name = "psycopg2"
name = "psycopg2-binary"
version = "2.9.9"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
optional = false
python-versions = ">=3.7"
files = [
{file = "psycopg2-2.9.9-cp310-cp310-win32.whl", hash = "sha256:38a8dcc6856f569068b47de286b472b7c473ac7977243593a288ebce0dc89516"},
{file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"},
{file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"},
{file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"},
{file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"},
{file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"},
{file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"},
{file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"},
{file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"},
{file = "psycopg2-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:bac58c024c9922c23550af2a581998624d6e02350f4ae9c5f0bc642c633a2d5e"},
{file = "psycopg2-2.9.9-cp39-cp39-win32.whl", hash = "sha256:c92811b2d4c9b6ea0285942b2e7cac98a59e166d59c588fe5cfe1eda58e72d59"},
{file = "psycopg2-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:de80739447af31525feddeb8effd640782cf5998e1a4e9192ebdf829717e3913"},
{file = "psycopg2-2.9.9.tar.gz", hash = "sha256:d1454bde93fb1e224166811694d600e746430c006fbb031ea06ecc2ea41bf156"},
{file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"},
]
[[package]]
@ -388,15 +539,40 @@ files = [
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
name = "python-telegram-bot"
version = "21.2"
description = "We have made you a wrapper you can't refuse"
optional = false
python-versions = ">=3.8"
files = [
{file = "python-telegram-bot-21.2.tar.gz", hash = "sha256:2ebb462a98f502727d108c00bb50c513a68ddaf9545298c42f13996a9acf8354"},
{file = "python_telegram_bot-21.2-py3-none-any.whl", hash = "sha256:af0f45d61521126de98f5bdc8a75a9df8b93d0c35d18b018181ca7648a38b017"},
]
[package.dependencies]
httpx = ">=0.27,<1.0"
[package.extras]
all = ["APScheduler (>=3.10.4,<3.11.0)", "aiolimiter (>=1.1.0,<1.2.0)", "cachetools (>=5.3.3,<5.4.0)", "cryptography (>=39.0.1)", "httpx[http2]", "httpx[socks]", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"]
callback-data = ["cachetools (>=5.3.3,<5.4.0)"]
ext = ["APScheduler (>=3.10.4,<3.11.0)", "aiolimiter (>=1.1.0,<1.2.0)", "cachetools (>=5.3.3,<5.4.0)", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"]
http2 = ["httpx[http2]"]
job-queue = ["APScheduler (>=3.10.4,<3.11.0)", "pytz (>=2018.6)"]
passport = ["cryptography (>=39.0.1)"]
rate-limiter = ["aiolimiter (>=1.1.0,<1.2.0)"]
socks = ["httpx[socks]"]
webhooks = ["tornado (>=6.4,<7.0)"]
[[package]]
name = "requests"
version = "2.32.2"
version = "2.32.3"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
files = [
{file = "requests-2.32.2-py3-none-any.whl", hash = "sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c"},
{file = "requests-2.32.2.tar.gz", hash = "sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289"},
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
]
[package.dependencies]
@ -409,6 +585,17 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "sniffio"
version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
name = "sqlparse"
version = "0.5.0"
@ -494,4 +681,4 @@ zstd = ["zstandard (>=0.18.0)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "470159f92c63a9db1bc186d9da1a7fd1c579103ae33a346bb3484114c047d445"
content-hash = "58528b70db47be1cef118b365a2830312668b023e992f3aff0674f834f3c7acc"

View File

@ -2,21 +2,21 @@
name = "back"
version = "0.1.0"
description = ""
authors = ["k.mikhailova@svs-tech.pro"]
authors = ["Ksenia Miqhailova <k.mikhailova@svs-tech.pro>"]
readme = "readme.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.10"
Django = "^5.0.6"
djangorestframework = "^3.15.1"
psycopg2 = "^2.9.9"
psycopg2-binary = "^2.9.9"
markdown = "^3.6"
django-filter = "^24.2"
pillow = "^10.3.0"
python-dotenv = "^1.0.1"
requests = "^2.32.2"
django-cors-headers = "^4.3.1"
python-telegram-bot = "^21.2"
[tool.poetry.group.dev.dependencies]
taskipy = "^1.12.2"
@ -26,5 +26,5 @@ requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.taskipy.tasks]
migrate = "cd back && python manage.py makemigrations && python manage.py migrate"
server = "cd back && python manage.py runserver"
migrate = "python manage.py makemigrations && python manage.py migrate"
server = "python manage.py runserver 0.0.0.0:8000"

0
back/tgbot/__init__.py Normal file
View File

5
back/tgbot/admin.py Normal file
View File

@ -0,0 +1,5 @@
from django.contrib import admin
from .models import Item
# Register your models here.
admin.site.register(Item)

13
back/tgbot/apps.py Normal file
View File

@ -0,0 +1,13 @@
from django.apps import AppConfig
import django.conf
import requests
class TgbotConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "tgbot"
def ready(self):
from django.conf import settings
requests.get(f"https://api.telegram.org/bot{settings.TGBOT['token']}/setWebhook?url=https://{settings.TGBOT['base_url']}/api/tgbot/&drop_pending_updates=true")

View File

8
back/tgbot/models.py Normal file
View File

@ -0,0 +1,8 @@
from django.db import models
class Item(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
def __str__(self):
return f"Tg item {self.id}"

12
back/tgbot/serializers.py Normal file
View File

@ -0,0 +1,12 @@
from rest_framework import serializers
from .models import Item
import logging
logger = logging.getLogger("root")
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = '__all__'

3
back/tgbot/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

78
back/tgbot/views.py Normal file
View File

@ -0,0 +1,78 @@
import json
from django.conf import settings
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters
from telegram.constants import ParseMode, ChatType
from asgiref.sync import async_to_sync
from rest_framework import viewsets
from rest_framework.response import Response
from .models import Item
from .serializers import ItemSerializer
import logging
logger = logging.getLogger("root")
async def start(update, context):
logger.info(update)
await update.message.reply_html(text="123")
class ItemViewSet(viewsets.ViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
@async_to_sync
async def create(self, request):
req = json.loads(request.body)
logger.info(f"que len before put {ptb_application.update_queue.qsize()}")
update_item = Update.de_json(data=req, bot=ptb_application.bot)
await ptb_application.update_queue.put(update_item)
logger.info(f"que len after put {ptb_application.update_queue.qsize()}")
# await self.ptb_application.bot.send_message(
# chat_id=req["message"]["from"]["id"],
# text=f'Вы прислали текст `{req["message"]["text"]}`',
# parse_mode=ParseMode.MARKDOWN_V2,
# reply_to_message_id=req["message"]["message_id"],
# )
# if req["message"]["chat"]["type"] != ChatType.PRIVATE:
# return Response()
# if req["message"]["text"] == "/add":
# await self.ptb_application.bot.send_message(
# chat_id=req["message"]["from"]["id"],
# text=f'Вы хотите создать новую инвентаризацию?',
# parse_mode=ParseMode.MARKDOWN_V2,
# reply_to_message_id=req["message"]["message_id"],
# )
return Response({"test": "create"})
async def init_tg():
ptb_application = (
Application.builder().token(settings.TGBOT["token"]).concurrent_updates(True).updater(None).build()
)
ptb_application.add_handler(CommandHandler("start", start))
ptb_application.add_handler(
MessageHandler(filters.ChatType.PRIVATE, callback=start)
)
logger.info(
{
"app": ptb_application,
"bot": ptb_application.bot,
"handlers": ptb_application.handlers,
}
)
await ptb_application.initialize()
await ptb_application.start()
return ptb_application
ptb_application = async_to_sync(init_tg)()

12
compose-dev.yaml Normal file
View File

@ -0,0 +1,12 @@
services:
app:
entrypoint:
- sleep
- infinity
image: docker/dev-environments-javascript:stable-1
init: true
volumes:
- type: bind
source: /var/run/docker.sock
target: /var/run/docker.sock

2
dev.sh
View File

@ -1,3 +1,3 @@
#!/bin/bash
x-terminal-emulator -title "To Invetory FRONT" -e "cd front && npm run dev"&
x-terminal-emulator -title "To Invetory FRONT" -e "cd front && npm run dev -- --host"&
x-terminal-emulator -title "To Invetory BACK" -e "poetry run task server"

55
docker-compose.yml Normal file
View File

@ -0,0 +1,55 @@
services:
back:
build:
context: ./back
dockerfile: Dockerfile
container_name: toinv-back
restart: always
expose:
- "8000"
# healthcheck:
# test: curl -f http://localhost:8000/ || exit 1
# interval: 5s
# timeout: 3s
volumes:
- ./.env:/app/.env
networks:
- toinv-network
image: ci.svs-tech.pro/toinv_back:latest
front:
build:
context: ./front
dockerfile: Dockerfile
container_name: toinv-front
restart: always
expose:
- "3000"
# depends_on:
# back:
# condition: service_healthy
networks:
- toinv-network
image: ci.svs-tech.pro/toinv_front:latest
nginx:
image: ci.svs-tech.pro/nginx:1.25
container_name: toinv-nginx
restart: always
ports:
- "80:80"
# depends_on:
# back:
# condition: service_healthy
links:
- back:back
- front:front
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/conf.d:/etc/nginx/conf.d
networks:
- toinv-network
networks:
toinv-network:
driver: bridge

12
front/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM ci.svs-tech.pro/library/node:21
RUN mkdir -p /src
COPY package.json src/package.json
WORKDIR /src
RUN npm install --omit=dev
COPY . /src
RUN npm run build
CMD npm run preview -- --host

View File

@ -40,13 +40,15 @@
}
dl {
// @apply grid grid-cols-4;
@apply grid grid-cols-12;
dt {
// @apply col-span-1
@apply col-span-full
}
dd {
@apply mb-2
@apply col-span-full col-start-2;
&:last-of-type {
@apply mb-2
}
}
}

View File

@ -36,20 +36,7 @@ const external_data = reactive<ExternalDataType>({
categories: [],
element: [],
})
const external_elements = reactive<any>({})
const loadOneElement = async (id: string) => {
const data = await $fetch<ApiTypeExternal>(`${apiBase}/element/external/id/${id}`)
external_elements[id] = data.Description
}
const getExternalElementName = (id: string) => {
if (!external_elements[id]) {
loadOneElement(id)
return id
}
return external_elements[id]
}
const show_error = ref()
const elements = ref(props.elements)
const validate = (state: any): FormError[] => {
@ -60,24 +47,37 @@ const validate = (state: any): FormError[] => {
errors.push({ path: 'partner', message: txt })
}
if (!state.element_id) errors.push({ path: 'element_id', message: txt })
if (!state.element_additional_data) errors.push({ path: 'element_additional_data', message: txt })
// if (!state.element_additional_data) errors.push({ path: 'element_additional_data', message: txt })
return errors
}
async function onSubmit(event: FormSubmitEvent<any>) {
show_error.value = undefined
const prepader_data = event.data
if (!external_data || !external_data.partner) {
return false
}
prepader_data.partner_name = external_data.partner.find(el => el.Ref_Key == state.partner)?.Description
const data = await $fetch<ApiElementSave>(`${apiBase}/element/`, { method: 'POST', body: JSON.stringify(prepader_data) })
const data = await $fetch<ApiElementSave>(`${apiBase}/element/`, {
method: 'POST', body: JSON.stringify(prepader_data), onResponseError: (error) => {
if (error.response.status == 500) {
show_error.value = Object.entries(error.response._data).map(el => `${el[0]}: ${el[1]}`).join('\n')
}
}
})
const inv_id = route.params.inv_id || data.inventory?.id
if (!route.params.inv_id) {
navigateTo(`/organization/p_${data.partner.id}/i_${data.inventory.id}`)
} else {
const newElements = await $fetch<ApiTypeList>(`${apiBase}/element?inventory_id=${inv_id}`, { headers })
elements.value = newElements.results
external_data.element = []
external_data.categories = []
state.categories = []
state.element = undefined
state.element_id = undefined
state.element_additional_data = undefined
}
}
@ -141,7 +141,7 @@ onMounted(async () => {
</script>
<template>
<div class="grid grid-cols-10 gap-4">
<div class="col-span-3">
<div class="col-span-7">
<UForm :state="state" :validate="validate" class="flex flex-col gap-4" @submit="onSubmit">
<UFormGroup label="Выбрать организацию" name="partner">
<USelectMenu v-model="state.partner" :options="external_data.partner" value-attribute="Ref_Key"
@ -170,7 +170,7 @@ onMounted(async () => {
<UInput placeholder="ID" v-model="state.element_id" />
</UFormGroup>
<UFormGroup label="Дополнительные сведения" name="element_additional_data">
<UTextarea placeholder="ID" v-model="state.element_additional_data" />
<UTextarea placeholder="Дополнительные сведения" v-model="state.element_additional_data" />
</UFormGroup>
</div>
@ -179,16 +179,16 @@ onMounted(async () => {
</UButton>
</UForm>
</div>
<div class="col-span-7">
<div class="col-span-3 flex flex-col gap-4">
<h2>{{ external_data.partner?.find(el => el["Ref_Key"] == state.partner)?.Description }}</h2>
<UAlert v-if="show_error" title="Server error" :description="show_error" color="primary" />
<dl v-if="elements?.length">
<template v-for="item in elements">
<dt>Название</dt>
<dd>{{ getExternalElementName(item.external_id) }}</dd>
<dt>ID</dt>
<dt>
<Element :id="item.external_id" />
</dt>
<dd>{{ item.element_id }}</dd>
<dt>Дополнительные сведения</dt>
<dd>{{ item.additional_data }}</dd>
<dd>{{ item.additional_text }}</dd>
</template>
</dl>
</div>

View File

@ -0,0 +1,32 @@
<script setup lang="ts">
import { apiBase } from '~/helpers';
import type { ApiTypeExternal } from '~/helpers';
type ExternalNameType = { [s: string]: string; }
const props = defineProps(['id'])
const id = props.id
const external_elements = useState<ExternalNameType>('external_elements', () => { return {} })
const name = ref()
const loadOneElement = async (id: string) => {
external_elements.value[id] = 'loading'
const data = await $fetch<ApiTypeExternal>(`${apiBase}/element/external/id/${id}`)
external_elements.value[id] = data.Description
name.value = external_elements.value[id]
}
if (external_elements.value[id]) {
name.value = external_elements.value[id]
} else {
await loadOneElement(id)
}
watch(external_elements, () => {
if (name.value !== external_elements.value[id]) {
name.value = external_elements.value[id]
}
}, { deep: true })
</script>
<template>
{{ name }}
</template>

View File

@ -5,7 +5,7 @@ export default defineNuxtConfig({
modules: ["@nuxt/ui", "nuxt-svgo"],
runtimeConfig: {
public: {
apiBase: '',
apiBase: '/api',
},
},
})

View File

@ -12,7 +12,7 @@ const { data } = await useFetch<ApiTypeList>(`${apiBase}/partner/`, { headers })
<UButton icon="i-heroicons-plus" to="/organization/new">Новая инвентаризация</UButton>
</div>
<div class="mb-4">
<UTable :rows="data?.results":columns="makeColumns(['id', 'external_id', 'name', 'total_inventory'])">
<UTable :rows="data?.results" :columns="makeColumns(['id', 'name', 'external_id', 'total_inventory'])">
<template #name-data="{ row }">
<NuxtLink :to="`/organization/p_${row.id}`">{{ row.name }}</NuxtLink>
</template>

View File

@ -33,6 +33,10 @@ onMounted(async () => {
</UCard>
<div class="mb-4">
<UTable :rows="elements?.results"
:columns="makeColumns(['id', 'external_id', 'element_id', 'photo', 'additional_text'])" />
:columns="makeColumns(['id', 'external_id', 'element_id', 'photo', 'additional_text'])">
<template #external_id-data="{ row }">
<Element :id="row.external_id" />
</template>
</UTable>
</div>
</template>

View File

@ -50,7 +50,7 @@ onMounted(async () => {
</NuxtLink>
</template>
<template #created_at-data="{ row }">
{{ new Date(row.created_at).toLocaleString() }}
{{ new Date(row.created_at).toLocaleString('ru-RU') }}
</template>
</UTable>
</div>

62
nginx/nginx.conf Normal file
View File

@ -0,0 +1,62 @@
user root;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream django {
server back:8000;
keepalive 16;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://front:3000;
proxy_set_header Host $host:$server_port;
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 $scheme;
}
location /api/ {
proxy_pass http://back:8000/api/;
}
location /admin/ {
proxy_pass http://back:8000/admin/;
}
location /static/admin/ {
proxy_pass http://back:8000/static/admin/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}