From 0cbc52b7da1a8c6052d5b6f9e6d4f66369499f88 Mon Sep 17 00:00:00 2001 From: "suhotskiy.ne" Date: Thu, 6 Mar 2025 16:31:45 +1000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20barcode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 18 ++ __init__.py | 1 + barcode/__init__.py | 1 + barcode/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 242 bytes .../__pycache__/celery_app.cpython-312.pyc | Bin 0 -> 628 bytes barcode/__pycache__/settings.cpython-312.pyc | Bin 0 -> 4256 bytes barcode/__pycache__/urls.cpython-312.pyc | Bin 0 -> 1407 bytes barcode/__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 689 bytes barcode/asgi.py | 20 ++ barcode/celery_app.py | 11 + barcode/consumers.py | 20 ++ barcode/data_fetcher.py | 63 +++++ barcode/routing.py | 6 + barcode/settings.py | 203 +++++++++++++++++ barcode/urls.py | 33 +++ barcode/wsgi.py | 16 ++ batches/__init__.py | 0 batches/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 201 bytes batches/__pycache__/admin.cpython-312.pyc | Bin 0 -> 245 bytes batches/__pycache__/apps.cpython-312.pyc | Bin 0 -> 509 bytes batches/__pycache__/models.cpython-312.pyc | Bin 0 -> 242 bytes batches/__pycache__/urls.cpython-312.pyc | Bin 0 -> 773 bytes batches/__pycache__/views.cpython-312.pyc | Bin 0 -> 9206 bytes batches/admin.py | 3 + batches/apps.py | 6 + batches/migrations/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 212 bytes batches/models.py | 3 + batches/tests.py | 3 + batches/urls.py | 14 ++ batches/views.py | 193 ++++++++++++++++ db.sqlite3 | Bin 0 -> 139264 bytes docker-compose.yml | 32 +++ forms/__init__.py | 0 forms/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 199 bytes forms/__pycache__/admin.cpython-312.pyc | Bin 0 -> 243 bytes forms/__pycache__/apps.cpython-312.pyc | Bin 0 -> 503 bytes forms/__pycache__/models.cpython-312.pyc | Bin 0 -> 1450 bytes forms/__pycache__/serializers.cpython-312.pyc | Bin 0 -> 751 bytes forms/__pycache__/urls.cpython-312.pyc | Bin 0 -> 726 bytes forms/__pycache__/views.cpython-312.pyc | Bin 0 -> 5016 bytes forms/admin.py | 3 + forms/apps.py | 6 + forms/forms.py | 16 ++ forms/migrations/0001_initial.py | 30 +++ forms/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 1606 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 210 bytes forms/models.py | 17 ++ forms/serializers.py | 9 + forms/tests.py | 3 + forms/urls.py | 8 + forms/views.py | 90 ++++++++ inventory/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 203 bytes inventory/__pycache__/admin.cpython-312.pyc | Bin 0 -> 247 bytes inventory/__pycache__/apps.cpython-312.pyc | Bin 0 -> 515 bytes inventory/__pycache__/models.cpython-312.pyc | Bin 0 -> 4255 bytes .../__pycache__/serializers.cpython-312.pyc | Bin 0 -> 2762 bytes inventory/__pycache__/urls.cpython-312.pyc | Bin 0 -> 947 bytes inventory/__pycache__/views.cpython-312.pyc | Bin 0 -> 6836 bytes inventory/admin.py | 3 + inventory/apps.py | 6 + inventory/migrations/0001_initial.py | 47 ++++ inventory/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 2666 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 214 bytes inventory/models.py | 59 +++++ inventory/serializers.py | 34 +++ inventory/tests.py | 3 + inventory/urls.py | 13 ++ inventory/views.py | 122 ++++++++++ manage.py | 22 ++ requirements.txt | Bin 0 -> 6426 bytes scan/__init__.py | 0 scan/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 198 bytes scan/__pycache__/admin.cpython-312.pyc | Bin 0 -> 242 bytes scan/__pycache__/apps.cpython-312.pyc | Bin 0 -> 500 bytes scan/__pycache__/models.cpython-312.pyc | Bin 0 -> 1308 bytes scan/__pycache__/serializers.cpython-312.pyc | Bin 0 -> 747 bytes scan/__pycache__/urls.cpython-312.pyc | Bin 0 -> 450 bytes scan/__pycache__/views.cpython-312.pyc | Bin 0 -> 10135 bytes scan/admin.py | 3 + scan/api_client.py | 53 +++++ scan/apps.py | 6 + scan/celery_app.py | 11 + scan/constants.py | 19 ++ scan/migrations/0001_initial.py | 32 +++ scan/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 1496 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 209 bytes scan/models.py | 15 ++ scan/serializers.py | 7 + scan/tasks.py | 13 ++ scan/tests.py | 3 + scan/urls.py | 10 + scan/utils.py | 83 +++++++ scan/views.py | 215 ++++++++++++++++++ scan/viewss1/__init__.py | 3 + scan/viewss1/management_views.py | 88 +++++++ scan/viewss1/scan_views.py | 77 +++++++ users/__init__.py | 0 users/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 199 bytes users/__pycache__/admin.cpython-312.pyc | Bin 0 -> 243 bytes users/__pycache__/apps.cpython-312.pyc | Bin 0 -> 503 bytes users/__pycache__/models.cpython-312.pyc | Bin 0 -> 240 bytes users/__pycache__/serializers.cpython-312.pyc | Bin 0 -> 1438 bytes users/__pycache__/urls.cpython-312.pyc | Bin 0 -> 424 bytes users/__pycache__/views.cpython-312.pyc | Bin 0 -> 1132 bytes users/admin.py | 3 + users/apps.py | 6 + users/migrations/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 210 bytes users/models.py | 3 + users/serializers.py | 23 ++ users/tests.py | 3 + users/urls.py | 6 + users/views.py | 11 + 118 files changed, 1830 insertions(+) create mode 100644 Dockerfile create mode 100644 __init__.py create mode 100644 barcode/__init__.py create mode 100644 barcode/__pycache__/__init__.cpython-312.pyc create mode 100644 barcode/__pycache__/celery_app.cpython-312.pyc create mode 100644 barcode/__pycache__/settings.cpython-312.pyc create mode 100644 barcode/__pycache__/urls.cpython-312.pyc create mode 100644 barcode/__pycache__/wsgi.cpython-312.pyc create mode 100644 barcode/asgi.py create mode 100644 barcode/celery_app.py create mode 100644 barcode/consumers.py create mode 100644 barcode/data_fetcher.py create mode 100644 barcode/routing.py create mode 100644 barcode/settings.py create mode 100644 barcode/urls.py create mode 100644 barcode/wsgi.py create mode 100644 batches/__init__.py create mode 100644 batches/__pycache__/__init__.cpython-312.pyc create mode 100644 batches/__pycache__/admin.cpython-312.pyc create mode 100644 batches/__pycache__/apps.cpython-312.pyc create mode 100644 batches/__pycache__/models.cpython-312.pyc create mode 100644 batches/__pycache__/urls.cpython-312.pyc create mode 100644 batches/__pycache__/views.cpython-312.pyc create mode 100644 batches/admin.py create mode 100644 batches/apps.py create mode 100644 batches/migrations/__init__.py create mode 100644 batches/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 batches/models.py create mode 100644 batches/tests.py create mode 100644 batches/urls.py create mode 100644 batches/views.py create mode 100644 db.sqlite3 create mode 100644 docker-compose.yml create mode 100644 forms/__init__.py create mode 100644 forms/__pycache__/__init__.cpython-312.pyc create mode 100644 forms/__pycache__/admin.cpython-312.pyc create mode 100644 forms/__pycache__/apps.cpython-312.pyc create mode 100644 forms/__pycache__/models.cpython-312.pyc create mode 100644 forms/__pycache__/serializers.cpython-312.pyc create mode 100644 forms/__pycache__/urls.cpython-312.pyc create mode 100644 forms/__pycache__/views.cpython-312.pyc create mode 100644 forms/admin.py create mode 100644 forms/apps.py create mode 100644 forms/forms.py create mode 100644 forms/migrations/0001_initial.py create mode 100644 forms/migrations/__init__.py create mode 100644 forms/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 forms/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 forms/models.py create mode 100644 forms/serializers.py create mode 100644 forms/tests.py create mode 100644 forms/urls.py create mode 100644 forms/views.py create mode 100644 inventory/__init__.py create mode 100644 inventory/__pycache__/__init__.cpython-312.pyc create mode 100644 inventory/__pycache__/admin.cpython-312.pyc create mode 100644 inventory/__pycache__/apps.cpython-312.pyc create mode 100644 inventory/__pycache__/models.cpython-312.pyc create mode 100644 inventory/__pycache__/serializers.cpython-312.pyc create mode 100644 inventory/__pycache__/urls.cpython-312.pyc create mode 100644 inventory/__pycache__/views.cpython-312.pyc create mode 100644 inventory/admin.py create mode 100644 inventory/apps.py create mode 100644 inventory/migrations/0001_initial.py create mode 100644 inventory/migrations/__init__.py create mode 100644 inventory/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 inventory/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 inventory/models.py create mode 100644 inventory/serializers.py create mode 100644 inventory/tests.py create mode 100644 inventory/urls.py create mode 100644 inventory/views.py create mode 100644 manage.py create mode 100644 requirements.txt create mode 100644 scan/__init__.py create mode 100644 scan/__pycache__/__init__.cpython-312.pyc create mode 100644 scan/__pycache__/admin.cpython-312.pyc create mode 100644 scan/__pycache__/apps.cpython-312.pyc create mode 100644 scan/__pycache__/models.cpython-312.pyc create mode 100644 scan/__pycache__/serializers.cpython-312.pyc create mode 100644 scan/__pycache__/urls.cpython-312.pyc create mode 100644 scan/__pycache__/views.cpython-312.pyc create mode 100644 scan/admin.py create mode 100644 scan/api_client.py create mode 100644 scan/apps.py create mode 100644 scan/celery_app.py create mode 100644 scan/constants.py create mode 100644 scan/migrations/0001_initial.py create mode 100644 scan/migrations/__init__.py create mode 100644 scan/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 scan/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 scan/models.py create mode 100644 scan/serializers.py create mode 100644 scan/tasks.py create mode 100644 scan/tests.py create mode 100644 scan/urls.py create mode 100644 scan/utils.py create mode 100644 scan/views.py create mode 100644 scan/viewss1/__init__.py create mode 100644 scan/viewss1/management_views.py create mode 100644 scan/viewss1/scan_views.py create mode 100644 users/__init__.py create mode 100644 users/__pycache__/__init__.cpython-312.pyc create mode 100644 users/__pycache__/admin.cpython-312.pyc create mode 100644 users/__pycache__/apps.cpython-312.pyc create mode 100644 users/__pycache__/models.cpython-312.pyc create mode 100644 users/__pycache__/serializers.cpython-312.pyc create mode 100644 users/__pycache__/urls.cpython-312.pyc create mode 100644 users/__pycache__/views.cpython-312.pyc create mode 100644 users/admin.py create mode 100644 users/apps.py create mode 100644 users/migrations/__init__.py create mode 100644 users/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 users/models.py create mode 100644 users/serializers.py create mode 100644 users/tests.py create mode 100644 users/urls.py create mode 100644 users/views.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5554078 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +# Python +FROM python:3.10 + +# +WORKDIR /app + +# +COPY . /app/ + +# +RUN pip install --no-cache-dir --upgrade pip \ + && pip install -r requirements.txt + +# +RUN python manage.py migrate + +# Django +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..4b09802 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from .celery_app import app as celery_app diff --git a/barcode/__init__.py b/barcode/__init__.py new file mode 100644 index 0000000..4b09802 --- /dev/null +++ b/barcode/__init__.py @@ -0,0 +1 @@ +from .celery_app import app as celery_app diff --git a/barcode/__pycache__/__init__.cpython-312.pyc b/barcode/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9eb26143873923756be68d91c12cd687b99e0430 GIT binary patch literal 242 zcmX|*u?oU46h)I3M1+Fh;1;BQfQXB`ba9bDh)FQ5Z4#1p$d~vH4t|N7V+R)}H^I%x z1k}6Sd)|Y0-zCdNAm}k*Gb*FL+NKq;KDOWZBtlX3D!Dnd@Hs3BIXrUd!0K* xRn9aQF6YA3+T$Iyk^fFesmhj+nep%8qS3so#nKK#aeU9k@Ju0uH-PLQzz3}2MD+jw literal 0 HcmV?d00001 diff --git a/barcode/__pycache__/celery_app.cpython-312.pyc b/barcode/__pycache__/celery_app.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73a2be7ef0e30e4e3f5ddb41b70d771398739984 GIT binary patch literal 628 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 l^)GUJORX4XlJG?;o3{{=fdpHu(< literal 0 HcmV?d00001 diff --git a/barcode/__pycache__/wsgi.cpython-312.pyc b/barcode/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a18c3eb53737dcd26dd8ace9af341743acdeaf86 GIT binary patch literal 689 zcmYjP!EVz)5Z$#yQ#mB65bb$|xI`j$d*D)4NYIuj2+~Svg%nwGHr|QjVDDPqO^SQ# ziBIT-A3*&SE=3?!@(F}Ea0BJm6YDymox{$|j^4aCJNvm_w~#i!{xLYm2>mRQrBxQe ze5iwS#84kG%!~;4vG!(U#Ae^b2=HWe3Ay!?+HJgyScToYFv<)g1A%$5DD_t|qq%Z= zWxegSJH!`!6b57@QZl5eFBp&{6%+84ZCM>f;C&(_NTS9-N&t-_I*fo&Nhpa0n?}%z zU2>A&WQ{{$NF82*=^$dVF1c2?z}rP z@{PS$&%5mdueX16)akZ+-kXEH`&z$U zv`tM-lvjbz!c_3w(m)0yI*n9byTkYi%?m}>Yf?`zG5%{* tab^AhVe1##`i_hb)|qvM*1w?j%MJT-^Tju``=jybYvb`*ExoTn{{ZO~&p-eG literal 0 HcmV?d00001 diff --git a/barcode/asgi.py b/barcode/asgi.py new file mode 100644 index 0000000..df29ea8 --- /dev/null +++ b/barcode/asgi.py @@ -0,0 +1,20 @@ +""" +ASGI config for barcode project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +""" + +import os +from django.core.asgi import get_asgi_application +from channels.routing import ProtocolTypeRouter, URLRouter +from barcode.routing import websocket_urlpatterns + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "barcode.settings") + +application = ProtocolTypeRouter({ + "http": get_asgi_application(), + "websocket": URLRouter(websocket_urlpatterns), +}) diff --git a/barcode/celery_app.py b/barcode/celery_app.py new file mode 100644 index 0000000..c2a8f87 --- /dev/null +++ b/barcode/celery_app.py @@ -0,0 +1,11 @@ +import os +from celery import Celery + +# Django +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "barcode.settings") + +app = Celery("barcode") + +# Celery Django +app.config_from_object("django.conf:settings", namespace="CELERY") +app.autodiscover_tasks() diff --git a/barcode/consumers.py b/barcode/consumers.py new file mode 100644 index 0000000..5d756be --- /dev/null +++ b/barcode/consumers.py @@ -0,0 +1,20 @@ +import json +import asyncio +from channels.generic.websocket import AsyncWebsocketConsumer +from .data_fetcher import fetch_lines_data # Вынесем логику получения данных в отдельный модуль + +class LineDataConsumer(AsyncWebsocketConsumer): + async def connect(self): + await self.accept() + self.running = True + await self.send(text_data=json.dumps({"message": "WebSocket connected"})) + asyncio.create_task(self.send_data_periodically()) + + async def disconnect(self, close_code): + self.running = False + + async def send_data_periodically(self): + while self.running: + data = await fetch_lines_data() + await self.send(text_data=json.dumps(data)) + await asyncio.sleep(5) # Ждем 5 секунд перед следующим запросом diff --git a/barcode/data_fetcher.py b/barcode/data_fetcher.py new file mode 100644 index 0000000..38f8070 --- /dev/null +++ b/barcode/data_fetcher.py @@ -0,0 +1,63 @@ +import requests +import logging +import time + +LOGIN_URL_ASPU = "http://192.168.254.10:3428/api/login" + +API_URLS = { + "Sipa": "http://192.168.254.10:3428/api/Management/", + "Devin": "http://192.168.254.20:3428/api/Management/", + "JR": "http://192.168.254.30:3428/api/Management/", + "5L": "http://192.168.254.40:3428/api/Management/", + "19L": "http://192.168.254.50:3428/api/Management/", +} + +USERNAME = "superuser" +PASSWORD = "Superuser1105" + +token_cache = {"token": None, "timestamp": 0} +TOKEN_LIFETIME = 1440 * 60 # 24 часа в секундах + +def get_new_token(): + """Получение нового токена с кешированием.""" + global token_cache + current_time = time.time() + + if token_cache["token"] and (current_time - token_cache["timestamp"] < TOKEN_LIFETIME): + return token_cache["token"] + + payload = {"UserName": USERNAME, "Password": PASSWORD} + try: + response = requests.post(LOGIN_URL_ASPU, json=payload, timeout=5) + response_data = response.json() + + if response.status_code == 200 and response_data.get("IsSuccess"): + token_cache["token"] = response_data["Value"]["Token"] + token_cache["timestamp"] = current_time + return token_cache["token"] + + logging.error(f"Ошибка получения токена: {response_data}") + return None + except requests.RequestException as e: + logging.error(f"Ошибка сети при получении токена: {str(e)}") + return None + +async def fetch_lines_data(): + """Асинхронное получение данных с линий.""" + token = get_new_token() + if not token: + return {"error": "Не удалось получить токен"} + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + results = {} + + for key, url in API_URLS.items(): + try: + response = requests.get(url, headers=headers, timeout=5) + response.raise_for_status() + results[key] = response.json() + except requests.RequestException as e: + logging.error(f"Ошибка запроса к {key}: {str(e)}") + results[key] = {"error": str(e)} + + return results diff --git a/barcode/routing.py b/barcode/routing.py new file mode 100644 index 0000000..cc78f28 --- /dev/null +++ b/barcode/routing.py @@ -0,0 +1,6 @@ +from django.urls import re_path +from .consumers import LineDataConsumer + +websocket_urlpatterns = [ + re_path(r"ws/lines/$", LineDataConsumer.as_asgi()), +] diff --git a/barcode/settings.py b/barcode/settings.py new file mode 100644 index 0000000..d9545c8 --- /dev/null +++ b/barcode/settings.py @@ -0,0 +1,203 @@ +""" +Django settings for barcode project. + +Generated by 'django-admin startproject' using Django 5.1.5. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +from pathlib import Path +# -*- coding: utf-8 -*- +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-i#8aotin&c00-&#v@x!moruf-uimxr#c!8pi9ehltop98-@bzr' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + +CELERY_BROKER_URL = 'redis://localhost:6379/0' +CELERY_ACCEPT_CONTENT = ['json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' + + +CELERY_WORKER_POOL = 'solo' +CELERY_WORKER_CONCURRENCY = 1 +CELERY_TASK_ALWAYS_EAGER = True +# Application definition + +INSTALLED_APPS = [ + 'inventory', + 'batches', + 'scan', + 'forms', + 'users', + "channels", + 'rest_framework', + 'rest_framework_simplejwt', + 'django.contrib.admin', + 'corsheaders', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', + + 'django.middleware.common.CommonMiddleware', +] + +ROOT_URLCONF = 'barcode.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'barcode.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'mydatabase', + 'USER': 'myuser', + 'PASSWORD': 'mypassword', + 'HOST': '31.130.144.182', + 'PORT': '5432', + 'OPTIONS': { + 'client_encoding': 'UTF8', + }, + } +} + + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +import sys +import os + +# ������������� UTF-8 �� ��������� +os.environ["PYTHONIOENCODING"] = "utf-8" +sys.stdout.reconfigure(encoding="utf-8") +sys.stderr.reconfigure(encoding="utf-8") + +REST_FRAMEWORK = { + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', # ��������� ������ JSON + ), +} + +CORS_ALLOW_HEADERS = ['Content-Type'] # ��������� ��������� + +CORS_ALLOW_ALL_ORIGINS = True # ��������� ������� �� ���� ���������� +# ��� ��������� ������ localhost:3000 +CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000", + "http://127.0.0.1:3000" +] + + +from datetime import timedelta + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ), +} + +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(days=1), # Токен живет 1 день + 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), # Refresh-токен живет 7 дней + 'AUTH_HEADER_TYPES': ('Bearer',), # Токен передается через заголовок Authorization +} + +ASGI_APPLICATION = "your_project.asgi.application" +CHANNEL_LAYERS = { + "default": { + "BACKEND": "channels.layers.InMemoryChannelLayer", + "CONFIG": { + "capacity": 1000, # Увеличь лимит соединений + }, + }, +} + diff --git a/barcode/urls.py b/barcode/urls.py new file mode 100644 index 0000000..abeb829 --- /dev/null +++ b/barcode/urls.py @@ -0,0 +1,33 @@ +""" +URL configuration for barcode project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +# from django.contrib import admin +# from django.urls import path +# +# urlpatterns = [ +# path('admin/', admin.site.urls), +# ] +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path("admin/", admin.site.urls), + path("", include("scan.urls")), + path("bad/", include("batches.urls")), + path("inventory/", include("inventory.urls")), + path("forms/", include("forms.urls")), + path("api/users/", include("users.urls")), +] \ No newline at end of file diff --git a/barcode/wsgi.py b/barcode/wsgi.py new file mode 100644 index 0000000..c250e38 --- /dev/null +++ b/barcode/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for barcode project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'barcode.settings') + +application = get_wsgi_application() diff --git a/batches/__init__.py b/batches/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/batches/__pycache__/__init__.cpython-312.pyc b/batches/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb936a48e792693406e43b593092f5e01305dba1 GIT binary patch literal 201 zcmX@j%ge<81b1?`ri19mAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdmEmj^6Iz^FR2)-W znvq{poSj*zmzNq7P??;OSd<%3l%JKFTv8lUP+5|Zp9kf}q+})LrRVDwCnx6VCg-Q5 z7VD;@7Ubkt#v~;cf%rhWBsn9sI3_+mGcU6wK3=b&@)w6qZhlH>PO4oIE6`#_AT9m}#sl@w(r6)^)9tYr8MQuiy>*(xTqIJKxarnodCzoa-j zvr;cFH71}kIU}(sH=rm#D>b>KIHsVoBqKi$%8h}Tr(2wyn5UbZpORXvo03|PlV2H= zlvo7f1L>0FjMU;7uoLtODt~d<4;{!7zBjY^=g(5Z}2LSeT BMtlGO literal 0 HcmV?d00001 diff --git a/batches/__pycache__/apps.cpython-312.pyc b/batches/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ca8ac5e5de708d60a89ff2a2c3bd2178e22802f GIT binary patch literal 509 zcmXv~F-yZh6uwJRt*sG}DmXZ}2pO7LM1;1YxRfp~A#hxC*CaK`#auGPP3RABtA9b9 z{S_{bCFtbjCRCSB-X-yd_rCAm_a5(kue#m>;=PA2?xV<`lKj6$4VFg&%mD!e1|WnM zgg^n8fG9ISR0^NfN@pp#W;DJeF<`OLi{k-{Ccf)bsU+~%3bQhwv-W<>2#?szx} zM+uu!C*^jWryh%HYF9gUxr3vwBevvSLgRqt_82FUi(KAh<2dGKoF~%UGH3#DU4$7} z>>s~rdy97G{ydRCWqoL2_yG=o P))ct=aPIb= literal 0 HcmV?d00001 diff --git a/batches/__pycache__/models.cpython-312.pyc b/batches/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a79861d31f754a2a3bf315ce38436e15ea847570 GIT binary patch literal 242 zcmX@j%ge<81b1?`rrQAN#~=<2FhLog1%Qm{3@HpLj5!Rsj8Tk?43$ip%r8OGnvA#D za`RJCbBg^mnQn2WWF_XM=j){;6)^)PS2BDCsri-WY!wq)oLW>IQ(T&nUs9Z%S*e$o z8WT{NoRL_R8&H&=m6}{q98*wPl98VW<;Fma(k)I-%+pN<+EA>Ul3I|HUm25>SOnq& v>5}A()Z!S3^?C)Bzc_4w?9!Z6yCM#twTwVq3}Sp>W@Kc%#~@S02IK$$^r%HT literal 0 HcmV?d00001 diff --git a/batches/__pycache__/urls.cpython-312.pyc b/batches/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3868238ea464d47e6da01c6723af211ebf9b174f GIT binary patch literal 773 zcmb7BO^ee&7@lb+ZTb<5g&+jMV`)iRiZ@wB*2A(O%dV)05LvRBp&#apnbak{dD~-e z`WIw9^d$ZVFD)q)1D-s{-d5Jro}6j3t9THd!!ytGzVGwC^YKYlD}b-v@I&7(0Pt0a ztEJ@R;s+;h00tO(z=sxu02i?26@1B(d}JYCwq(C(6(J}90x{w!jc#1E`L&k?<4NomAG$^mr>VByfB(k^Av5xK0T%QRuo8 zH>f!?U-3rWy;eD^;*k^d!_Js`jEXnSWRYW3=A0G|Ohl&?v#~jWj2;moS`x`h&!NK<$kjp3f zTx(BXthDw*Yp0qq+ghTBpOJEMYu;#1%~hkhXf)GCYgSvL`y$Tkjp^Y^GZvbWYMq(A jMB5^MXRi0&c2|0Dq4!e#?nh&ZcFt9$UU*%lbw2wibr;53 literal 0 HcmV?d00001 diff --git a/batches/__pycache__/views.cpython-312.pyc b/batches/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30cb6a3f56a244a4b5d2c50acd6941a4bc7b8340 GIT binary patch literal 9206 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 QZe^G)BjPs%W4c!V2PV9sDgXcg literal 0 HcmV?d00001 diff --git a/batches/admin.py b/batches/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/batches/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/batches/apps.py b/batches/apps.py new file mode 100644 index 0000000..27433ae --- /dev/null +++ b/batches/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BatchesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'batches' diff --git a/batches/migrations/__init__.py b/batches/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/batches/migrations/__pycache__/__init__.cpython-312.pyc b/batches/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..daddc0d82acbf116fd55be30f2b8b925827f391e GIT binary patch literal 212 zcmX@j%ge<81b1?`ri19mAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdRqSjP6Iz^FR2)-W znvq{poSj*zmzNq7P??;OSd<%3l%JKFTv8lUP+5|Zp9kf}q+})LrRVDwCnx6VCg-Q5 z7VD;@7Ubkt#v~;cf%rhWBsn9sI3_nUy(qCHGe565CO$qhFS8^*Uaz3?7l%!5eoARh Ys$CH)&<%`0Tnu7-WM*V!EMf+-03=I3Pyhe` literal 0 HcmV?d00001 diff --git a/batches/models.py b/batches/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/batches/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/batches/tests.py b/batches/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/batches/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/batches/urls.py b/batches/urls.py new file mode 100644 index 0000000..feae665 --- /dev/null +++ b/batches/urls.py @@ -0,0 +1,14 @@ +from django.urls import path +# from .views import ScanAPIView, ScanAspuAPIView, GetManagementAPIView +from .views import BatchListView, BatchListGTIN, BatchListReport, BatchReportView + + +urlpatterns = [ + # path('api/scan/', ScanAPIView.as_view(), name='scan'), + # path('api/scan_aspu/', ScanAspuAPIView.as_view(), name='scan_aspu'), + path('batches/', BatchListView.as_view(), name='batch-list'), + path('gtin/', BatchListGTIN.as_view(), name='batch-gtin'), + path('reports/', BatchListReport.as_view(), name='batch-gtin'), + path('reports-view/', BatchReportView.as_view(), name='batch-gtin') + +] \ No newline at end of file diff --git a/batches/views.py b/batches/views.py new file mode 100644 index 0000000..dc6aa83 --- /dev/null +++ b/batches/views.py @@ -0,0 +1,193 @@ +import requests +from django.http import JsonResponse +from django.views import View +from django.conf import settings +import asyncio +import aiohttp +import json +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt + + +# http://192.168.254.2/szkm/api/batch/load/eb7bb39a-fd68-47c7-aec8-0041a4fe5faf/ + +# Настройки API +API_BASE_URL = "http://192.168.254.2:8280/api" +API_REPORTS_URL = f"{API_BASE_URL}/productlinereport/list" +LOGIN_URL = f"{API_BASE_URL}/login" +BATCH_LIST_URL = f"{API_BASE_URL}/Batch/list" + + +# Данные для входа +USERNAME = "user" +PASSWORD = "user" + +def get_auth_token(): + """Авторизуется и получает Bearer токен""" + response = requests.post(LOGIN_URL, json={"username": USERNAME, "password": PASSWORD}) + if response.status_code == 200: + data = response.json() + token = data.get("Value", {}).get("Token") # Исправленный способ получения токена + if token: + return token + return None + + + +class BatchListView(View): + """Получает список партий с фильтрацией по CreatedOn""" + def get(self, request): + token = get_auth_token() + if not token: + return JsonResponse({"error": "Не удалось получить токен"}, status=401) + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + payload = { + "Filter": { + "Filters": [ + {"Value": "2024-01-1 00:00:00", "Operator": "gte", "Field": "CreatedOn"}, + {"Value": "2025.11.22", "Operator": "lte", "Field": "CreatedOn"} + ], + "Logic": "and" + } + } + + response = requests.post(BATCH_LIST_URL, json=payload, headers=headers) + if response.status_code == 200: + return JsonResponse(response.json(), safe=False) + return JsonResponse({"error": "Ошибка при запросе данных"}, status=response.status_code) + + +class BatchListGTIN(View): + """Получает список партий с фильтрацией по CreatedOn""" + def get(self, request): + token = get_auth_token() + if not token: + return JsonResponse({"error": "Не удалось получить токен"}, status=401) + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + payload = { + "Filter": { + "Filters": [ + { + "Filters": [ + { + "Value": "04607017161371", + "Operator": "eq", + "Field": "ProductType.GTIN" + }, + { + "Value": "04607017161371", + "Operator": "linq", + "Field": "CodesForProductLines.Any( a=> a.ProductType.GTIN=='{0}' )" + } + ], + "Logic": "or" + } + ], + "Logic": "and" + }, + "Includes": [ + "ProductType", + "CodesForProductLines.ProductType" + ] + } + + response = requests.post(BATCH_LIST_URL, json=payload, headers=headers) + if response.status_code == 200: + return JsonResponse(response.json(), safe=False) + return JsonResponse({"error": "Ошибка при запросе данных"}, status=response.status_code) + + +@method_decorator(csrf_exempt, name='dispatch') +class BatchListReport(View): + """Получает список партий с фильтрацией по CreatedOn и BatchId из тела запроса""" + + def post(self, request): + token = get_auth_token() + if not token: + return JsonResponse({"error": "Не удалось получить токен"}, status=401) + + try: + body = json.loads(request.body) + batch_id = body.get("id") + except json.JSONDecodeError: + return JsonResponse({"error": "Неверный формат JSON"}, status=400) + + if not batch_id: + return JsonResponse({"error": "Отсутствует параметр 'id'"}, status=400) + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + payload = {"skip":0,"take":10,"includes":["ProductLineBatch","ProductLineBatch.ProductType","ProductLine"],"sort":[{"field":"CreatedOn","dir":"desc"}],"filter":{"filters":[{"operator":"eq","field":"ProductLineBatchId","value":batch_id}],"logic":"and"}} + + response = requests.post(API_REPORTS_URL, json=payload, headers=headers) + if response.status_code == 200: + return JsonResponse(response.json(), safe=False) + return JsonResponse({"error": "Ошибка при запросе данных"}, status=response.status_code) +class BatchReportView(View): + """Получает список партий и прикрепляет к ним отчёты асинхронно.""" + + async def fetch_reports(self, session, headers, batch_id): + """Асинхронно получает список отчётов для заданной партии.""" + if not batch_id: + return [] + + report_payload = { + "Skip": 0, + "Take": 100000, + "Filter": { + "Field": "BatchId", + "Operator": "eq", + "Value": batch_id + }, + "TotalCount": 0, + "Sort": [{"Field": "CreatedOn", "Dir": "desc"}] + } + + async with session.post(API_REPORTS_URL, json=report_payload, headers=headers) as response: + if response.status == 200: + data = await response.json() + return data.get("Value", {}).get("Items", []) + return [] + + async def fetch_all_reports(self, batches, headers): + """Запрашивает отчёты для всех партий параллельно.""" + async with aiohttp.ClientSession() as session: + tasks = [self.fetch_reports(session, headers, batch.get("ProductTypeId")) for batch in batches] + reports = await asyncio.gather(*tasks) + + # Присваиваем отчёты соответствующим партиям + for batch, report in zip(batches, reports): + batch["Reports"] = report + + def get(self, request): + """Основной метод обработки GET-запроса.""" + token = get_auth_token() + if not token: + return JsonResponse({"error": "Не удалось получить токен"}, status=401) + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + # Запрос списка партий + batch_payload = { + "Filter": { + "Filters": [ + {"Value": "2025-01-1 00:00:00", "Operator": "gte", "Field": "CreatedOn"}, + {"Value": "2025.11.22", "Operator": "lte", "Field": "CreatedOn"} + ], + "Logic": "and" + } + } + batch_response = requests.post(BATCH_LIST_URL, json=batch_payload, headers=headers) + + if batch_response.status_code != 200: + return JsonResponse({"error": "Ошибка при запросе списка партий"}, status=batch_response.status_code) + + batch_data = batch_response.json() + batches = batch_data.get("Value", {}).get("Items", []) + print(batches) + # Асинхронно получаем отчёты для всех партий + asyncio.run(self.fetch_all_reports(batches, headers)) + print(fetch_all_reports) + return JsonResponse({"Batches": batches}, safe=False) + diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..25c14b2b26e453487d5cf226805407d28ad3e0be GIT binary patch literal 139264 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 literal 0 HcmV?d00001 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1f80496 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +version: '3.8' + +services: + django: + build: . + container_name: django_app + restart: always + ports: + - "8000:8000" + volumes: + - .:/app + depends_on: + - db + environment: + - DEBUG=True + - DATABASE_URL=postgres://myuser:mypassword@db:5432/mydatabase + + db: + image: postgres:15 + container_name: postgres_db + restart: always + environment: + POSTGRES_USER: myuser + POSTGRES_PASSWORD: mypassword + POSTGRES_DB: mydatabase + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" + +volumes: + pgdata: diff --git a/forms/__init__.py b/forms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/forms/__pycache__/__init__.cpython-312.pyc b/forms/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5942cd1b8caa533cbd66e9d5a34a33e0f8d79a7 GIT binary patch literal 199 zcmX@j%ge<81T1~W(n0iN5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!N^`b~2`x@7Dvl{G z&B!k)&d#jV%S(+3s7%gCEXoZi%FjwoE-8*Fs4U6I&x3MfQnC{B((`qTlN0lFlk-zj zi*-{{3v%)+W0De!K>V1r{G#0AnE3e2yv&mLc)fzkUmP~M`6;D2sdh!IKx-L+xERFv N$jr#dSi}ru0RS`TI8guq literal 0 HcmV?d00001 diff --git a/forms/__pycache__/admin.cpython-312.pyc b/forms/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e268c0fdcc7bfcbd22fb62149db032a7fe9938a9 GIT binary patch literal 243 zcmX@j%ge<81T1~W(w%_xV-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9eI8O~zZS zi7C06d48HqxA;=B67$mY^^)`RN{TX*ikN{4Rx*4Bsr!}eY!wq)oLW>IQ(T&nUs9Z% zS*e$o8WT{NoRL_R8&H&=m6}{q98*wPl98VW<;FnF(=AR;%+pQIPf0D-O-U`t$*+t_ zN-P5LW76`Aa*JcY4$v#8{Ka9Do1apelWJGQ0ko45h>Jmt56p~=jQ1E6ir9c00LCOm AApigX literal 0 HcmV?d00001 diff --git a/forms/__pycache__/apps.cpython-312.pyc b/forms/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4b22badf3e216b0ef780158ee33560158c11a8f GIT binary patch literal 503 zcmXv~ze~eF6uwJRsr?~RMR0I%5i&HhhzMG#Tj}Bw0>?FXNmG+tyi10-37y43=-(jz zA1*GACE(=bCRCSB-X-yd_rCAm_dVWwZ{2PS$Y(sf`cE={D)RptHCP=d6EoR>;=ACm8%GAvf7nk0VuE`0k%e7Af7L*G|WK))kn;^Gh>Wp)Dig`5V zY)0KwI7yyPSzJ@6ny@ci9QR#0qv#Wwge-R^m@Dp-Gdeg)gq7r6owW>x1fna$94z;b z-;KRxyLW%d)lXR;nR?N#*H>n)4qe$h3YEewiPp{2YT+}!EvdS(Q1|`TA%q{`@MlAV MJCEiskg0V20hF?bTL1t6 literal 0 HcmV?d00001 diff --git a/forms/__pycache__/models.cpython-312.pyc b/forms/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abf651f65e1b0b16b330a7a8fc2ca2f606343e0e GIT binary patch literal 1450 zcmah}O=ufO6rTN&CF{>wHP)dnMy8eOZLpI=2qCy7K_Y>ax*>IX5Q1U7GqR=Z?x-`X z#4;i1U}%oY$$?NvPff88KK9sK54~8(ATdxV^psnu+KW$}*_CXyC1?(B-h1EspU3=~ zO2vUV-=6%k^`{2FA5s{f$cP!<5X?S604WY|m9nA$72E+x-2+I&N?jj2oZkl`mON#x zrJIpAk~I}R;}V1&W=nB=#?C`ukx4KpD+rWT6=1C%8ylV92P3u|2`rN5$e2UQp?V-< z$y1I_jT4({F+0^F9#~|;>0_uVK-{hs(C4r3Ep2P&A3S_(%6Ocf(?`FONroO(bfWQIj%}Y+8ZCdDcq6fb>Xx@fR+w#hE4J8Rwt{fOA+5@$MWtM+it({Z zxZJCa7LP*6dOHD-Put*3Ar$LFid7hLFc06bYM-p_qb_4JDw`UhJk9d+A*J&LFYy>{>su&`m77Vke19?b2XA-}$;fpYP7+d-ET+*Z=2XEOoMd zW2tK_^^6bOcL$k5r_j$7x|u>Rv)ulCLVjKPNk7SaDswSF2~3m@(? l`jG(SUb6QEd8;Xk@*DWzFFmc4l+LyNwdZSp1IY)z{{TTTN>=~? literal 0 HcmV?d00001 diff --git a/forms/__pycache__/serializers.cpython-312.pyc b/forms/__pycache__/serializers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..395c54d90d266068f912aaf25706619e179b21be GIT binary patch literal 751 zcmZuvF>ljA6u$G>PG}Qo0f9t_=!E1UnJa`?kjm7m0Y>-m>T}g6S5JV`dNE$C{^tbQmKUbM5Z{xEPeVYdI^r0$V`SsX%5q>pUY^!N`|q{_H{9mNok^S zRSt`M5k_JZ=Lbd4ByrwLK-Khw9IK*=UdI~aXaIJC;^pU?{L{Sfbm@wBA5Aueyn>u>^dhh1?&<@j5>VfGxxtbm_X5 zrsM++PQ5Mkp~gR>_UDcmYtM~cv}g5VR{m-KgHh~<3vGCG9-{FG#~ z^a5Oc7T_a50Kou+(1j475>kRvsJKd~x@xGo8U!VPAyP$Drdyj9H~U!uk@l&4rmV?I zp^K>9s;K;@s0xL8tD<(J7%8rv)f-zE(y{?_HTIM7g%}kyl>FW^Lc^Ds#Ux_5N3oya zR}=g$H~!A=Pgt_b{&V^;4v3Frex|c@5Ka;&-&unrT6C0lkssn&QE<|gh&i*}V_|Hs zsZDWAXfms9WX#F$1j6)BANHbA$?It9Mk`O@rIgi8fN_#Ie3Kz|PU zzsf+Xp4{g~iyIG@Mr&@g7RDZbvVX2V|E5-W^#Qj!+-fbY&fMxOtS;~M&(+tWbYgL{ c&CN$kvpqN43-dAWO3|gR?v&ow>0M#<8*GQk;Q#;t literal 0 HcmV?d00001 diff --git a/forms/__pycache__/views.cpython-312.pyc b/forms/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3634eac3284fbe20ff3b64c9238d917a7fc5da31 GIT binary patch literal 5016 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 bTNn?%-g5sTI$|O%c zQjVD^1u}HZR*zl_Yv`CUV~Z{nWI)1AK|5s=;1mTi^+-yv3OImnIK7Yey?a0U-u;sn-+~47$V(LuY3SVPr&70oEH><8V5m*hg>bx0<|y?P^6ul zbqH+8!w@@LXp7gJagc*y80kgNS&80i@^HatF9&#bKc4``iTRw&>}g=bcuzQI`FV@+ z5_8;@oP(2^gR|&!R)W~G`%K={X`U&%t@`-4T14t4_5Ny&Kc$ zEv}YyR-`PAKbR`wfF`S$7~dhQ!{mV|ufZzizrH7hX;SIhSXK#AYy@edLo`Dnj(mh1 z8n+P96kU6QX!0152DXrFDh8qpO+~daacEfAO!OMEswIvIP&tuxWVY->x`;c7i4hZP z;?W_vp(BF|h!9NVC)h+ZfmO9j2!gU=(*!{l){lL_)EsH(ig`rCW>?qg62y;9TQiW% z(zLKig{F2Q8^}JykP4IB*(EHApd1rFmK6xuJz+oLXj4Nvv>r++8k7BuWSI)G%*Ldv zBW5RM`NyuJdt`053y|>X->&R^+%4% zE-@Yv+(xQx)jJL=+MK{TY%699Z&|8hZmBFIYl|hUW2fFwh==P<_ODy@tCyj^v$Jzs zo|ISWIK+Ei{LM40TWs{6fme&mPxr@RE)#uve;fnx{LjKLw$_iWjnes7=^Ji#gTR_XO#ZHT`!;Q4%f^5_3~i-Q&-%*-usrD-#$Ga=C}L#?Lq#wD}FLcuepWFS#(&a z^b3_iVb>Ku`~SBWm6!0M@U1J}AEiHZizXTh;Hheh*9o_I~K6jH@Z)0BsO1n!+ k40`XIm1EmD#BtoeQNT%WqBptdb73eJ`(p7Qz<}@lAAA9sV*mgE literal 0 HcmV?d00001 diff --git a/forms/migrations/__pycache__/__init__.cpython-312.pyc b/forms/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62dbc7639ec2bb920aed02d82e94ef92112cb57d GIT binary patch literal 210 zcmX@j%ge<81T1~W(n0iN5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!Ds;Ar2`x@7Dvl{G z&B!k)&d#jV%S(+3s7%gCEXoZi%FjwoE-8*Fs4U6I&x3MfQnC{B((`qTlN0lFlk-zj zi*-{{3v%)+W0De!K>V1r{G#0AnB2_tqQsKS{Ji3r`1s7c%#!$cy@JYL95%W6DWy57 Yc15f}7cc^GF^KVznURsPh#ANN02^jJ^#A|> literal 0 HcmV?d00001 diff --git a/forms/models.py b/forms/models.py new file mode 100644 index 0000000..b1e928c --- /dev/null +++ b/forms/models.py @@ -0,0 +1,17 @@ +from django.db import models + +class FormResponse(models.Model): + factory = models.TextField() + line = models.CharField(max_length=100) # ����� + operators_name = models.TextField() # ��� ��������� + problem = models.TextField() # �������� + error_zone = models.TextField() # ���� ������� + occurred_at = models.DateTimeField() # ����� ������������� + resolved_at = models.DateTimeField(null=True, blank=True) # ����� ������� + downtime_reason = models.TextField() # ������� ������� + fix_method = models.TextField() # ������ ���������� �������� + + created_at = models.DateTimeField(auto_now_add=True) # ����� �������� ������ + + def __str__(self): + return f"{self.line} - {self.problem} ({self.occurred_at})" diff --git a/forms/serializers.py b/forms/serializers.py new file mode 100644 index 0000000..625614f --- /dev/null +++ b/forms/serializers.py @@ -0,0 +1,9 @@ +from rest_framework import serializers +from .models import FormResponse + +class FormResponseSerializer(serializers.ModelSerializer): + class Meta: + model = FormResponse + fields = '__all__' + + diff --git a/forms/tests.py b/forms/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/forms/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/forms/urls.py b/forms/urls.py new file mode 100644 index 0000000..696eab6 --- /dev/null +++ b/forms/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from .views import FormResponseCreateView, FormResponseListView, FormResponseUploadView + +urlpatterns = [ + path('submit/', FormResponseCreateView.as_view(), name='submit_response'), # POST-запрос + path('submit/uploads/', FormResponseUploadView.as_view(), name='form-upload-report'), + path('responses/', FormResponseListView.as_view(), name='list_responses'), # GET-запрос +] diff --git a/forms/views.py b/forms/views.py new file mode 100644 index 0000000..63fcae2 --- /dev/null +++ b/forms/views.py @@ -0,0 +1,90 @@ +from .serializers import FormResponseSerializer +import pandas as pd +from rest_framework import generics, status +from rest_framework.response import Response +from rest_framework.parsers import MultiPartParser, FormParser +from rest_framework.views import APIView +from .models import FormResponse +import logging +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +import pandas as pd +from .models import FormResponse # Убедись, что модель импортирована +from .serializers import FormResponseSerializer # Импортируем сериализатор +from django.core.files.storage import default_storage +from django.core.files.base import ContentFile +from datetime import datetime +import json + +# API для сохранения ответов +class FormResponseCreateView(generics.CreateAPIView): + queryset = FormResponse.objects.all() + serializer_class = FormResponseSerializer + +# API для получения списка ответов +class FormResponseListView(generics.ListAPIView): + queryset = FormResponse.objects.all() + serializer_class = FormResponseSerializer + + + +# API для загрузки Excel-файла и сохранения в БД +logger = logging.getLogger(__name__) # Логирование ошибок + + +class FormResponseUploadView(APIView): + def post(self, request, *args, **kwargs): + print("\n===== [DEBUG] Данные, полученные на сервере =====") + print(json.dumps(request.data, indent=4, ensure_ascii=False)) # Логируем JSON + + if "data" not in request.data: + return Response({"error": "Данные не найдены в запросе"}, status=status.HTTP_400_BAD_REQUEST) + + uploaded_data = request.data["data"] + responses = [] + errors = [] + + for item in uploaded_data: + # Обработка значений по умолчанию + operators_name = item.get("operators_name", "Анонимный пользователь") or "Анонимный пользователь" + error_zone = item.get("error_zone", "Не указано") or "Не указано" + downtime_reason = item.get("downtime_reason", "Не указано") or "Не указано" + fix_method = item.get("fix_method", "Не указано") or "Не указано" + + # Функция для парсинга дат + def parse_date(date_str, default_now=False): + if not date_str: + return datetime.utcnow().isoformat() if default_now else None # Подставляем текущую дату если default_now=True + try: + return datetime.fromisoformat(date_str.replace("Z", "+00:00")).isoformat() + except ValueError: + errors.append({"occurred_at": f"Неверный формат даты: {date_str}"}) + return None + + occurred_at = parse_date(item.get("occurred_at"), default_now=True) # Если нет даты — ставим текущее время + resolved_at = parse_date(item.get("resolved_at")) # Если нет, оставляем None + + response_data = { + "line": item.get("line", "Неизвестная линия"), + "operators_name": operators_name, + "problem": item.get("problem", "Не указано"), + "error_zone": error_zone, + "occurred_at": occurred_at, + "resolved_at": resolved_at, + "downtime_reason": downtime_reason, + "fix_method": fix_method, + } + + serializer = FormResponseSerializer(data=response_data) + if serializer.is_valid(): + responses.append(serializer.save()) + else: + errors.append(serializer.errors) + + if errors: + print("\n===== [DEBUG] Ошибки при сохранении данных =====") + print(json.dumps(errors, indent=4, ensure_ascii=False)) # Логируем ошибки + return Response({"error": "Некоторые записи не были сохранены", "details": errors}, status=status.HTTP_400_BAD_REQUEST) + + return Response({"message": "Данные успешно загружены!", "saved": len(responses)}, status=status.HTTP_201_CREATED) \ No newline at end of file diff --git a/inventory/__init__.py b/inventory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/inventory/__pycache__/__init__.cpython-312.pyc b/inventory/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7bd4d5c6b686d509ceaca60a94fdc4f7903cd76 GIT binary patch literal 203 zcmX@j%ge<81ldQor-SInAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdmE~*|6Iz^FR2)-W znvq{poSj*zmzNq7P??;OSd<%3l%JKFTv8lUP+5|Zp9kf}q+})LrRVDwCnx6VCg-Q5 z7VD;@7Ubkt#v~;cf%q|*d1a}2CHX~_G4b)4d6^~g@p=W7zc_4i^HWN5QtgUZfmSmD RaWRPTk(rT^v4|PS0syXII;#Kx literal 0 HcmV?d00001 diff --git a/inventory/__pycache__/admin.cpython-312.pyc b/inventory/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f8d24d5a0875018868f8a0f4defbe556ff15b6d GIT binary patch literal 247 zcmX@j%ge<81ldQor#k`Z#~=<2FhLog1%Qm{3@HpLj5!Rsj8Tk?43$ip%r8OGnvAzt z6H{_C^ZYcKZtm}#sl@w(r6)^)9tYr8MQuiy}*(xTqIJKxarnodCzoa-j zvr;cFH71}kIU}(sH=rm#D>b>KIHsVoBqKi$%8h}Tr(2wyn5UbZpORXvo03|PlV2H= zlvo7f$7JS}rRJ677gff99idlH`HRCQH$SB`C)KWq186TJ5Ep|OAD9^#8SgPD6tMw0 E0F4AkdjJ3c literal 0 HcmV?d00001 diff --git a/inventory/__pycache__/apps.cpython-312.pyc b/inventory/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6c09774cff28317c79ff7ea587ce9cea486012b5 GIT binary patch literal 515 zcmX|7ze~eF6uwJR=}#4rii3lTkfE7HM5qe9)(B=rsNec!$BJ>Gq9jYbv7xPE;ZKFa#3$SO(&SR6<&0|XH0 zg9)@D1RA&iM4JMlQ~0dsI$KK&v-G8i3DZhD3_C0sctbaD=NT4cu`R(2P+&s>Y>hx$ zCz@>}x@n{vm%%L!BF2+^!QCDi<6y`v(zhmzP+wT5-mo1_-U8C1{XsF;wpfEYyL^LFdT15|j%^#3L4oYcH_^ z>huzKg!!b$*_gVKaKa=Sv7n$%eq~FzIB2=@PSGMX^jYHcF<0FAzth2CD6BBy%Hk{; z5{QOOQ!w8>d^ei&TJ!#ltCy_bHTASs*r0~FGCd!!E0hV>C0dq`^G~1fO-YsCLV5kI ULkK^>{?D2QcOJZ7AWJs&2QgHQp8x;= literal 0 HcmV?d00001 diff --git a/inventory/__pycache__/models.cpython-312.pyc b/inventory/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc292760b5982de0440910001f3c73a5fb78acd8 GIT binary patch literal 4255 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 T6JR=LhGyj(@;3yAmj3?&#~k}$ literal 0 HcmV?d00001 diff --git a/inventory/__pycache__/serializers.cpython-312.pyc b/inventory/__pycache__/serializers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b216b63c3de956ec5315573fe5703113d149bb8 GIT binary patch literal 2762 zcmbtW-D@0G6u)<7b~D+{X0tVIwoTiNhQ=RW9hEq(Hw*{|%bX#_9KnS0Ou z&AI2E&-po#h!Gg~>Ft@%BtrhgMQuc?ll3oQa-A5&pe89$l~Q5|hG+@}Q56f4DitDX z1bD=d%xIxQ?Vv;;$A}TROpGX_b3HYIjrX!xnX8>4vICIvHpp0r>;zD&zh?1(j*hr!m-{9q#=`us$@_# zQWi4elm35~Gjg1{deEV1x@l_KWF{KOnr7(*rfES;(+ajxGI8FeY44VFv$Dg{vq1zl znHfZ<^UO4yC9;kxJSiNV$i4|SbFxlp)^?rK`SOUxvM0*9S)CV7aC?sBTqj#ByR)`c zU1p6r-I}q7ot$nB=Rm46Y_Otfm$RpIj_YjRIs*o^c{#hu9V5kZAff)e3r_BU$HB>E zvevWrN@OjyXEDmrB77H+$t8(*<06R!Ln>tRXK)sxcH#`9IV4e-tXIT65`;+qLjt4% zL`;+Y>puMjc0SnT0f8`aP6lSnSgx+s$hL@Rh(OI-hCvPC3$Y$8@|HNM30Fy&FtzI6 z#tcXxHEzg2yRMLFYII(f)x;HYj;h^LBohl#)zEGP;C#y3lmsIf7$-AA5OwSl&#|DB zGu_Z^%PhA5XWYEiWW4_pyt~?t595|6z$Os0t&T$pc|<{vV~e0zyIUo|-m! zYesWy+v0fXCZ&5ft5k!{4?SJ=8mvW0=`fQ40DE>wNl6TX;tFP4K^Kr5{R|5tPN@jBKjqpQXGP8&s8hO| zx2=rG2jQqd&RdSFTbL7?20g=ZG|j;)Dat|^0sIA6%n!EPu1(|~06KetJYFDw$URpR zi-`}Dza@JY&-%%OpQl%nqn#8=b9etOtD^tyNQtzXk`d(zG= z*kRHB3ybzcnMRa{Q5qR~6cr<54+xM-Ax9N*@YF}WkSoAyG}0}b^X{tCY8}5}-X^iN zQt`C@ynjnkPc3!W^|mQ>F98JaQ!jM`ZKZCKV@|df%0tLQGJumblCV)CYckUnbVIbe z+BApKf7a%wc^G2R@UOrLe-U_Ch*8Br#vdU!Utk>DVURqI{|*gvRr+oi7vZVRCHskd zr1B&3GCu-eqdA>pKpu#c(tBj+cQWCViF;)CMpUHzH{My@H}3Bn-ypEqi0zm ZdTfKhV&fpNKaH;{hkfPn--ros^$!cEj4A*C literal 0 HcmV?d00001 diff --git a/inventory/__pycache__/urls.cpython-312.pyc b/inventory/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77601e96c4a6cba4e44c1b3d1b0bb08d24d441c9 GIT binary patch literal 947 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 b expiration_date + + def save(self, *args, **kwargs): + """При сохранении автоматически обновляем статус""" + if self.is_expired(): + self.status = "просрочены" + elif self.is_expiring_soon(): + self.status = "скоро истекает" + else: + self.status = "в наличии" + super().save(*args, **kwargs) + + def is_expiring_soon(self): + """Срок годности истекает через 1 месяц""" + if not self.emission_date: + return False + expiration_date = self.emission_date + timedelta(days=365) + return expiration_date - timedelta(days=30) <= date.today() < expiration_date + + +class StickerMovement(models.Model): + sticker = models.ForeignKey(Sticker, on_delete=models.CASCADE) + date = models.DateTimeField(auto_now_add=True) + from_location = models.CharField(max_length=50) + to_location = models.CharField(max_length=50) + quantity = models.IntegerField() + + def save(self, *args, **kwargs): + if self.from_location == "склад" and self.to_location == "цех": + self.sticker.location = "цех" + elif self.to_location == "склад": + self.sticker.location = "склад" + self.sticker.save() + super().save(*args, **kwargs) diff --git a/inventory/serializers.py b/inventory/serializers.py new file mode 100644 index 0000000..1cf07aa --- /dev/null +++ b/inventory/serializers.py @@ -0,0 +1,34 @@ +from rest_framework import serializers +from .models import Nomenclature, Sticker, StickerMovement +from datetime import date + + +class NomenclatureSerializer(serializers.ModelSerializer): + class Meta: + model = Nomenclature + fields = '__all__' + +class StickerSerializer(serializers.ModelSerializer): + is_expired = serializers.SerializerMethodField() + is_expiring_soon = serializers.SerializerMethodField() + nomenclature_name = serializers.CharField(source="nomenclature.name", read_only=True) + nomenclature_gtin = serializers.CharField(source="nomenclature.gtin", read_only=True) + class Meta: + model = Sticker + fields = '__all__' + + def get_is_expired(self, obj): + return obj.is_expired() + + def get_is_expiring_soon(self, obj): + return obj.is_expiring_soon() + + def to_representation(self, instance): + """Обновляем статус при каждом запросе""" + instance.save() + return super().to_representation(instance) + +class StickerMovementSerializer(serializers.ModelSerializer): + class Meta: + model = StickerMovement + fields = '__all__' diff --git a/inventory/tests.py b/inventory/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/inventory/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/inventory/urls.py b/inventory/urls.py new file mode 100644 index 0000000..20b1f69 --- /dev/null +++ b/inventory/urls.py @@ -0,0 +1,13 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import NomenclatureViewSet, StickerViewSet, StickerMovementViewSet, ExternalNomenclatureView + +router = DefaultRouter() +router.register(r'nomenclature', NomenclatureViewSet) +router.register(r'stickers', StickerViewSet) +router.register(r'movement', StickerMovementViewSet) + +urlpatterns = [ + path('api/', include(router.urls)), + path('api/external-nomenclature/', ExternalNomenclatureView.as_view(), name="external-nomenclature"), +] diff --git a/inventory/views.py b/inventory/views.py new file mode 100644 index 0000000..cf20938 --- /dev/null +++ b/inventory/views.py @@ -0,0 +1,122 @@ +import time +import logging +import requests +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status, viewsets +from .models import Nomenclature, Sticker, StickerMovement +from .serializers import NomenclatureSerializer, StickerSerializer, StickerMovementSerializer + +# Авторизационные данные +USERNAME = "superuser" +PASSWORD = "Superuser1105" +TOKEN_URL = "http://192.168.254.2:8280/api/login" # URL для получения токена +PRODUCT_LIST_URL = "http://192.168.254.2:8280/api/ProductType/list" + +# Глобальный кэш для хранения токена +token_cache = {"token": None, "timestamp": 0} +TOKEN_LIFETIME = 1440 * 60 # 24 часа в секундах + + +def get_new_token(): + """Получение нового токена с кешированием.""" + global token_cache + current_time = time.time() + + # Если токен есть и он еще не истек, возвращаем его + if token_cache["token"] and (current_time - token_cache["timestamp"] < TOKEN_LIFETIME): + return token_cache["token"] + + payload = {"UserName": USERNAME, "Password": PASSWORD} + try: + response = requests.post(TOKEN_URL, json=payload, timeout=5) + response_data = response.json() + + if response.status_code == 200 and response_data.get("IsSuccess"): + token_cache["token"] = response_data["Value"]["Token"] + token_cache["timestamp"] = current_time + return token_cache["token"] + + logging.error(f"Ошибка получения токена: {response_data}") + return None + except requests.RequestException as e: + logging.error(f"Ошибка сети при получении токена: {str(e)}") + return None + + +class ExternalNomenclatureView(APIView): + """Получение списка номенклатур с внешнего API.""" + + def post(self, request): + product_line_id = request.data.get("product_line_id", "40ba525b-1686-4900-a2a9-443436812b1d") + token = get_new_token() + + if not token: + return Response({"error": "Ошибка авторизации"}, status=status.HTTP_401_UNAUTHORIZED) + + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + payload = { + "Filter": { + "Filters": [ + { + "Logic": "and", + "Operator": "linq", + "Field": "ProductType_ProductLines.Where(i=>i.ProductLineId=='{0}').Any()", + "Value": product_line_id, + } + ], + "Logic": "and", + }, + "Includes": ["ProductType_ProductLines"], + } + + try: + response = requests.post(PRODUCT_LIST_URL, json=payload, headers=headers, timeout=10) + response.raise_for_status() + return Response(response.json(), status=status.HTTP_200_OK) + except requests.RequestException as e: + logging.error(f"Ошибка запроса {PRODUCT_LIST_URL}: {str(e)}") + return Response({"error": "Ошибка запроса к API"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +# Основные API ViewSet'ы +class NomenclatureViewSet(viewsets.ModelViewSet): + queryset = Nomenclature.objects.all() + serializer_class = NomenclatureSerializer + + +class StickerViewSet(viewsets.ModelViewSet): + queryset = Sticker.objects.all().select_related('nomenclature') + serializer_class = StickerSerializer + + def create(self, request, *args, **kwargs): + data = request.data + gtin = data.get("nomenclature_gtin") # Берем GTIN из запроса + + if not gtin: + return Response({"error": "GTIN обязателен"}, status=status.HTTP_400_BAD_REQUEST) + + # Проверяем, есть ли номенклатура с таким GTIN + nomenclature, created = Nomenclature.objects.get_or_create( + gtin=gtin, + defaults={"name": data.get("nomenclature_name", "Неизвестный продукт")} + ) + + # Обновляем данные запроса, чтобы стикер использовал ID найденной/созданной номенклатуры + data["nomenclature"] = nomenclature.id + + serializer = self.get_serializer(data=data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + self.perform_destroy(instance) + return Response({"message": "Стикер успешно удалён"}, status=status.HTTP_204_NO_CONTENT) + +class StickerMovementViewSet(viewsets.ModelViewSet): + queryset = StickerMovement.objects.all() + serializer_class = StickerMovementSerializer diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..f237a58 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'barcode.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..c38d0a8e75fed55d5795985db8da69b662725b84 GIT binary patch literal 6426 zcma)=U2l^|5QX=;Qh!Pic0z!QUQi(}P*JHsqP>b7+i?v3!gffUAK&&obKc3k4pgcv z^WmADojG%6c6R^yd(`G_-LBiHZS*;`X?!pB|Ajug_KQBl_N1NZd)j_(KS|;h`?l8# zdH)y+PPO*E$PXn6+)}eI1r;)kJJK0^!?o_*g&9;s8 z|4ID+O8?sGchK7l$+i4CY2RdTq`z0v*@w+xBZ9ODdfNwl*$C)c3*VsGe^!Ml2O>0E9RTX^;&;#v_~Q|2%l1a9*iKWts+ky zymm~NlF#Ms7g^qhoP3u*V|fEo!;pc+`9n10@RN0i2Pv@{52RZmf~WJ44<#9PXe1(I zNmI_wgRq|#U^t8c53|}>)*iB8|<7mpG8kuGtg z-?x}3uJt>Sj3SoQ0Lk;tOW40t%=omIw(~jc^$1rs^@9d6Y@>FtmK`GZgAbd~sy<bR~hGF>@DpRc`kHQs5}B6{wZ2z56bVpiYRW7y*Z#^=h5`PxYa* z%!H}?f%%=gptBB*RrpGT8M~_iM6curryccxv^woe?V0$HHR`s|!(&Tl`gN_SH(F~8 zPg@n<}LD$hwsKq?1>>)^1 z1aq;CsRbFia>nVM$9faO*k{w+VxyE9c!}Iky>t^I?jy2e3;8NjHic! zCV9KjFNtRH`K@qXMdvZrW>$8!&syhG`7+iLC7#;LFIs!6FTO5RJ=r-^%b0(WbSW!0 zVFCV^1u(qSdbn%vh-0Wv=5*0qYk2NHlx$4Ng*xOu>hVJ8QYrn@3}1{1XXjQGLf5!e z%qQBLla&sXd8!P)>$I6?KIq?9vYY5()7`_f6Mc?3($SG$ulkz@?WF?BlMWL$kj}U z7$s}GjMJIyaWZaXh3WwXw0s&AMu*77N<2tE&2Rfq(hFh0t&582-}20%)Mh_=1w8c} zXOvxy_~gz?TzSSN2a{XJI&`KwlWYy9^euuV-jlWJkngqIOkVrEC>{<1gXAgq(wv%M zQ~GTbs;lAq_nOR@%m!Z+u_sT?rmZxyQx$W;2%p}zPwiv)OEh&lvx8YUk=OgKqVd3N zOnx2O??RcVKIrpXXUTc!=QT5d*RIg4Vq=Fd@7wG4rpF)OI*m={bn~#@EG8l*ljrga zR@yJD{UNWc1D{G|;a%a?`KIV|^G$2EIkK@!% zP4yD+$u;z=oFQNmb?d2@YDdN!nC}U7Iv8Q}jnr22;!arw$;>P{%3T(+LM)t}F!;Ul zg&v4t%k150KltS7%iY14!4S9)lAu~|6RsqC@wkE{!0DhI@%$4C=N-m*}Ivi!+f2!X<(Le^ZG_?fYZm7aoSZEcp=YMDkG7#`Aw@XRlVHJtB2DCYF@fk zRQt$zPOpy4`ABBpN`t%KBqGVI<7wUpZnX<{JHJ}M!L;L=!_!aF;ZDY@1FwtNA-nE4 zXfV6yYjNV>9Q-NCPV!HZQ_(r|Bs+{h>+}67E6f$hRG9ZIB%TAzO8lXxfKHW#w SrqTyv@5)OKJqGQX>iz{kY@LAs literal 0 HcmV?d00001 diff --git a/scan/__init__.py b/scan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scan/__pycache__/__init__.cpython-312.pyc b/scan/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f917528b9e22428fe32ce46aebc5558ce18c22e GIT binary patch literal 198 zcmX@j%ge<81YUM)(?RrO5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!N_Do12`x@7Dvl{G z&B!k)&d#jV%S(+3s7%gCEXoZi%FjwoE-8*Fs4U6I&x3MfQnC{B((`qTlN0lFlk-zj zi*-{{3v%)+W0De!K>QexOiX-yW?p7Ve7s&k&^u literal 0 HcmV?d00001 diff --git a/scan/__pycache__/admin.cpython-312.pyc b/scan/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73f94b7280ca3281b0a40e11c00bbe2352203862 GIT binary patch literal 242 zcmX@j%ge<81dY6N(w%_xV-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9eI8O~zZS zi7C06d48HqxA;=B67$mY^^)`RN{TX*ikN{4Rx*4Bsr!}WY!wq)oLW>IQ(T&nUs9Z% zS*e$o8WT{NoRL_R8&H&=m6}{q98*wPl98VW<;FnF(=AR;%+pQIPf0D-O-U`t$*+t_ xN-P5LV?Z)7VEgq7Dt~d<4;{!7zBjY^=g(5Z}2LMX9L~sBA literal 0 HcmV?d00001 diff --git a/scan/__pycache__/apps.cpython-312.pyc b/scan/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00d2c56ba5f7ea31a2f0a8530879307b4b7b1d88 GIT binary patch literal 500 zcmXv~F-yZh6uwJRscj=#MCjn)B4lW05fN%dw^9(7aBy66*EBUvVlEltR_G59moEMR z@u#>rmVlF!n^0Xkd6$cCc<=k}d*AWido#@j5HUBOy(dvWCHaq91=hy`ECB@+24Dsq z2!RH!0o4|O>I}Z>s?X7K!>WBrV!>j&7svf59Q&TDWR+n>*82i10Rs-Cz|kmlbgDT< zu3JXYxN%9Sybp(TNByc%lm*CIlxg^vIL literal 0 HcmV?d00001 diff --git a/scan/__pycache__/models.cpython-312.pyc b/scan/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a6d528d5bb61321e5ed737bc8f2eb263c778866 GIT binary patch literal 1308 zcmbVL&1(}u6rW9Y(=?k{W3;qZsMrq-v^{7kMMRXAilr@u)=LPC+sv3IZZ_%62D{=R zLJxYfJ>}}bLraQy^nVb+V4-2blc(Oq)JqZcz1@_4P$)Qux9`1q^Lt;v{Tz!$2%JA( zK4gC>g#19`@MtZi$v~+RMwnuff|61c5+Dx=3#<_qG%qfC zMqFT9twHN}Qf*9IBdIW!;_3?-!?HM=fKvAU!tLT#z>O4)Rcdl8YcIQ@vXxsY^DV`V z8fCjkt>SZPFqUb7d5EavESgtARLM({_uMDN0<=5_U8@;LgB;Go2oUh@oCU!8FYP9F9#IlKcl+u;5Vfr@$2jI3(K*3WvZZZ)=J2q+9d@)zxp3jY7Nic0{*y1AGZxIoP zkLW;wO!NdI$zc#{q@nk`9^2J>>Uz&R`EhhdAFoU{;sfiQyYYc~d;mQ0@yc{lRkUs& z2|>;#)O)!3PePqfypTt{3y|W}skQ7>*o(My>KaTS@DVj5j+P9$u~{yqk>&D%$%QRK z5Va_9LjwQfxuh--MNr{3LHMpu z#e3ogAmE=QZ-V$4R21bqxwfxrO85F;b!cN~pMbi5K~-+APgQ3&W_}Y;z2yG@pEV(` literal 0 HcmV?d00001 diff --git a/scan/__pycache__/serializers.cpython-312.pyc b/scan/__pycache__/serializers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86108b0f59db377d8bc5fe00fab824580e0db8d6 GIT binary patch literal 747 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 E3yn&EvH$=8 literal 0 HcmV?d00001 diff --git a/scan/migrations/__pycache__/__init__.cpython-312.pyc b/scan/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ca640d13f0d515851ad98a3a83c34a535e031cb GIT binary patch literal 209 zcmX@j%ge<81dY6N(n0iN5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!DsZ-n2`x@7Dvl{G z&B!k)&d#jV%S(+3s7%gCEXoZi%FjwoE-8*Fs4U6I&x3MfQnC{B((`qTlN0lFlk-zj zi*-{{3v%)+W0De!K>QexOiXTOdQoCYW`16AOniK1US>&ryk0@&FAkgB{FKt1RJ$Tp VpaU3zxERFv$jr#dSi}ru0RXKXJVXEh literal 0 HcmV?d00001 diff --git a/scan/models.py b/scan/models.py new file mode 100644 index 0000000..bf63b27 --- /dev/null +++ b/scan/models.py @@ -0,0 +1,15 @@ +from django.db import models + +class ScannedCode(models.Model): + code = models.CharField(max_length=255, unique=True) + scanned_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.code + +class MessagesAlert(models.Model): + type_message = models.CharField(max_length=255, unique=True) + product_line = models.CharField(max_length=255, unique=True) + code_lexer = models.IntegerField() + time = models.DateTimeField(auto_now_add=True) + diff --git a/scan/serializers.py b/scan/serializers.py new file mode 100644 index 0000000..a78fe1e --- /dev/null +++ b/scan/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import ScannedCode + +class ScannedCodeSerializer(serializers.ModelSerializer): + class Meta: + model = ScannedCode + fields = "__all__" diff --git a/scan/tasks.py b/scan/tasks.py new file mode 100644 index 0000000..7493860 --- /dev/null +++ b/scan/tasks.py @@ -0,0 +1,13 @@ +from celery import shared_task +import requests + +@shared_task +def fetch_api_data(url, token): + """Запрос к API в фоновом режиме""" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + try: + response = requests.post(url, headers=headers, timeout=10) + if response.ok: + return response.json() + except requests.RequestException: + return None diff --git a/scan/tests.py b/scan/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/scan/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/scan/urls.py b/scan/urls.py new file mode 100644 index 0000000..e258af4 --- /dev/null +++ b/scan/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +# from .views import ScanAPIView, ScanAspuAPIView, GetManagementAPIView +from .views import GetManagementAPIView + + +urlpatterns = [ + # path('api/scan/', ScanAPIView.as_view(), name='scan'), + # path('api/scan_aspu/', ScanAspuAPIView.as_view(), name='scan_aspu'), + path('api/get_management/', GetManagementAPIView.as_view(), name='get_management'), +] diff --git a/scan/utils.py b/scan/utils.py new file mode 100644 index 0000000..f3ed7af --- /dev/null +++ b/scan/utils.py @@ -0,0 +1,83 @@ +import requests + +import logging + +from rest_framework.response import Response + +from .constants import * + +def get_new_token(url): + +"""Function to get a new token.""" + +payload = {"UserName": USERNAME, "Password": PASSWORD} + +try: + +response = requests.post(url, json=payload) + +response_data = response.json() + +if response.status_code == 200 and response_data.get("IsSuccess"): + +return response_data["Value"]["Token"] + +else: + +return None + +except requests.RequestException as e: + +logging.error(f"Network error while getting token: {str(e)}") + +return None + +def request_to_api(url, token, payload=None, timeout=10): + +"""General function to make API requests.""" + +if payload is None: + +payload = {} + +headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + +try: + +response = requests.post(url, json=payload, headers=headers, timeout=timeout) + +if response.ok: + +return response.json() + +else: + +logging.error(f"Error from API ({url}): {response.status_code} - {response.text}") + +return None + +except requests.Timeout: + +logging.error(f"Timeout while requesting {url}") + +return None + +except requests.RequestException as e: + +logging.error(f"Request error to {url}: {str(e)}") + +return None + +def create_response(status, message=None, data=None, status_code=200): + +"""Function to create consistent API responses.""" + +return Response({ + +"status": status, + +"message": message, + +"data": data + +}, status=status_code) \ No newline at end of file diff --git a/scan/views.py b/scan/views.py new file mode 100644 index 0000000..1671c76 --- /dev/null +++ b/scan/views.py @@ -0,0 +1,215 @@ +import requests +import logging +import time +from concurrent.futures import ThreadPoolExecutor, as_completed +from rest_framework.response import Response +from rest_framework.views import APIView +from collections import defaultdict + + + +from .models import ScannedCode +from .serializers import ScannedCodeSerializer + +# Константы API URL и креденшелы +LOGIN_URL_ASPU = "http://192.168.254.10:3428/api/login" +API_URL_ASPU_MANAGEMENT = "http://192.168.254.10:3428/api/Management/" +API_URL_MANAGEMENT_DEVIN = "http://192.168.254.20:3428/api/Management/" +API_URL_MANAGEMENT_JR = "http://192.168.254.30:3428/api/Management/" +API_URL_MANAGEMENT_5L = "http://192.168.254.40:3428/api/Management/" +API_URL_MANAGEMENT_19L = "http://192.168.254.50:3428/api/Management/" + +API_URL_ASPU_MASSAGE_LIST_SIPA = "http://192.168.254.10:3428/api/Messages/" +API_URL_ASPU_MASSAGE_LIST_DEVIN = "http://192.168.254.20:3428/api/Messages/" +API_URL_ASPU_MASSAGE_LIST_JR = "http://192.168.254.30:3428/api/Messages/" +API_URL_ASPU_MASSAGE_LIST_5L = "http://192.168.254.40:3428/api/Messages/" +API_URL_ASPU_MASSAGE_LIST_19L = "http://192.168.254.50:3428/api/Messages/" + +USERNAME = "superuser" +PASSWORD = "Superuser1105" + +# Глобальные переменные для хранения токена +token_cache = {"token": None, "timestamp": 0} +TOKEN_LIFETIME = 1440 * 60 # 24 часа в секундах + + +def get_new_token(url): + """Получение нового токена с кешированием.""" + global token_cache + current_time = time.time() + + if token_cache["token"] and (current_time - token_cache["timestamp"] < TOKEN_LIFETIME): + return token_cache["token"] + + payload = {"UserName": USERNAME, "Password": PASSWORD} + try: + response = requests.post(url, json=payload, timeout=5) + response_data = response.json() + + if response.status_code == 200 and response_data.get("IsSuccess"): + token_cache["token"] = response_data["Value"]["Token"] + token_cache["timestamp"] = current_time + return token_cache["token"] + + logging.error(f"Ошибка получения токена: {response_data}") + return None + except requests.RequestException as e: + logging.error(f"Ошибка сети при получении токена: {str(e)}") + return None + + +def request_to_api(url, token, payload=None, timeout=10): + """Отправка запроса к API.""" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + try: + response = requests.post(url, json=payload or {}, headers=headers, timeout=timeout) + response.raise_for_status() + return response.json() + except requests.Timeout: + logging.error(f"Тайм-аут при запросе {url}") + except requests.RequestException as e: + logging.error(f"Ошибка запроса {url}: {str(e)}") + return None + + +def extract_product_id(text): + """Функция извлечения идентификатора продукта из текста.""" + import re + match = re.search(r'Product\s*ID:\s*(\d+)', text) + return match.group(1) if match else None + + +def create_response(status, message=None, data=None, status_code=200): + """Создание стандартного ответа API.""" + return Response({ + "status": status, + "message": message, + "data": data + }, status=status_code) + + +class GetManagementAPIView(APIView): + """Получение данных управления и сообщений с различных источников.""" + + def get(self, request): + token = get_new_token(LOGIN_URL_ASPU) + if not token: + return create_response("Error", message="Failed to get token", status_code=500) + + management_urls = { + "Sipa": API_URL_ASPU_MANAGEMENT, + "Devin": API_URL_MANAGEMENT_DEVIN, + "JR": API_URL_MANAGEMENT_JR, + "5L": API_URL_MANAGEMENT_5L, + "19L": API_URL_MANAGEMENT_19L + } + + message_urls = { + "Sipa": API_URL_ASPU_MASSAGE_LIST_SIPA, + "Devin": API_URL_ASPU_MASSAGE_LIST_DEVIN, + "JR": API_URL_ASPU_MASSAGE_LIST_JR, + "5L": API_URL_ASPU_MASSAGE_LIST_5L, + "19L": API_URL_ASPU_MASSAGE_LIST_19L + } + + message_payload = { + "skip": 1, + "take": 50, + "includes": [], + "sort": [{"field": "Time", "dir": "desc"}], + "filter": {"filters": [], "logic": "and"} + } + + # Используем defaultdict, чтобы избежать KeyError + all_data = defaultdict(lambda: { + "Name": "", + "ShortName": "", + "BatchName": "", + "Stats": [], + "Sources": [], + "Gtin": "", + "GroupType": "", + "Quantity": 0, + "Messages": [] + }) + + def process_management_response(key, response_data): + """Обработка данных управления.""" + if not response_data or "Value" not in response_data: + logging.error(f"Некорректный ответ от {key}: {response_data}") + return + + value_data = response_data["Value"] + batch_name = value_data.get("BatchName", "") + + for product in value_data.get("ProductTypes", []): + product_id = product.get("Id") + if not product_id: + continue + + all_data[product_id].update({ + "Name": product.get("Name", ""), + "ShortName": product.get("ShortName", ""), + "BatchName": batch_name, + "Gtin": product.get("Gtin", ""), + "GroupType": product.get("GroupType", ""), + }) + + all_data[product_id]["Stats"].extend(product.get("Stats", [])) + all_data[product_id]["Sources"].append(key) + + if product.get("GroupType") == "Pallet": + all_data[product_id]["Quantity"] = sum( + stat["Value"] for stat in product.get("Stats", []) if stat.get("Type") == "Validated" + ) + + def process_message_response(key, response_data): + """Обработка сообщений.""" + if not response_data or "Value" not in response_data: + logging.error(f"Некорректный ответ от {key}: {response_data}") + return + + for msg in response_data["Value"]: + if "Text" not in msg: + continue + + message = { + "Id": msg["Id"], + "Time": msg["Time"], + "Type": msg["Type"], + "Text": msg["Text"] + } + + # Добавляем в общий список сообщений + for product_id in all_data: + all_data[product_id]["Messages"].append(message) + + with ThreadPoolExecutor(max_workers=10) as executor: + futures = { + executor.submit(request_to_api, url, token, timeout=2): (key, "management") + for key, url in management_urls.items() + } + futures.update({ + executor.submit(request_to_api, url, token, payload=message_payload, timeout=2): (key, "messages") + for key, url in message_urls.items() + }) + + for future in as_completed(futures): + key, request_type = futures[future] + try: + response_data = future.result() + + if request_type == "management": + process_management_response(key, response_data) + elif request_type == "messages": + process_message_response(key, response_data) + + except Exception as e: + logging.error(f"Ошибка обработки {key}: {str(e)}") + + logging.info(f"Отправляем ответ с {len(all_data)} объектами управления") + + return create_response("OK", data=dict(all_data)) if all_data else create_response( + "Error", message="No data found", status_code=404 + ) \ No newline at end of file diff --git a/scan/viewss1/__init__.py b/scan/viewss1/__init__.py new file mode 100644 index 0000000..80873f5 --- /dev/null +++ b/scan/viewss1/__init__.py @@ -0,0 +1,3 @@ +# views/__init__.py +from .scan_views import StorageScanView, AspuScanView +from .management_views import ManagementDataView \ No newline at end of file diff --git a/scan/viewss1/management_views.py b/scan/viewss1/management_views.py new file mode 100644 index 0000000..7b02abd --- /dev/null +++ b/scan/viewss1/management_views.py @@ -0,0 +1,88 @@ +# views/management_views.py +from rest_framework.views import APIView +from concurrent.futures import ThreadPoolExecutor, as_completed +import logging +from ..api_client import APIClient +from ..constants import API_URLS + +logger = logging.getLogger(__name__) + +class ManagementDataView(APIView): + """Получение управленческих данных""" + client = APIClient() + response_handler = APIClient.create_response + MAX_WORKERS = 5 + REQUEST_TIMEOUT = 5 + + def get(self, request): + token = self.client.get_token(API_URLS["LOGIN_ASPU"]) + if not token: + return self.response_handler( + "Error", + "Failed to get token", + status_code=500 + ) + + all_data = {} + with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor: + futures = { + executor.submit( + self.client.make_request, + url, + token, + timeout=self.REQUEST_TIMEOUT + ): name + for name, url in API_URLS["MANAGEMENT_ENDPOINTS"].items() + } + + for future in as_completed(futures): + name = futures[future] + try: + self.process_response( + name, + future.result(), + all_data + ) + except Exception as e: + logger.error(f"Error processing {name}: {str(e)}") + + return self.response_handler( + "OK" if all_data else "Error", + data=all_data or None, + status_code=200 if all_data else 404 + ) + + def process_response(self, source_name, response, data_store): + """Обработка полученных данных""" + if not response or not response.get("Value"): + return + + value_data = response["Value"] + batch_name = value_data.get("BatchName") + + for product in value_data.get("ProductTypes", []): + product_id = product.get("Id") + if not product_id: + continue + + if product_id not in data_store: + data_store[product_id] = { + "Name": product.get("Name"), + "ShortName": product.get("ShortName"), + "BatchName": batch_name, + "Stats": [], + "Sources": [], + "Gtin": product.get("Gtin"), + "GroupType": product.get("GroupType", ""), + "Quantity": 0 + } + + data_store[product_id]["Stats"].extend(product.get("Stats", [])) + data_store[product_id]["Sources"].append(source_name) + + if product.get("GroupType") == "Pallet": + data_store[product_id]["Quantity"] = sum( + stat["Value"] + for stat in product.get("Stats", []) + if stat["Type"] == "Validated" + ) diff --git a/scan/viewss1/scan_views.py b/scan/viewss1/scan_views.py new file mode 100644 index 0000000..4dbb42b --- /dev/null +++ b/scan/viewss1/scan_views.py @@ -0,0 +1,77 @@ +# views/scan_views.py +from rest_framework.views import APIView +from ..api_client import APIClient +from ..constants import API_URLS + + +class BaseScanView(APIView): + """Базовый класс для сканирования""" + client = APIClient() + response_handler = APIClient.create_response + + def validate_code(self, code): + if not code: + return self.response_handler( + "Error", + "Code not found", + status_code=400 + ) + return None + + +class StorageScanView(BaseScanView): + """Сканирование кодов хранилища""" + + def post(self, request): + raw_code = request.data.get("code") + if error := self.validate_code(raw_code): + return error + + formatted_code = raw_code.replace("\u001d", chr(29)) + token = self.client.get_token(API_URLS["LOGIN"]) + + if not token: + return self.response_handler( + "Error", + "Failed to get token", + status_code=500 + ) + + response_data = self.client.make_request( + API_URLS["STORAGE_INFO"], + token, + {"IdentificationCode": formatted_code} + ) + + if response_data and response_data.get("Value"): + return self.response_handler("OK", data=response_data) + return self.response_handler("Error", "Data not found", status_code=404) + + +class AspuScanView(BaseScanView): + """Сканирование ASPU кодов""" + + def post(self, request): + raw_code = request.data.get("code") + if error := self.validate_code(raw_code): + return error + + formatted_code = raw_code.replace("\x1d", "\\u001d") + token = self.client.get_token(API_URLS["LOGIN_ASPU"]) + + if not token: + return self.response_handler( + "Error", + "Failed to get token", + status_code=500 + ) + + response_data = self.client.make_request( + API_URLS["ASPU_CODE_INFO"], + token, + {"code": formatted_code} + ) + + if response_data and response_data.get("Value"): + return self.response_handler("OK", data=response_data) + return self.response_handler("Error", "Data not found", status_code=404) \ No newline at end of file diff --git a/users/__init__.py b/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/users/__pycache__/__init__.cpython-312.pyc b/users/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d546f4ea4d78c235f183c74ba203619cb0da51b GIT binary patch literal 199 zcmX@j%ge<81P1qxr-SInAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdmF8>}6Iz^FR2)-W znvq{poSj*zmzNq7P??;OSd<%3l%JKFTv8lUP+5|Zp9kf}q+})LrRVDwCnx6VCg-Q5 z7VD;@7Ubkt#v~;cf%q|{U=!oxGxIV_;^XxSDt~d<!4S1ma>4<0CU8 KBV!RWkOcsf5jfcZ literal 0 HcmV?d00001 diff --git a/users/__pycache__/admin.cpython-312.pyc b/users/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4fabe5292a043a22b0a35aad31f91cb0d516602 GIT binary patch literal 243 zcmX@j%ge<81P1qxr#k`Z#~=<2FhLog1%Qm{3@HpLj5!Rsj8Tk?43$ip%r8OGnvAzt z6H{_C^ZYcKZtm}#sl@w(r6)^)9tYr8MQuiy_*(xTqIJKxarnodCzoa-j zvr;cFH71}kIU}(sH=rm#D>b>KIHsVoBqKi$%8h}Tr(2wyn5UbZpORXvo03|PlV2H= xlvo7f$CQFi1Uo>lpz;@oO>TZlX-=wL5eLvtMj$Q*F+MOeGBVy{P$*&pasWEmMX3M) literal 0 HcmV?d00001 diff --git a/users/__pycache__/apps.cpython-312.pyc b/users/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e380e043229ced9cd71719ee1912cd6007739003 GIT binary patch literal 503 zcmXv~Jxjzu5Z%p(ocAGeieO=3Bc!-sDI&soqTO+gO;}j=vU|B`l8xD>89iq@52iKvS2_UJWf6g|q~fahpT62(zDBO-Au>^M)ilhpx)o3YEeQiB^r1YT?slQ&M$fq3-*wLkQo&{?D2Q Mw;rrtAVXR82dLzS`2YX_ literal 0 HcmV?d00001 diff --git a/users/__pycache__/models.cpython-312.pyc b/users/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7bf56005064b7e9b4c2e775378c580cd03e7a74f GIT binary patch literal 240 zcmX@j%ge<81P1qxr`rJO#~=<2FhLog1%Qm{3@HpLj5!Rsj8Tk?43$ip%r8OGnvA#D za`RJCbBg^mnQn2WWF_XM=j){;6)^)PS2BDCsri-SY!wq)oLW>IQ(T&nUs9Z%S*e$o z8WT{NoRL_R8&H&=m6}{q98*wPl98VW<;Fma(k)I-%+pN<+EA>Ul3I|HUm25>SOnt7 rl!8r!Sgu!4`HRB_$S%!EwJYKPTFMB-#URE9W=2NFdkiu~Y(NeGJWoVt literal 0 HcmV?d00001 diff --git a/users/__pycache__/serializers.cpython-312.pyc b/users/__pycache__/serializers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d34199b6096fb859cda178407bc88021adddfc78 GIT binary patch literal 1438 zcmZ`(Pi)&%7=N~35;tq+k&2B5HX>10nJL~5Ody01(gf^)rm_|eRwU1}UmK^69o}=O zGE%8RJye1lhZP9~$D!^pfhG-A8@uhm#Z-p6;^S7%%t=T_ z?;#hjBR5B+=44iAEVtkcL1v+w*9(6NRhN=Oj8M<^y(@$=F!6_AgPgob7Aawkg>ab! zj)=`@R0R!8_;M8yWJ)eFWf>8rsb)cAxiwPF-2X+m(sg-DO2f>&tE>xzSkEO>0?Yg1 zk{8S$U|axf#TDvB#0mqy>&WAikm#8}H+TpPdL}`ipl7SH=uJ=VNv<*t9*|hb>$$B_ z_NvlT*t1dAbJWa(`w4RM8OG0~Xd-hqhZD@KRQ8lCauxTKr%fW#vWy;X9?A5v%<>

