Add Django backend, Next.js frontend, and deploy templates

This commit is contained in:
ogrechko committed 2026-08-27 15:38:31 +00:00
1 parent fc0c991335
commit e9cd22d36f
93 files changed
+7840 -1

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
DJANGO_SECRET_KEY=change-me
DJANGO_DEBUG=True
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,backend
DJANGO_CORS_ALLOWED_ORIGINS=http://localhost:3000
DATABASE_URL=postgresql://postgres:postgres@localhost:5433/kkpab
+14
View File
@@ -0,0 +1,14 @@
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
+1
View File
@@ -0,0 +1 @@
+8
View File
@@ -0,0 +1,8 @@
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_asgi_application()
+110
View File
@@ -0,0 +1,110 @@
import os
from pathlib import Path
from dotenv import load_dotenv
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / ".env")
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "change-me")
DEBUG = os.getenv("DJANGO_DEBUG", "False").lower() == "true"
ALLOWED_HOSTS = [host.strip() for host in os.getenv("DJANGO_ALLOWED_HOSTS", "").split(",") if host.strip()]
if DEBUG:
for internal_host in ("backend", "backend-1", "localhost", "127.0.0.1"):
if internal_host not in ALLOWED_HOSTS:
ALLOWED_HOSTS.append(internal_host)
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"corsheaders",
"rest_framework",
"content",
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"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 = "config.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "config.wsgi.application"
ASGI_APPLICATION = "config.asgi.application"
db_url = os.getenv("DATABASE_URL", "sqlite:///db.sqlite3")
if db_url.startswith("postgresql://"):
from urllib.parse import urlparse
parsed = urlparse(db_url)
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": parsed.path.lstrip("/"),
"USER": parsed.username,
"PASSWORD": parsed.password,
"HOST": parsed.hostname,
"PORT": parsed.port or 5432,
}
}
else:
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3"}}
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"},
]
LANGUAGE_CODE = "ru-ru"
TIME_ZONE = "Europe/Moscow"
USE_I18N = True
USE_TZ = True
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
CORS_ALLOWED_ORIGINS = [
origin.strip()
for origin in os.getenv("DJANGO_CORS_ALLOWED_ORIGINS", "").split(",")
if origin.strip()
]
REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer",
"rest_framework.renderers.BrowsableAPIRenderer",
]
}
+10
View File
@@ -0,0 +1,10 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("content.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+8
View File
@@ -0,0 +1,8 @@
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()
+1
View File
@@ -0,0 +1 @@
+42
View File
@@ -0,0 +1,42 @@
from django.contrib import admin
from .models import ContactMessage, Doctor, Document, Page, Service, SiteSettings
@admin.register(SiteSettings)
class SiteSettingsAdmin(admin.ModelAdmin):
list_display = ("site_name", "phone", "city", "updated_at")
@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
list_display = ("title", "slug", "is_published", "nav_order", "updated_at")
list_editable = ("is_published", "nav_order")
prepopulated_fields = {"slug": ("title",)}
@admin.register(Service)
class ServiceAdmin(admin.ModelAdmin):
list_display = ("title", "slug", "sort_order", "updated_at")
list_editable = ("sort_order",)
prepopulated_fields = {"slug": ("title",)}
@admin.register(Doctor)
class DoctorAdmin(admin.ModelAdmin):
list_display = ("full_name", "position", "sort_order", "updated_at")
list_editable = ("sort_order",)
@admin.register(Document)
class DocumentAdmin(admin.ModelAdmin):
list_display = ("title", "category", "sort_order", "updated_at")
list_editable = ("sort_order",)
@admin.register(ContactMessage)
class ContactMessageAdmin(admin.ModelAdmin):
list_display = ("name", "phone", "email", "is_processed", "created_at")
list_editable = ("is_processed",)
readonly_fields = ("name", "phone", "email", "message", "created_at", "updated_at")
+7
View File
@@ -0,0 +1,7 @@
from django.apps import AppConfig
class ContentConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "content"
+237
View File
@@ -0,0 +1,237 @@
[
{
"model": "content.sitesettings",
"pk": 1,
"fields": {
"site_name": "Консультационное клиническое патологоанатомическое бюро",
"tagline": "Точная морфологическая диагностика и профессиональное сопровождение",
"phone": "+7 (8652) 22-66-77",
"email": "kkpab@bk.ru",
"address": "улица Балакирева, 5к7",
"city": "Ставрополь",
"working_hours": "Пн-Пт 08:00-17:00, Сб 09:00-14:00",
"hero_title": "Современная патоморфология с вниманием к клинической точности",
"hero_subtitle": "Переосмысленный цифровой сайт для бюро, где ключевая информация, специалисты, документы и направления на исследования доступны быстро и понятно.",
"hero_image": "https://images.unsplash.com/photo-1579154204601-01588f351e67?auto=format&fit=crop&w=1600&q=80",
"about_excerpt": "Бюро объединяет специалистов по патологической анатомии и гистологии, обеспечивая качественную диагностику и профессиональную коммуникацию с медицинскими организациями."
}
},
{
"model": "content.page",
"pk": 1,
"fields": {
"title": "О бюро",
"slug": "about",
"intro": "Специалисты разного уровня и современное оборудование для морфологической диагностики.",
"body": "В патологоанатомическом бюро работают врачи с большим стажем, квалификационными категориями и постоянным профессиональным развитием. Для гистологической обработки используется современное высокотехнологичное оборудование, а диагностический процесс поддерживается подготовленным средним и младшим медицинским персоналом.",
"meta_title": "О бюро",
"meta_description": "Информация о консультационном клиническом патологоанатомическом бюро.",
"is_published": true,
"nav_order": 1
}
},
{
"model": "content.page",
"pk": 2,
"fields": {
"title": "Сведения о медицинской организации",
"slug": "svedeniya-mo",
"intro": "Юридическая информация, лицензии, график и данные страховых организаций.",
"body": "Полное наименование: ООО \"Консультационное Клиническое Патологоанатомическое Бюро\". ОГРН 1182651002093. Режим работы: пн-пт с 8:00 до 17:00, суббота с 9:00 до 14:00. Генеральный директор: Балабеков Андрей Вагифович. Телефон: +7 (8652) 22-66-77. Электронная почта: kkpab@bk.ru.",
"meta_title": "Сведения МО",
"meta_description": "Официальные сведения о медицинской организации.",
"is_published": true,
"nav_order": 2
}
},
{
"model": "content.service",
"pk": 1,
"fields": {
"title": "Гистологические исследования",
"slug": "histology",
"short_description": "Базовый и наиболее распространенный метод исследования тканей.",
"description": "Гистологическое исследование используется для диагностики опухолевых и неопухолевых заболеваний. Материал проходит фиксацию, гистопроцессинг, заливку в парафин, микротомию, окрашивание и оценку врачом-патоморфологом.",
"icon": "microscope",
"sort_order": 1
}
},
{
"model": "content.service",
"pk": 2,
"fields": {
"title": "Консультация морфологических препаратов",
"slug": "consultations",
"short_description": "Профессиональная верификация и экспертная оценка сложных случаев.",
"description": "Консультация препаратов помогает уточнить диагноз, подтвердить сложные клинические случаи и обеспечить единый профессиональный стандарт интерпретации.",
"icon": "stethoscope",
"sort_order": 2
}
},
{
"model": "content.service",
"pk": 3,
"fields": {
"title": "Документационное сопровождение",
"slug": "documents-support",
"short_description": "Подготовка обязательной информации и доступ к официальным документам.",
"description": "Пациенты и партнеры получают быстрый доступ к сведениям о медицинской организации, лицензии, графику работы и контактам.",
"icon": "file-text",
"sort_order": 3
}
},
{
"model": "content.service",
"pk": 4,
"fields": {
"title": "Исследование биопсийно-операционного материала",
"slug": "biopsy-operative-material",
"short_description": "Исследование биопсийно-операционного материала органов и тканей при воспалительных процессах, дистрофиях, патологии артерий/вен, пороках развития.",
"description": "Исследование биопсийно-операционного материала органов и тканей при воспалительных процессах, дистрофиях, патологии артерий/вен, пороках развития.",
"icon": "microscope",
"sort_order": 4
}
},
{
"model": "content.service",
"pk": 5,
"fields": {
"title": "Исследование операционного материала",
"slug": "operative-material",
"short_description": "Исследование операционного материала органов и тканей при инфекционных заболеваниях, опухолях ясного гистогенеза без дисплазии, гранулематозном воспалении.",
"description": "Исследование операционного материала органов и тканей при инфекционных заболеваниях, опухолях ясного гистогенеза без дисплазии, гранулематозном воспалении.",
"icon": "file-text",
"sort_order": 5
}
},
{
"model": "content.service",
"pk": 6,
"fields": {
"title": "Исследование диагностических биопсий",
"slug": "diagnostic-biopsies",
"short_description": "Исследование диагностических биопсий (кроме пункционных) органов и тканей при воспалении, дисфункции, дисплазии, опухолях, требующих уточнения гистогенеза, степени инвазии, стадии прогрессирования.",
"description": "Исследование диагностических биопсий (кроме пункционных) органов и тканей при воспалении, дисфункции, дисплазии, опухолях, требующих уточнения гистогенеза, степени инвазии, стадии прогрессирования.",
"icon": "stethoscope",
"sort_order": 6
}
},
{
"model": "content.service",
"pk": 7,
"fields": {
"title": "Исследование диагностических пункционных биопсий",
"slug": "diagnostic-puncture-biopsies",
"short_description": "Исследование диагностических пункционных биопсий различных органов и тканей, биопсий и операционный материал при иммунопатологических процессах, опухолях и опухолеподобных поражениях.",
"description": "Исследование диагностических пункционных биопсий различных органов и тканей, биопсий и операционный материал при иммунопатологических процессах, опухолях и опухолеподобных поражениях.",
"icon": "microscope",
"sort_order": 7
}
},
{
"model": "content.service",
"pk": 8,
"fields": {
"title": "Экспресс-исследование диагностических биопсий",
"slug": "express-diagnostic-biopsies",
"short_description": "Экспресс-исследование диагностических биопсий (кроме пункционных) органов и тканей при воспалении, дисфункции, дисплазии, опухолях, требующих уточнения гистогенеза, степени инвазии, стадии прогрессирования.",
"description": "Экспресс-исследование диагностических биопсий (кроме пункционных) органов и тканей при воспалении, дисфункции, дисплазии, опухолях, требующих уточнения гистогенеза, степени инвазии, стадии прогрессирования.",
"icon": "stethoscope",
"sort_order": 8
}
},
{
"model": "content.service",
"pk": 9,
"fields": {
"title": "Исследование костно-хрящевых тканей",
"slug": "bone-cartilage-tissues",
"short_description": "Исследование костно-хрящевых тканей, включая декальцинацию операционного материала.",
"description": "Исследование костно-хрящевых тканей, включая декальцинацию операционного материала.",
"icon": "file-text",
"sort_order": 9
}
},
{
"model": "content.service",
"pk": 10,
"fields": {
"title": "Гистологическое исследование последов",
"slug": "placenta-histology",
"short_description": "Гистологическое исследование последов с целью диагностики внутриутробного инфицирования.",
"description": "Гистологическое исследование последов с целью диагностики внутриутробного инфицирования.",
"icon": "microscope",
"sort_order": 10
}
},
{
"model": "content.doctor",
"pk": 1,
"fields": {
"full_name": "Чуков Сергей Залимович",
"position": "Заведующий отделением, врач-патологоанатом",
"credentials": "Сертификат специалиста 2514 от 24.12.2018",
"bio": "Руководит отделением и участвует в диагностике сложных клинических случаев.",
"photo": "",
"sort_order": 1
}
},
{
"model": "content.doctor",
"pk": 2,
"fields": {
"full_name": "Балабеков Андрей Вагифович",
"position": "Врач-патологоанатом, генеральный директор",
"credentials": "Сертификат специалиста 307 от 07.03.2019",
"bio": "Координирует работу бюро и участвует в развитии диагностической службы.",
"photo": "",
"sort_order": 2
}
},
{
"model": "content.doctor",
"pk": 3,
"fields": {
"full_name": "Громова Юлия Владимировна",
"position": "Врач-патологоанатом",
"credentials": "Сертификат ВСБ № 0947855 от 30.09.2006",
"bio": "Работает с морфологической диагностикой и сопровождением исследований.",
"photo": "",
"sort_order": 3
}
},
{
"model": "content.doctor",
"pk": 4,
"fields": {
"full_name": "Чеккуева Зульфия Алиевна",
"position": "Врач-патологоанатом",
"credentials": "Сертификат специалиста 530 от 21.04.2021 г. Диплом об окончании ординатуры 1163 от 31.08.2020 г.",
"bio": "Врач-патологоанатом.",
"photo": "",
"sort_order": 4
}
},
{
"model": "content.document",
"pk": 1,
"fields": {
"title": "Лицензия на осуществление медицинской деятельности",
"category": "Лицензии",
"description": "Лицензия № ЛО-26-01-005362 от 20 марта 2020 года.",
"file_url": "",
"sort_order": 1
}
},
{
"model": "content.document",
"pk": 2,
"fields": {
"title": "Территориальная программа ОМС СК",
"category": "Документы",
"description": "Обязательная информация для пациентов и партнеров.",
"file_url": "",
"sort_order": 2
}
}
]
+129
View File
@@ -0,0 +1,129 @@
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="ContactMessage",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("name", models.CharField(max_length=255)),
("phone", models.CharField(blank=True, max_length=64)),
("email", models.EmailField(blank=True, max_length=254)),
("message", models.TextField()),
("is_processed", models.BooleanField(default=False)),
],
options={
"verbose_name": "Сообщение",
"verbose_name_plural": "Сообщения",
"ordering": ["-created_at"],
},
),
migrations.CreateModel(
name="Doctor",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("full_name", models.CharField(max_length=255)),
("position", models.CharField(max_length=255)),
("credentials", models.TextField(blank=True)),
("bio", models.TextField(blank=True)),
("photo", models.URLField(blank=True)),
("sort_order", models.PositiveIntegerField(default=0)),
],
options={
"verbose_name": "Специалист",
"verbose_name_plural": "Специалисты",
"ordering": ["sort_order", "full_name"],
},
),
migrations.CreateModel(
name="Document",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
("category", models.CharField(blank=True, max_length=120)),
("description", models.TextField(blank=True)),
("file_url", models.URLField(blank=True)),
("sort_order", models.PositiveIntegerField(default=0)),
],
options={
"verbose_name": "Документ",
"verbose_name_plural": "Документы",
"ordering": ["sort_order", "title"],
},
),
migrations.CreateModel(
name="Page",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
("slug", models.SlugField(unique=True)),
("intro", models.TextField(blank=True)),
("body", models.TextField()),
("meta_title", models.CharField(blank=True, max_length=255)),
("meta_description", models.CharField(blank=True, max_length=320)),
("is_published", models.BooleanField(default=True)),
("nav_order", models.PositiveIntegerField(default=0)),
],
options={
"verbose_name": "Страница",
"verbose_name_plural": "Страницы",
"ordering": ["nav_order", "title"],
},
),
migrations.CreateModel(
name="Service",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("title", models.CharField(max_length=255)),
("slug", models.SlugField(unique=True)),
("short_description", models.TextField()),
("description", models.TextField()),
("icon", models.CharField(blank=True, max_length=64)),
("sort_order", models.PositiveIntegerField(default=0)),
],
options={
"verbose_name": "Услуга",
"verbose_name_plural": "Услуги",
"ordering": ["sort_order", "title"],
},
),
migrations.CreateModel(
name="SiteSettings",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
("site_name", models.CharField(max_length=255)),
("tagline", models.CharField(blank=True, max_length=255)),
("phone", models.CharField(max_length=64)),
("email", models.EmailField(blank=True, max_length=254)),
("address", models.CharField(max_length=255)),
("city", models.CharField(default="Ставрополь", max_length=120)),
("working_hours", models.CharField(blank=True, max_length=255)),
("hero_title", models.CharField(max_length=255)),
("hero_subtitle", models.TextField()),
("hero_image", models.URLField(blank=True)),
("about_excerpt", models.TextField(blank=True)),
],
options={
"verbose_name": "Настройки сайта",
"verbose_name_plural": "Настройки сайта",
},
),
]
+1
View File
@@ -0,0 +1 @@
+116
View File
@@ -0,0 +1,116 @@
from django.db import models
class TimestampedModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class SiteSettings(TimestampedModel):
site_name = models.CharField(max_length=255)
tagline = models.CharField(max_length=255, blank=True)
phone = models.CharField(max_length=64)
email = models.EmailField(blank=True)
address = models.CharField(max_length=255)
city = models.CharField(max_length=120, default="Ставрополь")
working_hours = models.CharField(max_length=255, blank=True)
hero_title = models.CharField(max_length=255)
hero_subtitle = models.TextField()
hero_image = models.URLField(blank=True)
about_excerpt = models.TextField(blank=True)
class Meta:
verbose_name = "Настройки сайта"
verbose_name_plural = "Настройки сайта"
def __str__(self):
return self.site_name
class Page(TimestampedModel):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
intro = models.TextField(blank=True)
body = models.TextField()
meta_title = models.CharField(max_length=255, blank=True)
meta_description = models.CharField(max_length=320, blank=True)
is_published = models.BooleanField(default=True)
nav_order = models.PositiveIntegerField(default=0)
class Meta:
ordering = ["nav_order", "title"]
verbose_name = "Страница"
verbose_name_plural = "Страницы"
def __str__(self):
return self.title
class Service(TimestampedModel):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
short_description = models.TextField()
description = models.TextField()
icon = models.CharField(max_length=64, blank=True)
sort_order = models.PositiveIntegerField(default=0)
class Meta:
ordering = ["sort_order", "title"]
verbose_name = "Услуга"
verbose_name_plural = "Услуги"
def __str__(self):
return self.title
class Doctor(TimestampedModel):
full_name = models.CharField(max_length=255)
position = models.CharField(max_length=255)
credentials = models.TextField(blank=True)
bio = models.TextField(blank=True)
photo = models.URLField(blank=True)
sort_order = models.PositiveIntegerField(default=0)
class Meta:
ordering = ["sort_order", "full_name"]
verbose_name = "Специалист"
verbose_name_plural = "Специалисты"
def __str__(self):
return self.full_name
class Document(TimestampedModel):
title = models.CharField(max_length=255)
category = models.CharField(max_length=120, blank=True)
description = models.TextField(blank=True)
file_url = models.URLField(blank=True)
sort_order = models.PositiveIntegerField(default=0)
class Meta:
ordering = ["sort_order", "title"]
verbose_name = "Документ"
verbose_name_plural = "Документы"
def __str__(self):
return self.title
class ContactMessage(TimestampedModel):
name = models.CharField(max_length=255)
phone = models.CharField(max_length=64, blank=True)
email = models.EmailField(blank=True)
message = models.TextField()
is_processed = models.BooleanField(default=False)
class Meta:
ordering = ["-created_at"]
verbose_name = "Сообщение"
verbose_name_plural = "Сообщения"
def __str__(self):
return f"{self.name} ({self.created_at:%d.%m.%Y})"
+41
View File
@@ -0,0 +1,41 @@
from rest_framework import serializers
from .models import ContactMessage, Doctor, Document, Page, Service, SiteSettings
class SiteSettingsSerializer(serializers.ModelSerializer):
class Meta:
model = SiteSettings
fields = "__all__"
class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = "__all__"
class ServiceSerializer(serializers.ModelSerializer):
class Meta:
model = Service
fields = "__all__"
class DoctorSerializer(serializers.ModelSerializer):
class Meta:
model = Doctor
fields = "__all__"
class DocumentSerializer(serializers.ModelSerializer):
class Meta:
model = Document
fields = "__all__"
class ContactMessageSerializer(serializers.ModelSerializer):
class Meta:
model = ContactMessage
fields = ("id", "name", "phone", "email", "message", "created_at")
read_only_fields = ("id", "created_at")
+26
View File
@@ -0,0 +1,26 @@
from django.urls import path
from .views import (
ContactMessageCreateAPIView,
DoctorListAPIView,
DocumentListAPIView,
HomeAPIView,
PageDetailAPIView,
PageListAPIView,
ServiceDetailAPIView,
ServiceListAPIView,
SiteSettingsAPIView,
)
urlpatterns = [
path("home/", HomeAPIView.as_view(), name="home"),
path("site/", SiteSettingsAPIView.as_view(), name="site-settings"),
path("pages/", PageListAPIView.as_view(), name="page-list"),
path("pages/<slug:slug>/", PageDetailAPIView.as_view(), name="page-detail"),
path("services/", ServiceListAPIView.as_view(), name="service-list"),
path("services/<slug:slug>/", ServiceDetailAPIView.as_view(), name="service-detail"),
path("doctors/", DoctorListAPIView.as_view(), name="doctor-list"),
path("documents/", DocumentListAPIView.as_view(), name="document-list"),
path("contact/", ContactMessageCreateAPIView.as_view(), name="contact-create"),
]
+70
View File
@@ -0,0 +1,70 @@
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import ContactMessage, Doctor, Document, Page, Service, SiteSettings
from .serializers import (
ContactMessageSerializer,
DoctorSerializer,
DocumentSerializer,
PageSerializer,
ServiceSerializer,
SiteSettingsSerializer,
)
class HomeAPIView(APIView):
def get(self, request):
settings_obj = SiteSettings.objects.order_by("id").first()
data = {
"site": SiteSettingsSerializer(settings_obj).data if settings_obj else None,
"pages": PageSerializer(Page.objects.filter(is_published=True)[:6], many=True).data,
"services": ServiceSerializer(Service.objects.all()[:6], many=True).data,
"doctors": DoctorSerializer(Doctor.objects.all(), many=True).data,
"documents": DocumentSerializer(Document.objects.all()[:4], many=True).data,
}
return Response(data)
class SiteSettingsAPIView(generics.RetrieveAPIView):
serializer_class = SiteSettingsSerializer
def get_object(self):
return SiteSettings.objects.order_by("id").first()
class PageListAPIView(generics.ListAPIView):
serializer_class = PageSerializer
queryset = Page.objects.filter(is_published=True)
class PageDetailAPIView(generics.RetrieveAPIView):
serializer_class = PageSerializer
lookup_field = "slug"
queryset = Page.objects.filter(is_published=True)
class ServiceListAPIView(generics.ListAPIView):
serializer_class = ServiceSerializer
queryset = Service.objects.all()
class ServiceDetailAPIView(generics.RetrieveAPIView):
serializer_class = ServiceSerializer
lookup_field = "slug"
queryset = Service.objects.all()
class DoctorListAPIView(generics.ListAPIView):
serializer_class = DoctorSerializer
queryset = Doctor.objects.all()
class DocumentListAPIView(generics.ListAPIView):
serializer_class = DocumentSerializer
queryset = Document.objects.all()
class ContactMessageCreateAPIView(generics.CreateAPIView):
serializer_class = ContactMessageSerializer
queryset = ContactMessage.objects.all()
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
+8
View File
@@ -0,0 +1,8 @@
Django==5.1.6
djangorestframework==3.15.2
django-cors-headers==4.6.0
psycopg[binary]==3.2.3
python-dotenv==1.0.1
gunicorn==23.0.0
Pillow==11.0.0