back-end 1.0

This commit is contained in:
The unwasted guests 2025-01-28 21:43:26 +10:00
commit 8ec69c5d16
33 changed files with 541 additions and 0 deletions

0
backend/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

16
backend/asgi.py Normal file
View File

@ -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()

145
backend/settings.py Normal file
View File

@ -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',),
}

25
backend/urls.py Normal file
View File

@ -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')),
]

16
backend/wsgi.py Normal file
View File

@ -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()

22
manage.py Normal file
View File

@ -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()

0
orthanc/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
orthanc/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
orthanc/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class OrthancConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'orthanc'

View File

@ -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)),
],
),
]

View File

@ -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')),
],
),
]

View File

36
orthanc/models.py Normal file
View File

@ -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")

7
orthanc/serializers.py Normal file
View File

@ -0,0 +1,7 @@
from rest_framework import serializers
from .models import Patient
class PatientSerializer(serializers.ModelSerializer):
class Meta:
model = Patient
fields = "__all__"

108
orthanc/tasks.py Normal file
View File

@ -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"),
},
)

3
orthanc/tests.py Normal file
View File

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

12
orthanc/urls.py Normal file
View File

@ -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'),
]

76
orthanc/views.py Normal file
View File

@ -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)