y$M#cCI+TfX(42QY;=ALupwB^-?-3Qx zf*upBkTLNTm=0_m{i@AueA?G$Vr^zeJGplLG5ZF-ubo{xKftA2F|8sB2waEZFA`ac z6tKZEE~gkOPuUv-Kog%)42j8$|IM-?m!S7ZgF661modul*QE;Vxj|SV#>9-1Mk{Pd#5=zHz(u9-(Ya? zzi@ETXku~+E>3J1HYQtv_=eZ-*O#x~>r1s-2CDA9p8cUZzcsANT8i1J5<@@$fgy-s z2SPv$VunU!c1#EiKp`opZuUzn>;E}gN@6|QBXa>h>{wZ`QJQFMz`PLSB$&VjRL>+8 z$K3C+^B%n(*rAqtgL0~_-9OL{h9i2?u=5J(W9IW_Dncpr^SO1UY*Ogu^CFfP8uN;p ztB`~WCLh2|&A_~Sw2yR_L^2I{A}@PclTp;pya0=+EqI@Li9~Uh1e`4qE#h41Vdi?A zQ0WpHhde`FEHop%rbK$3W}MAS2N5S}NLyk(!~> bAAUOZH>dvY>^#*z3azPWY#KKev91iiPp5S| literal 0 HcmV?d00001 diff --git a/users/__pycache__/views.cpython-312.pyc b/users/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e0cb997c8c2e1ae2b98de540f9b0ca8479c9f78 GIT binary patch literal 1132 zcmZ8g%}*0S6rb7MwoA*00V*UAtOpI7NJEHR)R3SO6s43e51S^NwL6rSZMV#9Atji2 z;Na04{0Wkhc=71ngBO}Kso8)BJrOS^l?x~5?T0PS;l1B`@0Z`aeQ)+_Pfrx_GyDBZ z?iWVrCj`L>`JlDH!67n`flXAzD#m~XQz(k6=wi{7iXk<`v1EkIa517rFcQ!_GQtPQ zh!9*8JuOwP%1YM-1PXQ{v+3ur@?@7od4;gDRbu2AJ7H$)c7!I@BvcAs-TEY&p!#cE-Scn&q&-sbFClT93ndq zL;JYU$@AFg1lN4V8)0841S9gR;A%dmckD=z=1e)jZEw4yhq%z)&Iag2sQLG=qH9XM z0i*3Zly0=M0qRsz#1tQ?Wazd|p?Rf?LT3~jaO6DGcKCn|3R7`sC~sZ}6Zn`O#Xd($fUVa6!vrJOavvU+JE zYZ-)17^G}k)y!+1LY%3%iaUJSELpBPQrTkmUjMaMkc1p;h^4rBi_YTXA95efW3v%| z_%8Y_7H`JJ>anp#EO9n4a0cWnpU)oiV3(|)3z(R=~d`W&OJtSh&vhw1JA-W zFmMLzIY2Ez3N2xe1of2CF@ca2o1l|?+3*BuD AwEzGB literal 0 HcmV?d00001 diff --git a/users/admin.py b/users/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/users/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/users/apps.py b/users/apps.py new file mode 100644 index 0000000..72b1401 --- /dev/null +++ b/users/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'users' diff --git a/users/migrations/__init__.py b/users/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/users/migrations/__pycache__/__init__.cpython-312.pyc b/users/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfad1306f54ef5f298f7e81ab7cf7ff9bb3090f2 GIT binary patch literal 210 zcmX@j%ge<81P1qxr-SInAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdRp@LL6Iz^FR2)-W znvq{poSj*zmzNq7P??;OSd<%3l%JKFTv8lUP+5|Zp9kf}q+})LrRVDwCnx6VCg-Q5 z7VD;@7Ubkt#v~;cf%q|{U=wpQ(~A;IGV}9_W8&j8^D;}~