From 8ec69c5d16665930d88fe48c5e0f68ad55f710d6 Mon Sep 17 00:00:00 2001 From: The unwasted guests Date: Tue, 28 Jan 2025 21:43:26 +1000 Subject: [PATCH] back-end 1.0 --- backend/__init__.py | 0 backend/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 140 bytes backend/__pycache__/settings.cpython-312.pyc | Bin 0 -> 3060 bytes backend/__pycache__/urls.cpython-312.pyc | Bin 0 -> 1089 bytes backend/__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 628 bytes backend/asgi.py | 16 ++ backend/settings.py | 145 ++++++++++++++++++ backend/urls.py | 25 +++ backend/wsgi.py | 16 ++ manage.py | 22 +++ orthanc/__init__.py | 0 orthanc/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 140 bytes orthanc/__pycache__/admin.cpython-312.pyc | Bin 0 -> 184 bytes orthanc/__pycache__/apps.cpython-312.pyc | Bin 0 -> 448 bytes orthanc/__pycache__/models.cpython-312.pyc | Bin 0 -> 3212 bytes .../__pycache__/serializers.cpython-312.pyc | Bin 0 -> 677 bytes orthanc/__pycache__/tasks.cpython-312.pyc | Bin 0 -> 5078 bytes orthanc/__pycache__/urls.cpython-312.pyc | Bin 0 -> 923 bytes orthanc/__pycache__/views.cpython-312.pyc | Bin 0 -> 2352 bytes orthanc/admin.py | 3 + orthanc/apps.py | 6 + orthanc/migrations/0001_initial.py | 27 ++++ orthanc/migrations/0002_series_instance.py | 39 +++++ orthanc/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-312.pyc | Bin 0 -> 1425 bytes .../0002_series_instance.cpython-312.pyc | Bin 0 -> 2464 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 151 bytes orthanc/models.py | 36 +++++ orthanc/serializers.py | 7 + orthanc/tasks.py | 108 +++++++++++++ orthanc/tests.py | 3 + orthanc/urls.py | 12 ++ orthanc/views.py | 76 +++++++++ 33 files changed, 541 insertions(+) create mode 100644 backend/__init__.py create mode 100644 backend/__pycache__/__init__.cpython-312.pyc create mode 100644 backend/__pycache__/settings.cpython-312.pyc create mode 100644 backend/__pycache__/urls.cpython-312.pyc create mode 100644 backend/__pycache__/wsgi.cpython-312.pyc create mode 100644 backend/asgi.py create mode 100644 backend/settings.py create mode 100644 backend/urls.py create mode 100644 backend/wsgi.py create mode 100644 manage.py create mode 100644 orthanc/__init__.py create mode 100644 orthanc/__pycache__/__init__.cpython-312.pyc create mode 100644 orthanc/__pycache__/admin.cpython-312.pyc create mode 100644 orthanc/__pycache__/apps.cpython-312.pyc create mode 100644 orthanc/__pycache__/models.cpython-312.pyc create mode 100644 orthanc/__pycache__/serializers.cpython-312.pyc create mode 100644 orthanc/__pycache__/tasks.cpython-312.pyc create mode 100644 orthanc/__pycache__/urls.cpython-312.pyc create mode 100644 orthanc/__pycache__/views.cpython-312.pyc create mode 100644 orthanc/admin.py create mode 100644 orthanc/apps.py create mode 100644 orthanc/migrations/0001_initial.py create mode 100644 orthanc/migrations/0002_series_instance.py create mode 100644 orthanc/migrations/__init__.py create mode 100644 orthanc/migrations/__pycache__/0001_initial.cpython-312.pyc create mode 100644 orthanc/migrations/__pycache__/0002_series_instance.cpython-312.pyc create mode 100644 orthanc/migrations/__pycache__/__init__.cpython-312.pyc create mode 100644 orthanc/models.py create mode 100644 orthanc/serializers.py create mode 100644 orthanc/tasks.py create mode 100644 orthanc/tests.py create mode 100644 orthanc/urls.py create mode 100644 orthanc/views.py diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/__pycache__/__init__.cpython-312.pyc b/backend/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db1e5a6f6b3ee5ed12c7cf2cdf79c3c96d76829f GIT binary patch literal 140 zcmX@j%ge<81o_LRri19mAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdrR`!Blb2eqTTqmr zm6}{q9FvrooSmAN0;l8SGxIV_;^XxSDsOSv$on0&y{j@sXL4k+Fyw G$N~T~+#q}a literal 0 HcmV?d00001 diff --git a/backend/__pycache__/settings.cpython-312.pyc b/backend/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed256461903034537c327c27b6815e6af47763ca GIT binary patch literal 3060 zcmb7G%X8bt83#y;Z&H*@$&wr^aAaAg6M>dru``KVhy*3p6v+^f)nGEkK-?7t84m^k zEej|1+)HoG!Ka*(N&b^w>}h7K?ny~czERF(I@41>fTYFHo~g@`7TEpXzx{r@_IwKjY5<}AsPiVS`SD8nhH=jfP>$I%=;b0`p?nv z`@!VEk1;9%{kN=1VHA`iC?rK&loV@GXyO}Mio-Ju&jgA{NdyuULY^%C4Xx5B@if$; zr4&j^X_P`~G>I~33QePPDEl<5($XZFL9?JYgU?+Mi*g#C%G7oUmVdL=)Y+y@qFk*I(m=)yPtSU%X z4aRm9%Nb~{FdZAbFaxKz*){eySeL-3Zd#a84REh3j%peV{5kt71XMNr5yZ#weaC6r zpXBq%Y}#x;#xR4Xspqkg2ZwpbY^zN>KTJ5^ckk?DX0M}ZjHcQSWA2?5q(C7cRW0VR zqIIzS|HQKJ-bkRAUU%eZ5~?fCzPmm^K{agL>{xi!y{pUk>f_J;w47VpTm5Xm)zsF| z$I6Xmt0gZTUbZf)t>q`n+TD&KHxI5lcJ5HjMcr)Q4cjyg$5MA$A5{0;_-Y3tUKm^S zOt9fNM{R7o=f{LLwrzmKw6xhXlNAy_UJR#-j}KVn^(S{@RFD?#&?30=j9i1^@9s zRBdaIEx_5~ z=L!$_YLSGB<%U3_T)i&)Z;@JEEZ3@nJ2wRE&~B-$(ryFF{uYu5!h1?bbKHxA*l70* zzqV;REok(wG!lAZDMutaTyaxtH*Q_Oe&^nuo7dMLT_Q2fY%1D5=#$XxTQ_gy!X(UB zH_BC>gsR*YjN1a=AkjJ}2s^a~q`FxXL^vpF4bhr{Q^VaHtKDzCwqn~rFC@dsq#{3K zcH71l=Xgg!$H9WCtD535>S15QwA}me>S;?gRK25Du+eh%&+LEquJ(PV)dxAAIr#m% zI;eJZY^lvRJxpL@wPTauwpef@aDM>f@}oPXn+R(*yRNo?k!DH78p_e`z2BZmvaJFk z@WG+OdSIUMmi@zy2zDbYV0`Vj#%vo~y4pV>*|I&Nj!`Z^7K?m|+pdT*w=Hh+Rk2*) zyiSt~71&^2APYlzoo{TF1z|)6_e0E4bqp0a%FD(6jS+#161LNR>3xWJHwaRWYWae*&1cu{`9e?caS z{QC9=NpY1*ZHF()UNZ|MU9JiuEWrX$P$2QGavg>oXB}$1v2MXF(i%rkgJqISOb!$DqMAAo7>>!LaoSyLjXgT zYQVQBFikG6-K~CH;l_aHnp`ULl_E(4fTCP#08fFN4@eZ4hP^Ga z<~?-cLK%|fW$2pks9#`(`p<{OPrfwp;p*V)!~Sw`zPx@8KR(zt92%cOyLL2u-?|UB z-Ui$EVC)5Hnm!H0>Cmrp6g__$rjnVzM*njEMdH%)#HD{EmR=;TJx^SFkyv}4SnEc6 zX)4{IyYXI_ilw>}CzQy{89q6WC%|_D!kYuUMVmH}~Qdyop&P*R?=T4H$D{5jX z-3vyt@zYpxBHRn6Q-N-*$52`J8I=LA6Y1mWwd2g}ac1E-yL6nn0Pm-%NCs46zUl{5 zZ1Pz~>IDN}9`z^n(aGndlSY3slX@@kjGF6Rp{C}#sgvYXH+GVk>_$%@_i!&srO!jY zaccJVGitgQ3ao^C;lNV(G#2sd`87+?@Bei1%D2{!7jOJ?apl|3e_YHT&tE=`gwlaO K$E{BR!v6yF;Pw&# literal 0 HcmV?d00001 diff --git a/backend/__pycache__/urls.cpython-312.pyc b/backend/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f59f21f879def0876e2e07957b9c2cd08df3fdf3 GIT binary patch literal 1089 zcmb7DL2J}N6rN<0?lx^Lc+umCNE&GqD)bVnMbu(JJcwSF!ge+@yPGvL6J{pXmLhoT z(Vkj=g7l|&>@61&Pu^OJ7f;S)6T1k4&SCQM=DqKI-+MEk7ZyCA*ZAYf@Sp|2H}li1 zc5Pf9XyX`Q&;{7Sc4kq#Yg@){Wi9G-9qM*n3)p}-*dg|Kz5%%Uj;~$Wjdf~ItSZmd zyNhQ|t%Yup$jAgoy1b0MCvOqeSTgoD3zk%rtFjg7Y6u@< z3=xAV&ACu9gag4Tqi0#~TGkz?4Frs>P0$e1EytHR6;kLxw21?;G0> zyhj<5a;2|@;VIO=vEXV%gdq}Ej2k*nHP-*IG4UaiJp-~7{K068C0T(}Hq=e1>KSQf z!cBj+s>VP~MUThClBEC+>Q=^+c1^|+nua$d>)7S0DgVs2pYdU&8`xCxjibW#gTVW4 zZf0PWF2Xcrfn7SL<7K;!-k+)b(Y}icHH!A<^*~36B~||OV76@cdiwVCdgZm6u-2(W z+86rSfpTt2g>bY-xW?_sRFTp#+RC}9d4B?z`WXE5AIIB_?5@<8QEu0d;rG{MhWTFE zS*IKq8F?TUb)I=;a$RrdEz7bl>_yA@0fBYv3%GMJ1Kioen;+JWX^<7nf|x&77Y a9NswcPr|e7!I^XSyzMO7@8`sV2KWVv@|6@|i{6!G9q$<33q&Bpc}=DjzM-=8C;Jr^Uu_Lnx=wB)Nm#HearXEsw64{!le~VE(b(OOU^YJasl^=2JlA4q{=U+Q;HYS~Q- F`Um6>w)_A9 literal 0 HcmV?d00001 diff --git a/backend/asgi.py b/backend/asgi.py new file mode 100644 index 0000000..ed01e6a --- /dev/null +++ b/backend/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for backend 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 + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') + +application = get_asgi_application() diff --git a/backend/settings.py b/backend/settings.py new file mode 100644 index 0000000..64c0af3 --- /dev/null +++ b/backend/settings.py @@ -0,0 +1,145 @@ +""" +Django settings for backend 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 + +# 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-z8m_e(v=^%)1f-=hgcl1d7a2%rg_#w$r$ig%x%l8ua_cj(ts)w' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', # Django Rest Framework + 'orthanc', # Наше приложение +] + +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', +] + +ROOT_URLCONF = 'backend.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 = 'backend.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'xray', + 'USER': 'postgres', + 'PASSWORD': '1240069630Bk!', + 'HOST': 'localhost', + 'PORT': '5432', + } +} + + +# 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' +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ), +} + +# Настройки токенов +from datetime import timedelta +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30), # Срок действия токена + 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), + 'AUTH_HEADER_TYPES': ('Bearer',), +} \ No newline at end of file diff --git a/backend/urls.py b/backend/urls.py new file mode 100644 index 0000000..fb36519 --- /dev/null +++ b/backend/urls.py @@ -0,0 +1,25 @@ +""" +URL configuration for backend 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, include + +urlpatterns = [ + path('admin/', admin.site.urls), + # path('patients/', include('orthanc.urls')), + path('auth/', include('orthanc.urls')), +] + diff --git a/backend/wsgi.py b/backend/wsgi.py new file mode 100644 index 0000000..07bda9d --- /dev/null +++ b/backend/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for backend 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', 'backend.settings') + +application = get_wsgi_application() diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..eb6431e --- /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', 'backend.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/orthanc/__init__.py b/orthanc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/orthanc/__pycache__/__init__.cpython-312.pyc b/orthanc/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53b16fb8651e4b2715b2a19b92df8eb8046ff7b4 GIT binary patch literal 140 zcmX@j%ge<81e2FdO$X7BK?FMZ%mNgd&QQsq$>_I|p@<2{`wUX^OWVaNCNH&Ix1cCL zD>b>KI3_7EIXg8kB__Y9BqK2|IVL_nGcU6wK3=b&@)n0pZhlH>PO4oID^M>Z5Ep|O NADI~$8H<>KEC5eyAjbdz literal 0 HcmV?d00001 diff --git a/orthanc/__pycache__/admin.cpython-312.pyc b/orthanc/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d276763a96b429827342ddc601d2f969084c62d4 GIT binary patch literal 184 zcmX@j%ge<81e2FdO?Lv)k3k$5V1hC}3ji6@8B!Qh7;_kM8KW2(87i4HnO}mWH5qTQ zCZ^YaTH8iMs^H+@qGT|$hzQl9xYRuaj%)53Thk=w2U0hoe}EuT{{lh$ zQ(PR&fs>P)5M7+S%f%bs``&%ud-v{pHO&TybN}?D&M+kR424|occ;7WhU|JbPNEU>vefMp?~;YotsZho z9H(|XNu)nzFa$_V5vDNT*n2b9=gszQH<33b-?MbytbQSz6X}?0Z%I@Nmj&YW1GV*G dvLZ@pRw|A9(h)-Mu=Ty5p|!iS9}ubR`U7{8agP81 literal 0 HcmV?d00001 diff --git a/orthanc/__pycache__/models.cpython-312.pyc b/orthanc/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3848d22960f5226a52159f931740b572c7c01bfd GIT binary patch literal 3212 zcmcJRPfXiZ9LMeaPe|eG&rS=d zha7sKnzVA6I!&6?V^BG8no?#&pJ(|3zY8nC|#z~14{3El1j~`w&@?;Syth@x%(4elq_9Y$b5SmI@(%xZInC$C5k^JLvg z@nsq=B`?c{drB6R2iurU+ZfZjgSNnh2Ek0jXQ(%P&6piKU0_}?Pnh}ot-a0?t2$W7 zw{a%rNUv~rl?O`hvC>(K3O;*q(F%}PgaxwB8ZMQ{Gb*mr`B!RaU{WEEPcPEkJek(i z$thv_0a3)s9M$FoWqQ&|nZCtk!^`ukMtOdPtGVv0O<>q6?xVm|xz-BTsdJsbaQ&t3 z{*teM2Lnx~!iJAG*XMa7!1LfWFB9kodH!KukTEizr?^+cKR74QVTs715vWDMOVGD> zA&E4Kx)ysvB}+!k9!yCPI4=qsG5j~jGil3_SJm>OL{uYU>lTP6fJ&erfv0Ol!EjhHd{qj0O*p876PX;10xsm-x1@lAKRJyUWes^Jr> zGnMcOJ$&Nj(59;#?k`-gw)L((sy(=god)GI>3WDVbYLo?akV=~y3a-XU6*FAV54 zNjYUDY*z;B7R1Xf2ol67P3IPPO_)^;=Pc1Eel@E)SUeygD`D#)8~G!sIS!_Vd{XC< z6|Pt3dP|>NC|w$T%cV=cw3(XeRE)NPlHxP89S!b)9!2vp7_3he>L*l}ytr%xhI5oi zv&t>9Y*J`=2d|C~ULCq-cx&v^4t%r^AN8o3pygE~_(d)!6G5?P_e&}`6Q*Qhw8(<0 z@%cq82qx+jOX)t^yK#sHzmfVqm?pIE=epebDKH)GKukOIaK}sU=G>c}pZk97D~G=V zSQ9;KGnGV-p6DqKZMn*cONC4|bbR$*C3IX59WTZ zTubY$g*#?PLo;FD^B6WUZg!;a={WPKj+jTaZ$yE-P^QT~j19k3n^b8JENQ}Oa{7O< z86MBvu>-j~GPDx0!L%poX^i7g4kNb4CNT-pW+Z-A8o2$I8!h=pYa~YBO(H#m>zYJh zu`)t7m}Y@9f+LDXW=ZXA_RmOgf~(RB9JFRPpNErq4^2)|M6$#~fwAi2q4a%@3!=e7 zpneUe34QTXYr~cJDLsDb<;>QF(%6@kvHSYi{c>C=jP8dhzBy5j3;;%vP6Vq{k8~CX zH^tZ8<;ZoIJq*l53Smv@iBwVBI#*6yDP$10bPYF*E}iliesNwt$& z>uBWF0Z%=7D@{wmQ{T%ZO~pBU^Sw9keecJ7sa7Wd&%3Mh-CMlBlQBBsko4D) zTz~?_40Kq+7$~lU;T@3(1{^j(1t*|F&GyQpzh?%S8+O|{EKGrsW zVBS~ykQQy76)m6OF&G;C1tb@sAz=yhPP@kuE^AEw5?SX!V9 z@lhKW?XvNd$rR|s2iU@K%$h^>y?cAD5AYKB;81hn3v+#k-v`cFXc zffPfdzO*_o(to;WFjnqooZ6%r#~b(ZPfNP;ieJ;Jokaoqhx{$V55X9_ ahsArS{|W@FUsk@&Eq$F^`VD9d8u<%~B$>hh literal 0 HcmV?d00001 diff --git a/orthanc/__pycache__/tasks.cpython-312.pyc b/orthanc/__pycache__/tasks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a633f307f91d3f2490d8c5efeaa129d838f710c GIT binary patch literal 5078 zcmd^DTTC1275>K_-)%5A8*{T0h=UdWZ4oGaQ;ZljRq*mH4@|5a00Vq?5`3m zFMJllF)lXyBY9syFPZL#1dfdfl5w2H9Lq~)m@kB4VfIn^oFFXrw6wH+*xuaM+1=dU z+}YaG-FmfMvOK_?z|O^aA*`%gkp{RC`1XGfVgm_EFUUUvh~Md!k)VQ$BD6NBDM=(4 z)rMeJwHe(@-3y)4TBedz#HNnq?=NyHg<#x~?^iW-d>sDq=p#$ZqVk{l=afW;62jbb zljOWN_UUl{=wNW5fBZ&pV(gA&#tLA(q@QJl{Dg_|8)KvWcW>l-?)Q(6e|B$dNOIy3 z$FsqiI1U1aLV}m*2p^9Hbdo-Sqr@V=7zcOocX)b43K!^KLW9e_8iqa_L?W$j}^GcWH0dR`SjDude^DZ-=hf2MZ{i3qPVG z&5wV8#!){e!JFUdm#K4nNuBFUT~c?BFLmep(yIBg1(Q9E?I0kmtvLjP9D_?qe;Ej# zgrCpn!!Ec>WrrXQGOQ5dqCBpIF?sR;C>pYnR7J|ZW9|5#(Z%Ip=KmF4OA9X~7+{6N z;F?MA(izZ}D9|d?RA6fWh${H;hk)f$=FLYt*7lzlu;vzCX4}KKT9nQ+fGy1dD?hA- z>;*uz_878rn@gMXKRaY?EnF^t9SK^S+J6STE(Km~rW(3+0B*xEfag<lU;mj#ODJF+l^l0NTx^I7#}_^g&GM2ZS51u! zNw%Ci8d_i_YpyZQK9rm}eSm``KNJ#J$uzF8tYo|snr5SXz$jT4l@bMlRkEvkjF_^i zEgozYtBq-5EXc^#a-1zW)xiZwZ7>viuEQ1JZOP2AGoeIO-~%SPkdSj8CRtxH#i!*; zL~LeD`4NFsGtER%|fu*8vzq4H6mi(b7z?8#SB z4=j;;lM=lUxw=tIoDk4sl9d(L+7#EysU<+mp29sa3O<+9J@F{^K?hHmM)Mx-f|e|X z$yg%@@L5sa=I806=G9NKW!{Z1*T38xPEEfqYg@DInM)pzJbCcSd>PPiwI2t<1_op* z>^?$;2Z0bY=uSFFL`-1g$dyDYK@r+Ji4GYZcaUCAAWY@}*_~*CW(+FSr@X_cI+HO0 zJ(<Up%HEW$cS5`{ zA#Ziq&#-&RBg+0(oL{&A1Q%oo!vzIGamL)q#=`OBkONjETNI206~e$DkT+S>LnLq+ zCgq77Zn%SJokYlFqU?J$+rmsA8IohpmMev18Zjyj+)IXQK^$ceOin?v7$u|YAP8^x zH#E~gw~RcF>+ll3+{TY0*YTG`g6y@OfTg^l=n zd~+`CYRP))o_U{oQx#hiX-{vq(oeW1buC@l4O_q13R~aZ30vQ7{LQ`9n`^gV=a<)R ztgNqW{u=hZyKBw1XD)j@zuEc9+yIeY-hDjM$$3SN^uK{}+Lr%Gk$zf)YvsZz(Rm={ zpBUfzb9acA=o!PehL=V)zA2I}O{0?eCKqMLxks#`O(fuWCWoeDB*w50M`Gg&{TL*N z8j5mzpnU$} zF)42_`8G>Q0Ykvqh+hEl zR!>pX52*03sN)}~NkmO=jfk>jP?3m=GRPw$PX;xJs9~qEe-{nBDQVh4PQXx;?~OE7 b`M%&JQA#zuZ`M=w@9j>iao=yF6tw;WclIl& literal 0 HcmV?d00001 diff --git a/orthanc/__pycache__/urls.cpython-312.pyc b/orthanc/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9b8307209f78c2c719799fbbc5a1109814e5b97 GIT binary patch literal 923 zcmaKq&ubGw6vtKsCZa|VVj*Mo9>S=yEP=@ z!CQ{rD*gpZJ@n}R;AKMs!2#*PQ*V{tJozR+EcB2$ym{~Y-e>sk%>Gc6ID(b={Hgga zg3xbPELLouoZsOPIz%QiIU6~g&T+_$n7kcvc%63yU2sHQ1Pz&j9d#sKa%5fRPy}Jl z6d_7&b#dgyel$?f@q3* zytkHHKV_el7G%w&J|w_v4*~zY>l8&!6QIG-0+4x9MpI#P$h$RVuy( zXFd@jCs7t8rd|inL4A*arLITz>%Qf5Y~1b!+H72AlkESalHu2xMiX!*I0!It{jca8 zdg$-X$MQ88i@Yx&Dk+X^x)8HbtMcH5bq<=$CK z8!)8l0|`E$FNF{TQ6D7bp%M+gNW!bW%!ZnpNgE%0FuW045`!6o@bI(2J z+;jJy^PMxlrc!kX=;5`igMTFv`jc;J6sZb}UxP4(EM#FDIatG(%aScQvL*{zwiPF$ zML<@ph#hreT1?1MJMJX3gpgx)ol~#XJ4r3+q_mVc7q=Unw3fz5LfyzpOd_j};AuI$ zS6e5v^`K3z&^8Kf3bYL?wCl&x>bi&gpo)W7XV0PI1u_a^yqB<&TV&)G4x-F6yfS-; zt6n{ZS0F_Xyc(-wAE5=qE{qeZ%0a^b@ysE;6ne3sY1Ll0h-VaR7Npo%(R}I!!P19V zXW1Kq$OY5zh-HS)2|2%;)8ZyzQ-~l9TS$|LJcu>L!jnjgj7e~Ls)xEBG2u9Wh$)Ff zA&bJoQ?z&#*c9?=A)!2$bRG|{^bJp-VWldpSgs>W{z@+3>FW7Dq$;@}<`Bk=K~io2 zSN}%kqW_bB$N#Z1u`657f@)jV|FUv9>(BUiDii)aVZZC&1D_fH=gQ>{Ri^x|Kq)gq zgE;3!!y&2^D2&(xL5z~KWx_nh|7AA&r2YN3^F=bcyF}e#crMGIG0YKCwDKi00GHS6gltXHMMBf1jV*d$_Xqu>Zyp|Hz2H z-S#&*57I?HUR(rMlvATYT-W(O)AgWE*B#d?+gwiS`q{E!hZdRw-(Y=-P^Z9H!7b{h zZ7@a{Z3e?FM0pYEM$XbT_9h6d5!fX9y;b#FcKXW3MFmG(9C~xd=Obc0Se6kXupkR8 zq^syWT43HYXyDlvXqXVkJ>Vfd7YDVLt~tRvU6n6LUaY1*FIo7uG+jl~1=&kHwaCL_ zVAS^Ex&lLTt{-|pag5@a8l`ct2Ql}IsBIt{woQ4cDn=~BGgO6c;F>1x8GqVrbpV`d zD>DAXEKrzfp8E03nv`%PyaxvMDlkAorgiGT7YF7tZIw*hgG~FS_Y}5Wr zbsQ&kH`3SA57O;^yj|2K2mhl5&eB%SHgm==qFXszF7u0g%h>_=F`lnUv?$3DHT2<@ z|5@C1eK0<%uVK$rm){_&`wIwDD6yj3YjwlO;~s+e1SnvSCrr>VpA~E9wp^=gA9@*i zyh4JuBf1ALupCSA>T9<1Qmw+PqQeJcEiFekrIYZPxFCHd^;OqVJc`cAC($TYBe@_N z4$$!(JOM-0Ef&UM(z?a0={hBwz~sxavT4FN8YtWLSO?|U4I+e6m#Q(!?-nqk{Eb2V z!T$ap{q;S2^kYYYMAtc!lz6Z}W*!Y*5XQY=Je2G2>dkc?)%&`7k9YOzUA?`>dSPZQ zLl6_jQ&59jpp;zZjW?~WQU_OegCqM0*hTb6QWB@}e6r>9WAmBKS3mZ5^!QsooYg*= z)9i|7&t@Dy>HIUF-t;%Z+!1_Ed~1HU?VACA%kCcr=JuVc>^n7^$^Vwj|NBVhE>9ST z;zXN@ZY4TjlJC6IxmCX3j6uA=H3IxOh!^{fb@|?H@!ugN-oC zCkf>s6V(h_S4Zktp1Vv;;&8}{78I{q-J3!Dop6f3OW%T!{Hw+e0)uQ~{5X#hRu|B& z1+;AeZC*e-|3KXp)V+ZAEyWe=;w#-th_6cx3RbTS@{PLG5XGnQmC?tDucE{M1EU!% AO#lD@ literal 0 HcmV?d00001 diff --git a/orthanc/admin.py b/orthanc/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/orthanc/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/orthanc/apps.py b/orthanc/apps.py new file mode 100644 index 0000000..d66b5d8 --- /dev/null +++ b/orthanc/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class OrthancConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'orthanc' diff --git a/orthanc/migrations/0001_initial.py b/orthanc/migrations/0001_initial.py new file mode 100644 index 0000000..c4a206c --- /dev/null +++ b/orthanc/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 5.1.5 on 2025-01-26 03:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Patient', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('orthanc_id', models.CharField(max_length=255, unique=True)), + ('patient_id', models.CharField(blank=True, max_length=255, null=True)), + ('patient_name', models.CharField(blank=True, max_length=255, null=True)), + ('patient_sex', models.CharField(blank=True, max_length=10, null=True)), + ('patient_birth_date', models.CharField(blank=True, max_length=20, null=True)), + ('studies', models.JSONField(blank=True, null=True)), + ('patient_metadata', models.JSONField(blank=True, null=True)), + ], + ), + ] diff --git a/orthanc/migrations/0002_series_instance.py b/orthanc/migrations/0002_series_instance.py new file mode 100644 index 0000000..ccecfef --- /dev/null +++ b/orthanc/migrations/0002_series_instance.py @@ -0,0 +1,39 @@ +# Generated by Django 5.1.5 on 2025-01-26 08:47 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orthanc', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Series', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('orthanc_id', models.CharField(max_length=255, unique=True)), + ('parent_study', models.CharField(max_length=255)), + ('main_dicom_tags', models.JSONField(blank=True, null=True)), + ('status', models.CharField(blank=True, max_length=50, null=True)), + ('is_stable', models.BooleanField(default=False)), + ('last_update', models.CharField(blank=True, max_length=50, null=True)), + ('patient', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='series', to='orthanc.patient')), + ], + ), + migrations.CreateModel( + name='Instance', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('orthanc_id', models.CharField(max_length=255, unique=True)), + ('file_size', models.IntegerField(blank=True, null=True)), + ('file_uuid', models.CharField(blank=True, max_length=255, null=True)), + ('main_dicom_tags', models.JSONField(blank=True, null=True)), + ('index_in_series', models.IntegerField(blank=True, null=True)), + ('parent_series', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='orthanc.series')), + ], + ), + ] diff --git a/orthanc/migrations/__init__.py b/orthanc/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/orthanc/migrations/__pycache__/0001_initial.cpython-312.pyc b/orthanc/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f65ddcd0d39ca99181279433d1cc3726ae8a18b0 GIT binary patch literal 1425 zcmb7DPiWg#7=KTeWm{HK*-08GUYxL)Od)F=wh$!5)Zpk4>AA1bjMFNA&I~8`y&5)MDF58o25pQf|dx!Ua-+RCBegA)* zm=FP5zx@1t{R$7jZ-Ef6lHLKQYSm4ko-Hvr29jH79aIztd zD?}J^D8b%R4f!A5AcBZT$z$P&g&3>{97=`kd?b(WD2?E8=7@{#7*qULCJedRI8erw z{s(vB4ctlQ!}!0K-oTar#?6J?>G7B%%(4KF^J9@s{a0o^eLNlO)uEd0&M22@W+$%X zXEb%2l@wbJdqb&I+pRU!)Wcu9HS`-nyi|-wNHY|Q{L*B zCY9p%@Y8H8+4#{}(C#!J-&74|PeaVZvmjwKWjh^YU|ZqH6q8AKDaY(4c67$onOA)H zv+%&cCbD-GiKewM0!^c$rm?>6n3!Qn(;jwoGaxf5Wmty8;-(V9ZEPWI)tD_3%n8kI zhKbq<7{E%oTwc>gPF-zxNifCX762Qv348)xOy(co81ishczkP^ z2ASe_qMx4g(sP5{l^3}gcXs*dfj_(K%`W%&(?wU+T#NK)ZC7>%xwqWn>azpCxat*G zd+$AW`o&wWyz|%l6ZG76KmEL4yz9#M2DzfUuy%OaUs&@N*4T$r?wx!6h5N4j#UMB5 z&X*4bf4=O^mrtBiF3_5fv`X&M#^Hg#wBapn^w24B?^pUuU%K*F%t^Pf@@(5Ltayc$ zllOb5U)XfztwHW>x3qqE*DtMmrS;wx^Q{#4Hq2$FCtoI|j5th8=7n!>5+%;bF6-$G zYUozIbrn^^s)dD$vi43u3%Sb-(ewvV4_qfJOk{}!c7bif1cdOl0HE?xn1RAm(U%uJ LdGQrsLa6;GYRhjz literal 0 HcmV?d00001 diff --git a/orthanc/migrations/__pycache__/0002_series_instance.cpython-312.pyc b/orthanc/migrations/__pycache__/0002_series_instance.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad49ff36e79d1729d5d7fbb1857545c2d712fdae GIT binary patch literal 2464 zcmcguJ#5=X6h2ZECCai*x=!5qN0ME&s1jJQVxuwAK&^k`*!gkdIzNbDfS`3JnGQuN zB;`g~+5$n*F-sATUJGl;s7&!_pbH5Ipm0kIbjl{cX@@TDk(3graf-A+D*@!aH$tB@H6Q$or1|m2Vpd-;(u@ z0rDc}Xh;>QT!03MIVu*Uq5F*8>^ThOk0C?;LCDrZyNo|mpkdha?!-E@2y&3AMRw_z zo?UPEtVMUJmww%zU*939_Rx)A>p6tj`&L8-^2Ar@C`a<_^L+b{Kn>R3I~2YDA7CEy ze2>40iK3f>Z_aK;kC+=iVlMXP96SyOYVW_|Qw-Nmbd(@j99_%7|E!in9`nQzdH%IV z!+)cvyF(7w=-n$n^3L&Np6CBlqc}{wR-@3X8WriBP#sN;VeV?@)RH3{(sXk}Qu8=E zF)@)5WmPt1Nx|$Y(q&{|T81|kFugRNVsKQdn3|Z^kz^u>yUMy;lJu&07gaH55O+zD zA0QmvLi)O9AW@Y{2uIrRq70w^L2BhS4e7q9Ahl?2V78*l_bOyjS<;beiiTN%)fA<> zM-TUwBv}=qoYzXCDHRQ}Hn^@x>RrsJ6@}zzn37pBo}Y1X1G+1)w@@E&S97vK;G}g0 zr6?SNs327o6L%?+VTzS9Bph(4OnMgyOOQOa4|?0#rrQrv0nBM61*9P29@UZJQumqB zrbZHCJeF)hMhY}=cu6H8)jYynK~|7xxPth@N=1gaS5_gqPec~|fVjKE+gF>D+v|-7 zJ)Km1&*fAf4vSu7QN$5ZB;B?`B8WIDiuWp#;^yI65S0-LqUK5Ubhm5ct~sw_SKaok zyIm7UXXWAyspW#3HRk3vB)vVnw0dpXA4X?2O+k|CTag7#M{-fUOlr--O-U_kn1<^( z>`6oVKMu{!tj^8MU%=5N)kH)P6(!01ilS@cZoixiuAiIbPPaO_Vo{wRI_f zm*l+GX1w<5rLso$PcJ&tEB5q?GkwRNzGH2aTL5fP z7wH!Oj4aZ`5SHkc!1edM2u8U`Yw$G@hW@PxuO-jyO*LYR7Qf_?O1q@eb}aqm#$MjK zexnh)Y4Nw-t`Je~tgGBvJ9)Ma_xl^kWsASo6cX0h_I|p@<2{`wUX^%gn_pCNH&Ix1cCL zD>b>KI3_7EIXg8kB__Y9BqK2|IVLwVy(qCHGe565CO$qhFS8^*Uaz3?7Kcr4eoARh Ys$CH)&=^J_E(S3^GBYwV7BK@^05ZcRYXATM literal 0 HcmV?d00001 diff --git a/orthanc/models.py b/orthanc/models.py new file mode 100644 index 0000000..f711c0d --- /dev/null +++ b/orthanc/models.py @@ -0,0 +1,36 @@ +from django.db import models + +class Patient(models.Model): + orthanc_id = models.CharField(max_length=255, unique=True) + patient_id = models.CharField(max_length=255, null=True, blank=True) + patient_name = models.CharField(max_length=255, null=True, blank=True) + patient_sex = models.CharField(max_length=10, null=True, blank=True) + patient_birth_date = models.CharField(max_length=20, null=True, blank=True) + studies = models.JSONField(null=True, blank=True) # Массив ID исследований + patient_metadata = models.JSONField(null=True, blank=True) + + def __str__(self): + return self.patient_name or "Unknown Patient" + +class Series(models.Model): + orthanc_id = models.CharField(max_length=255, unique=True) + parent_study = models.CharField(max_length=255) + patient = models.ForeignKey(Patient, related_name="series", on_delete=models.CASCADE) + main_dicom_tags = models.JSONField(null=True, blank=True) + status = models.CharField(max_length=50, null=True, blank=True) + is_stable = models.BooleanField(default=False) + last_update = models.CharField(max_length=50, null=True, blank=True) + + def __str__(self): + return self.main_dicom_tags.get("SeriesDescription", "Unknown Series") + +class Instance(models.Model): + orthanc_id = models.CharField(max_length=255, unique=True) + parent_series = models.ForeignKey(Series, related_name="instances", on_delete=models.CASCADE) + file_size = models.IntegerField(null=True, blank=True) + file_uuid = models.CharField(max_length=255, null=True, blank=True) + main_dicom_tags = models.JSONField(null=True, blank=True) + index_in_series = models.IntegerField(null=True, blank=True) + + def __str__(self): + return self.main_dicom_tags.get("SOPInstanceUID", "Unknown Instance") diff --git a/orthanc/serializers.py b/orthanc/serializers.py new file mode 100644 index 0000000..018e792 --- /dev/null +++ b/orthanc/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import Patient + +class PatientSerializer(serializers.ModelSerializer): + class Meta: + model = Patient + fields = "__all__" diff --git a/orthanc/tasks.py b/orthanc/tasks.py new file mode 100644 index 0000000..6f82b16 --- /dev/null +++ b/orthanc/tasks.py @@ -0,0 +1,108 @@ +import requests +from urllib3.exceptions import InsecureRequestWarning + +from .models import Patient, Series, Instance + +# Отключение предупреждений о небезопасном соединении +requests.packages.urllib3.disable_warnings(InsecureRequestWarning) + +ORTHANC_BASE_URL = "http://192.168.2.60:8042" +ORTHANC_USERNAME = "writehost" +ORTHANC_PASSWORD = "writehost" + +# 1. Получение всех пациентов +def fetch_patients(): + url = f"{ORTHANC_BASE_URL}/patients" + response = requests.get(url, auth=(ORTHANC_USERNAME, ORTHANC_PASSWORD)) + response.raise_for_status() + return response.json() + +# 2. Получение информации о конкретном пациенте +def fetch_patient_details(orthanc_id): + url = f"{ORTHANC_BASE_URL}/patients/{orthanc_id}" + response = requests.get(url, auth=(ORTHANC_USERNAME, ORTHANC_PASSWORD)) + response.raise_for_status() + return response.json() + +# 3. Получение данных об исследовании +def fetch_study_details(study_id): + url = f"{ORTHANC_BASE_URL}/studies/{study_id}" + response = requests.get(url, auth=(ORTHANC_USERNAME, ORTHANC_PASSWORD)) + response.raise_for_status() + return response.json() + +# 4. Получение данных о серии +def fetch_series_details(series_id): + url = f"{ORTHANC_BASE_URL}/series/{series_id}" + response = requests.get(url, auth=(ORTHANC_USERNAME, ORTHANC_PASSWORD)) + response.raise_for_status() + return response.json() + +# 5. Получение данных об изображении (инстансе) +def fetch_instance_details(instance_id): + url = f"{ORTHANC_BASE_URL}/instances/{instance_id}" + response = requests.get(url, auth=(ORTHANC_USERNAME, ORTHANC_PASSWORD)) + response.raise_for_status() + return response.json() + +# Основная синхронизация +def sync_patients(): + patient_ids = fetch_patients() + for patient_id in patient_ids: + patient_data = fetch_patient_details(patient_id) + + # Синхронизация пациента + patient, created = Patient.objects.update_or_create( + orthanc_id=patient_id, + defaults={ + "patient_id": patient_data["MainDicomTags"].get("PatientID"), + "patient_name": patient_data["MainDicomTags"].get("PatientName"), + "patient_sex": patient_data["MainDicomTags"].get("PatientSex"), + "patient_birth_date": patient_data["MainDicomTags"].get("PatientBirthDate"), + "studies": patient_data.get("Studies", []), + "patient_metadata": patient_data.get("Labels", []), + }, + ) + + # Синхронизация исследований (Studies) + for study_id in patient_data.get("Studies", []): + sync_study(study_id, patient) + +# Синхронизация исследования +def sync_study(study_id, patient): + study_data = fetch_study_details(study_id) + for series_id in study_data.get("Series", []): + sync_series(series_id, patient) + +# Синхронизация серии +def sync_series(series_id, patient): + series_data = fetch_series_details(series_id) + series, created = Series.objects.update_or_create( + orthanc_id=series_id, + defaults={ + "parent_study": series_data["ParentStudy"], + "patient": patient, + "main_dicom_tags": series_data.get("MainDicomTags", {}), + "status": series_data.get("Status"), + "is_stable": series_data.get("IsStable", False), + "last_update": series_data.get("LastUpdate"), + }, + ) + + # Синхронизация снимков (Instances) + for instance_id in series_data.get("Instances", []): + sync_instance(instance_id, series) + +# Синхронизация снимка +def sync_instance(instance_id, series): + instance_data = fetch_instance_details(instance_id) + Instance.objects.update_or_create( + orthanc_id=instance_id, + defaults={ + "parent_series": series, + "file_size": instance_data.get("FileSize"), + "file_uuid": instance_data.get("FileUuid"), + "main_dicom_tags": instance_data.get("MainDicomTags", {}), + "index_in_series": instance_data.get("IndexInSeries"), + }, + ) diff --git a/orthanc/tests.py b/orthanc/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/orthanc/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/orthanc/urls.py b/orthanc/urls.py new file mode 100644 index 0000000..2ce2c15 --- /dev/null +++ b/orthanc/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from .views import PatientListView, SyncPatientsView, ProtectedView +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView + +urlpatterns = [ + path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), # Получение токена + path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # Обновление токена + path('', PatientListView.as_view(), name='patient-list'), + path('sync/', SyncPatientsView.as_view(), name='sync-patients'), + path('protected/', ProtectedView.as_view(), name='protected'), + +] diff --git a/orthanc/views.py b/orthanc/views.py new file mode 100644 index 0000000..abedb1a --- /dev/null +++ b/orthanc/views.py @@ -0,0 +1,76 @@ +# from rest_framework.views import APIView +# from rest_framework.response import Response +# from rest_framework import status +# from .models import Patient +# from .serializers import PatientSerializer +# from .tasks import fetch_patients, fetch_patient_details, sync_patients +# +# +# class PatientListView(APIView): +# def get(self, request): +# patients = Patient.objects.all() +# serializer = PatientSerializer(patients, many=True) +# return Response(serializer.data) +# +# class SyncPatientsView(APIView): +# def post(self, request): +# try: +# patient_ids = fetch_patients() +# for orthanc_id in patient_ids: +# patient_data = fetch_patient_details(orthanc_id) +# patient, created = Patient.objects.update_or_create( +# orthanc_id=orthanc_id, +# defaults={ +# "patient_id": patient_data["MainDicomTags"].get("PatientID"), +# "patient_name": patient_data["MainDicomTags"].get("PatientName"), +# "patient_sex": patient_data["MainDicomTags"].get("PatientSex"), +# "patient_birth_date": patient_data["MainDicomTags"].get("PatientBirthDate"), +# "studies": patient_data.get("Studies", []), +# "patient_metadata": patient_data, +# }, +# ) +# return Response({"message": "Synchronization completed successfully"}) +# except Exception as e: +# return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) +# +# class SyncPatientsView(APIView): +# def post(self, request): +# try: +# sync_patients() +# return Response({"detail": "Synchronization completed successfully."}, status=status.HTTP_200_OK) +# except Exception as e: +# return Response({"detail": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + + +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from .models import Patient +from .serializers import PatientSerializer +from .tasks import fetch_patients, fetch_patient_details, sync_patients +from rest_framework.permissions import IsAuthenticated + + + +class ProtectedView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + return Response({"message": f"Привет, {request.user.username}! Вы авторизованы."}) + +# Эндпоинт для получения списка пациентов +class PatientListView(APIView): + def get(self, request): + patients = Patient.objects.all() + serializer = PatientSerializer(patients, many=True) + return Response(serializer.data) + +# Эндпоинт для синхронизации данных с Orthanc +class SyncPatientsView(APIView): + def post(self, request): + try: + sync_patients() + return Response({"detail": "Synchronization completed successfully."}, status=status.HTTP_200_OK) + except Exception as e: + return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)