From c8959e6a4ad90ae178ed322f64d2bb41aee5fe53 Mon Sep 17 00:00:00 2001 From: SEkaya13 Date: Fri, 12 Dec 2025 20:12:01 +0300 Subject: [PATCH 1/3] New Project - Hotel --- .dockerignore | 18 +++ .gitignore | 75 +++++++++ hotel/Dockerfile | 31 ++++ hotel/docker-compose.yml | 40 +++++ hotel/hotel/__init__.py | 0 hotel/hotel/asgi.py | 16 ++ hotel/hotel/settings.py | 123 ++++++++++++++ hotel/hotel/urls.py | 26 +++ hotel/hotel/wsgi.py | 16 ++ hotel/hotel_app/__init__.py | 0 hotel/hotel_app/admin.py | 3 + hotel/hotel_app/apps.py | 6 + hotel/hotel_app/migrations/0001_initial.py | 42 +++++ hotel/hotel_app/migrations/__init__.py | 0 hotel/hotel_app/models.py | 31 ++++ hotel/hotel_app/tests.py | 3 + hotel/hotel_app/urls.py | 13 ++ hotel/hotel_app/views.py | 178 +++++++++++++++++++++ hotel/manage.py | 22 +++ requirements.txt | 25 +++ 20 files changed, 668 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 hotel/Dockerfile create mode 100644 hotel/docker-compose.yml create mode 100644 hotel/hotel/__init__.py create mode 100644 hotel/hotel/asgi.py create mode 100644 hotel/hotel/settings.py create mode 100644 hotel/hotel/urls.py create mode 100644 hotel/hotel/wsgi.py create mode 100644 hotel/hotel_app/__init__.py create mode 100644 hotel/hotel_app/admin.py create mode 100644 hotel/hotel_app/apps.py create mode 100644 hotel/hotel_app/migrations/0001_initial.py create mode 100644 hotel/hotel_app/migrations/__init__.py create mode 100644 hotel/hotel_app/models.py create mode 100644 hotel/hotel_app/tests.py create mode 100644 hotel/hotel_app/urls.py create mode 100644 hotel/hotel_app/views.py create mode 100644 hotel/manage.py create mode 100644 requirements.txt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9b911c1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +.git +../.gitignore +.dockerignore +.env +.env.local +db.sqlite3 +*.pyc +__pycache__ +*.egg-info +.pytest_cache +.coverage +venv/ +env/ +.idea/ +.vscode/ +*.log +node_modules/ +.DS_Store \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6de6f3c --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# Python +*.py[cod] +*$py.class +__pycache__/ +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual Environment +venv/ +env/ +ENV/ +.venv +.env +.env.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Django +*.sqlite3 +*.sqlite +hotel/db.sqlite3 +/media/ +/staticfiles/ +.django_cache + +# Testing +.coverage +.pytest_cache/ +htmlcov/ + +# Logs +*.log +logs/ + +# OS +.DS_Store +Thumbs.db + +# Database backups +*.sql +*.sql.gz + +# Docker +.docker/ + +# Node (if using with frontend) +node_modules/ +npm-debug.log + +# Misc +.tmp/ +temp/ +*.bak +.idea \ No newline at end of file diff --git a/hotel/Dockerfile b/hotel/Dockerfile new file mode 100644 index 0000000..b4c5559 --- /dev/null +++ b/hotel/Dockerfile @@ -0,0 +1,31 @@ +# Используем официальный образ Python +FROM python:3.14 + +# Установим рабочую директорию +WORKDIR /app + +# Установим переменные окружения +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 + +# Установим зависимости системы +RUN apt-get update && apt-get install -y \ + gcc \ + postgresql-client \ + && rm -rf /var/lib/apt/lists/* + +# Копируем requirements.txt +COPY ../requirements.txt . + +# Устанавливаем Python зависимости +RUN pip install --no-cache-dir -r requirements.txt + +# Копируем весь проект +COPY . . + + +# Открываем порт +EXPOSE 8000 + +# Запускаем сервер +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file diff --git a/hotel/docker-compose.yml b/hotel/docker-compose.yml new file mode 100644 index 0000000..f342096 --- /dev/null +++ b/hotel/docker-compose.yml @@ -0,0 +1,40 @@ +services: + db: + image: postgres:15 + container_name: hotel_db + environment: + POSTGRES_DB: hotel_db + POSTGRES_USER: hotel_user + POSTGRES_PASSWORD: hotel_password + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U hotel_user -d hotel_db"] + interval: 10s + timeout: 5s + retries: 5 + + web: + build: . + container_name: hotel_api + command: > + sh -c "python manage.py migrate && + python manage.py runserver 0.0.0.0:8000" + volumes: + - .:/app + ports: + - "8000:8000" + environment: + DEBUG: "True" + ALLOWED_HOSTS: "localhost,127.0.0.1" + DATABASE_URL: postgresql://hotel_user:hotel_password@db:5432/hotel_db + depends_on: + db: + condition: service_healthy + stdin_open: true + tty: true + +volumes: + postgres_data: \ No newline at end of file diff --git a/hotel/hotel/__init__.py b/hotel/hotel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hotel/hotel/asgi.py b/hotel/hotel/asgi.py new file mode 100644 index 0000000..9aee96b --- /dev/null +++ b/hotel/hotel/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for hotel 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.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hotel.settings') + +application = get_asgi_application() diff --git a/hotel/hotel/settings.py b/hotel/hotel/settings.py new file mode 100644 index 0000000..172ae35 --- /dev/null +++ b/hotel/hotel/settings.py @@ -0,0 +1,123 @@ +""" +Django settings for hotel project. + +Generated by 'django-admin startproject' using Django 5.2.9. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.2/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.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-qd92hjb#a)qrwivifedzj@+lgvpwzn5c065ab@6x9v@ow236%_' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'web', '*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'hotel_app.apps.HotelAppConfig', +] + +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 = 'hotel.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 = 'hotel.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.2/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.2/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.2/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/hotel/hotel/urls.py b/hotel/hotel/urls.py new file mode 100644 index 0000000..cb7cf00 --- /dev/null +++ b/hotel/hotel/urls.py @@ -0,0 +1,26 @@ +""" +URL configuration for hotel project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.2/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 +from hotel_app.views import page_not_found + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('hotel_app.urls')) +] + +handler404 = page_not_found diff --git a/hotel/hotel/wsgi.py b/hotel/hotel/wsgi.py new file mode 100644 index 0000000..0e149f2 --- /dev/null +++ b/hotel/hotel/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for hotel 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.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hotel.settings') + +application = get_wsgi_application() diff --git a/hotel/hotel_app/__init__.py b/hotel/hotel_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hotel/hotel_app/admin.py b/hotel/hotel_app/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/hotel/hotel_app/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/hotel/hotel_app/apps.py b/hotel/hotel_app/apps.py new file mode 100644 index 0000000..3ccb91a --- /dev/null +++ b/hotel/hotel_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HotelAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'hotel_app' diff --git a/hotel/hotel_app/migrations/0001_initial.py b/hotel/hotel_app/migrations/0001_initial.py new file mode 100644 index 0000000..203dd51 --- /dev/null +++ b/hotel/hotel_app/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 5.2.9 on 2025-12-08 15:01 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='HotelRooms', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('description', models.TextField()), + ('price', models.IntegerField()), + ('date_create', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'db_table': 'hotel_rooms', + 'ordering': ['-date_create'], + }, + ), + migrations.CreateModel( + name='Reservations', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('check_in_date', models.DateField()), + ('check_out_date', models.DateField()), + ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotel_app.hotelrooms')), + ], + options={ + 'db_table': 'reservations', + 'ordering': ['check_in_date'], + 'indexes': [models.Index(fields=['room', 'check_in_date'], name='reservation_room_id_26bf11_idx')], + }, + ), + ] diff --git a/hotel/hotel_app/migrations/__init__.py b/hotel/hotel_app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hotel/hotel_app/models.py b/hotel/hotel_app/models.py new file mode 100644 index 0000000..ec24674 --- /dev/null +++ b/hotel/hotel_app/models.py @@ -0,0 +1,31 @@ +from django.db import models + +class HotelRooms(models.Model): + id = models.AutoField(primary_key=True) + description = models.TextField() + price = models.IntegerField() + date_create = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Room #{self.id} - {self.price}$ per night" + + class Meta: + db_table = 'hotel_rooms' + ordering = ['-date_create'] + + +class Reservations(models.Model): + id = models.AutoField(primary_key=True) + room = models.ForeignKey(HotelRooms, on_delete=models.CASCADE) + check_in_date = models.DateField() + check_out_date = models.DateField() + + def __str__(self): + return f"Booking #{self.id} - Room {self.room.id} ({self.check_in_date} to {self.check_out_date})" + + class Meta: + db_table = 'reservations' + ordering = ['check_in_date'] + indexes = [ + models.Index(fields=['room', 'check_in_date']), + ] \ No newline at end of file diff --git a/hotel/hotel_app/tests.py b/hotel/hotel_app/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/hotel/hotel_app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/hotel/hotel_app/urls.py b/hotel/hotel_app/urls.py new file mode 100644 index 0000000..293b9f2 --- /dev/null +++ b/hotel/hotel_app/urls.py @@ -0,0 +1,13 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.index, name='index'), + path('rooms/', views.get_list_hotel_room, name='list_rooms'), + path('rooms/add/', views.add_room, name='add_room'), + path('rooms//delete/', views.delete_room, name='delete_room'), + path('rooms//bookings/', views.get_reservations, name='list_reservations'), + path('bookings/create/', views.create_reservation, name='create_booking'), + path('bookings//delete/', views.delete_reservation, name='delete_booking'), + path('rooms//', views.get_room_detail, name='get_room'), +] \ No newline at end of file diff --git a/hotel/hotel_app/views.py b/hotel/hotel_app/views.py new file mode 100644 index 0000000..1e345ad --- /dev/null +++ b/hotel/hotel_app/views.py @@ -0,0 +1,178 @@ +from django.http import JsonResponse +from django.shortcuts import render +from django.views.decorators.csrf import csrf_exempt +from hotel_app.models import HotelRooms, Reservations +from datetime import datetime + + +def index(request): + return JsonResponse({'message': 'Hotel API is running'}) + +def get_room_detail(request, room_id): + try: + room = HotelRooms.objects.get(pk=room_id) + room_data = { + 'id': room.id, + 'description': room.description, + 'price': room.price, + 'date_created': room.date_created.strftime('%Y-%m-%d') if hasattr(room, 'date_created') else None + } + return JsonResponse(room_data) + except HotelRooms.DoesNotExist: + return JsonResponse({'error': 'Room not found'}, status=404) + + + +def get_list_hotel_room(request): + sort_by = request.GET.get('sort_by', None) + order = request.GET.get('order', 'asc') + + if sort_by and sort_by not in ['price', 'date']: + return JsonResponse({'error': 'sort_by must be "price" or "date"'}, status=400) + + if order not in ['asc', 'desc']: + return JsonResponse({'error': 'order must be "asc" or "desc"'}, status=400) + + if sort_by == 'price': + order_field = '-price' if order == 'desc' else 'price' + rooms = HotelRooms.objects.all().order_by(order_field) + elif sort_by == 'date': + order_field = '-date_create' if order == 'desc' else 'date_create' + rooms = HotelRooms.objects.all().order_by(order_field) + else: + rooms = HotelRooms.objects.all() + + rooms_list = [ + { + 'id': room.id, + 'description': room.description, + 'price': room.price, + 'date_created': room.date_create.strftime('%Y-%m-%d') + } + for room in rooms + ] + + return JsonResponse(rooms_list, safe=False) + +@csrf_exempt +def add_room(request): + + if request.method == 'POST': + description = request.POST.get('description', '').strip() + price = request.POST.get('price', '').strip() + + + if not description: + return JsonResponse({'error': 'description is required'}, status=400) + + if not price: + return JsonResponse({'error': 'price is required'}, status=400) + + try: + price = int(price) + if price < 0: + return JsonResponse({'error': 'price must be positive'}, status=400) + except ValueError: + return JsonResponse({'error': 'price must be an integer'}, status=400) + + # Создание номера + room = HotelRooms.objects.create(description=description, price=price) + return JsonResponse({'room_id': room.id}, status=201) + + return JsonResponse({'error': 'POST method required'}, status=405) + +@csrf_exempt +def delete_room(request, room_id): + + if request.method not in ['DELETE', 'POST']: + return JsonResponse({'error': 'DELETE or POST method required'}, status=405) + + try: + room = HotelRooms.objects.get(pk=room_id) + room.delete() + return JsonResponse({'message': 'Room deleted'}) + except HotelRooms.DoesNotExist: + return JsonResponse({'error': 'Room not found'}, status=404) + + + +def get_reservations(request, room_id): + try: + room = HotelRooms.objects.get(pk=room_id) + + reservations = Reservations.objects.filter(room_id=room_id).order_by('check_in_date') + + reservations_list = [ + { + 'booking_id': res.id, + 'date_start': res.check_in_date.strftime('%Y-%m-%d'), + 'date_end': res.check_out_date.strftime('%Y-%m-%d') + } + for res in reservations + ] + + return JsonResponse(reservations_list, safe=False) + + except HotelRooms.DoesNotExist: + return JsonResponse({'error': 'Room not found'}, status=404) + except Exception as e: + return JsonResponse({'error': str(e)}, status=500) + +@csrf_exempt +def create_reservation(request): + if request.method == 'POST': + room_id = request.POST.get('room_id', '').strip() + date_start = request.POST.get('date_start', '').strip() + date_end = request.POST.get('date_end', '').strip() + + + if not room_id: + return JsonResponse({'error': 'room_id is required'}, status=400) + if not date_start: + return JsonResponse({'error': 'date_start is required'}, status=400) + if not date_end: + return JsonResponse({'error': 'date_end is required'}, status=400) + + + try: + start = datetime.strptime(date_start, '%Y-%m-%d').date() + end = datetime.strptime(date_end, '%Y-%m-%d').date() + except ValueError: + return JsonResponse({'error': 'Invalid date format, use YYYY-MM-DD'}, status=400) + + if start >= end: + return JsonResponse({'error': 'date_start must be before date_end'}, status=400) + + try: + room_id = int(room_id) + room = HotelRooms.objects.get(pk=room_id) + except ValueError: + return JsonResponse({'error': 'room_id must be an integer'}, status=400) + except HotelRooms.DoesNotExist: + return JsonResponse({'error': 'Room not found'}, status=404) + + reservation = Reservations.objects.create( + room=room, + check_in_date=start, + check_out_date=end + ) + + return JsonResponse({'booking_id': reservation.id}, status=201) + + return JsonResponse({'error': 'POST method required'}, status=405) + +@csrf_exempt +def delete_reservation(request, booking_id): + if request.method not in ['DELETE', 'POST']: + return JsonResponse({'error': 'DELETE or POST method required'}, status=405) + + try: + reservation = Reservations.objects.get(pk=booking_id) + reservation.delete() + return JsonResponse({'message': 'Booking deleted'}) + except Reservations.DoesNotExist: + return JsonResponse({'error': 'Booking not found'}, status=404) + + +def page_not_found(request, exception): + return JsonResponse({'error': 'Endpoint not found'}, status=404) \ No newline at end of file diff --git a/hotel/manage.py b/hotel/manage.py new file mode 100644 index 0000000..f6445e0 --- /dev/null +++ b/hotel/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', 'hotel.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 0000000..c49affa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,25 @@ +asgiref==3.11.0 +asttokens==3.0.1 +colorama==0.4.6 +decorator==5.2.1 +Django==5.2.8 +django-extensions==4.1 +executing==2.2.1 +ipython==9.7.0 +ipython_pygments_lexers==1.1.1 +jedi==0.19.2 +matplotlib-inline==0.2.1 +parso==0.8.5 +pip==25.3 +prompt_toolkit==3.0.52 +pure_eval==0.2.3 +Pygments==2.19.2 +sqlparse==0.5.4 +stack-data==0.6.3 +traitlets==5.14.3 +tzdata==2025.2 +uv==0.9.13 +wcwidth==0.2.14 +psycopg==3.1.18 +python-decouple==3.8 +gunicorn==21.2.0 From 7461de872609035b3d7efd3a22934515065273ff Mon Sep 17 00:00:00 2001 From: SEkaya13 Date: Thu, 25 Dec 2025 19:53:03 +0300 Subject: [PATCH 2/3] Update hotel without CBV --- hotel/Dockerfile | 5 +++-- hotel/docker-compose.yml | 2 +- hotel/hotel/settings.py | 17 +++++++++++------ hotel/hotel_app/models.py | 4 ++++ hotel/hotel_app/urls.py | 12 ++++++------ hotel/hotel_app/views.py | 12 ++++++------ requirements.txt | 2 ++ 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/hotel/Dockerfile b/hotel/Dockerfile index b4c5559..d98f149 100644 --- a/hotel/Dockerfile +++ b/hotel/Dockerfile @@ -18,7 +18,8 @@ RUN apt-get update && apt-get install -y \ COPY ../requirements.txt . # Устанавливаем Python зависимости -RUN pip install --no-cache-dir -r requirements.txt +RUN pip install -r requirements.txt uvicorn +COPY .env . # Копируем весь проект COPY . . @@ -28,4 +29,4 @@ COPY . . EXPOSE 8000 # Запускаем сервер -CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file +CMD ["uvicorn", "hotel:application", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/hotel/docker-compose.yml b/hotel/docker-compose.yml index f342096..dd1ef54 100644 --- a/hotel/docker-compose.yml +++ b/hotel/docker-compose.yml @@ -1,6 +1,6 @@ services: db: - image: postgres:15 + image: postgres:18.1 container_name: hotel_db environment: POSTGRES_DB: hotel_db diff --git a/hotel/hotel/settings.py b/hotel/hotel/settings.py index 172ae35..d1d1119 100644 --- a/hotel/hotel/settings.py +++ b/hotel/hotel/settings.py @@ -19,11 +19,11 @@ # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-qd92hjb#a)qrwivifedzj@+lgvpwzn5c065ab@6x9v@ow236%_' +from decouple import config + +SECRET_KEY = config('SECRET_KEY') +DEBUG = config('DEBUG', default=False, cast=bool) -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'web', '*'] @@ -75,12 +75,17 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'hotel_db', + 'USER': 'hotel_user', + 'PASSWORD': config('DB_PASSWORD'), + 'HOST': 'localhost', + 'PORT': '5432', } } + # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators diff --git a/hotel/hotel_app/models.py b/hotel/hotel_app/models.py index ec24674..946f25f 100644 --- a/hotel/hotel_app/models.py +++ b/hotel/hotel_app/models.py @@ -5,6 +5,8 @@ class HotelRooms(models.Model): description = models.TextField() price = models.IntegerField() date_create = models.DateTimeField(auto_now_add=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"Room #{self.id} - {self.price}$ per night" @@ -19,6 +21,8 @@ class Reservations(models.Model): room = models.ForeignKey(HotelRooms, on_delete=models.CASCADE) check_in_date = models.DateField() check_out_date = models.DateField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"Booking #{self.id} - Room {self.room.id} ({self.check_in_date} to {self.check_out_date})" diff --git a/hotel/hotel_app/urls.py b/hotel/hotel_app/urls.py index 293b9f2..1ac14f2 100644 --- a/hotel/hotel_app/urls.py +++ b/hotel/hotel_app/urls.py @@ -4,10 +4,10 @@ urlpatterns = [ path('', views.index, name='index'), path('rooms/', views.get_list_hotel_room, name='list_rooms'), - path('rooms/add/', views.add_room, name='add_room'), - path('rooms//delete/', views.delete_room, name='delete_room'), - path('rooms//bookings/', views.get_reservations, name='list_reservations'), - path('bookings/create/', views.create_reservation, name='create_booking'), - path('bookings//delete/', views.delete_reservation, name='delete_booking'), - path('rooms//', views.get_room_detail, name='get_room'), + path('rooms/add/', views.new_room, name='new_room'), + path('rooms//delete/', views.del_room, name='del_room'), + path('rooms//bookings/', views.reservations, name='list_reservations'), + path('bookings/create/', views.new_reservation, name='new_booking'), + path('bookings//delete/', views.del_reservation, name='del_booking'), + path('rooms//', views.room_detail, name='get_room'), ] \ No newline at end of file diff --git a/hotel/hotel_app/views.py b/hotel/hotel_app/views.py index 1e345ad..e363cba 100644 --- a/hotel/hotel_app/views.py +++ b/hotel/hotel_app/views.py @@ -8,7 +8,7 @@ def index(request): return JsonResponse({'message': 'Hotel API is running'}) -def get_room_detail(request, room_id): +def room_detail(request, room_id): try: room = HotelRooms.objects.get(pk=room_id) room_data = { @@ -55,7 +55,7 @@ def get_list_hotel_room(request): return JsonResponse(rooms_list, safe=False) @csrf_exempt -def add_room(request): +def new_room(request): if request.method == 'POST': description = request.POST.get('description', '').strip() @@ -82,7 +82,7 @@ def add_room(request): return JsonResponse({'error': 'POST method required'}, status=405) @csrf_exempt -def delete_room(request, room_id): +def del_room(request, room_id): if request.method not in ['DELETE', 'POST']: return JsonResponse({'error': 'DELETE or POST method required'}, status=405) @@ -96,7 +96,7 @@ def delete_room(request, room_id): -def get_reservations(request, room_id): +def reservations(request, room_id): try: room = HotelRooms.objects.get(pk=room_id) @@ -119,7 +119,7 @@ def get_reservations(request, room_id): return JsonResponse({'error': str(e)}, status=500) @csrf_exempt -def create_reservation(request): +def new_reservation(request): if request.method == 'POST': room_id = request.POST.get('room_id', '').strip() date_start = request.POST.get('date_start', '').strip() @@ -162,7 +162,7 @@ def create_reservation(request): return JsonResponse({'error': 'POST method required'}, status=405) @csrf_exempt -def delete_reservation(request, booking_id): +def del_reservation(request, booking_id): if request.method not in ['DELETE', 'POST']: return JsonResponse({'error': 'DELETE or POST method required'}, status=405) diff --git a/requirements.txt b/requirements.txt index c49affa..23b220a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,3 +23,5 @@ wcwidth==0.2.14 psycopg==3.1.18 python-decouple==3.8 gunicorn==21.2.0 +uv==0.9.17 +decouple==0.0.7 \ No newline at end of file From eb082b6764e9c87b7d166c690a3082dcf48a0fbe Mon Sep 17 00:00:00 2001 From: SEkaya13 Date: Sun, 11 Jan 2026 15:21:48 +0300 Subject: [PATCH 3/3] Update hotel_project for DRF format --- hotel/hotel/settings.py | 24 +-- hotel/hotel/urls.py | 13 +- hotel/hotel_app/admin.py | 1 - hotel/hotel_app/apps.py | 3 +- hotel/hotel_app/migrations/0001_initial.py | 34 +--- hotel/hotel_app/migrations/0002_initial.py | 38 ++++ hotel/hotel_app/models.py | 12 +- hotel/hotel_app/serializers.py | 12 ++ hotel/hotel_app/urls.py | 13 -- hotel/hotel_app/views.py | 191 +++------------------ 10 files changed, 102 insertions(+), 239 deletions(-) create mode 100644 hotel/hotel_app/migrations/0002_initial.py create mode 100644 hotel/hotel_app/serializers.py delete mode 100644 hotel/hotel_app/urls.py diff --git a/hotel/hotel/settings.py b/hotel/hotel/settings.py index d1d1119..3f94cd4 100644 --- a/hotel/hotel/settings.py +++ b/hotel/hotel/settings.py @@ -11,6 +11,7 @@ """ from pathlib import Path +from decouple import config # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -19,10 +20,9 @@ # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ -from decouple import config SECRET_KEY = config('SECRET_KEY') -DEBUG = config('DEBUG', default=False, cast=bool) +DEBUG = config('DEBUG', default= False, cast=bool) ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'web', '*'] @@ -38,6 +38,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'hotel_app.apps.HotelAppConfig', + 'rest_framework', ] MIDDLEWARE = [ @@ -76,11 +77,11 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'hotel_db', - 'USER': 'hotel_user', + 'NAME': config('DB_NAME'), + 'USER': config('DB_USER'), 'PASSWORD': config('DB_PASSWORD'), - 'HOST': 'localhost', - 'PORT': '5432', + 'HOST': config('DB_HOST', default='localhost'), + 'PORT': config('DB_PORT', default='5432'), } } @@ -108,9 +109,9 @@ # Internationalization # https://docs.djangoproject.com/en/5.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = 'ru' -TIME_ZONE = 'UTC' +TIME_ZONE = 'Europe/Moscow' USE_I18N = True @@ -120,9 +121,12 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.2/howto/static-files/ -STATIC_URL = 'static/' - +STATIC_URL = '/static/' # Default primary key field type # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +REST_FRAMEWORK = { + 'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.OrderingFilter'], +} diff --git a/hotel/hotel/urls.py b/hotel/hotel/urls.py index cb7cf00..4050a75 100644 --- a/hotel/hotel/urls.py +++ b/hotel/hotel/urls.py @@ -15,12 +15,17 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path, include -from hotel_app.views import page_not_found +from django.urls import path, re_path, include +from hotel_app.views import * + urlpatterns = [ path('admin/', admin.site.urls), - path('', include('hotel_app.urls')) + path('api/v1/hotel/', HotelRoomsList.as_view()), + path('api/v1/hotel//', HotelRoomsUpdate.as_view()), + path('api/v1/hoteldelete//', HotelRoomsDestroy.as_view()), + path('api/v1/reservations/', ReservationsList.as_view()), + path('api/v1/reservarions//', ReservationsUpdate.as_view()), + path('api/v1/reservationsdelete//', ReservationsDestroy.as_view()), ] -handler404 = page_not_found diff --git a/hotel/hotel_app/admin.py b/hotel/hotel_app/admin.py index 8c38f3f..c6fe108 100644 --- a/hotel/hotel_app/admin.py +++ b/hotel/hotel_app/admin.py @@ -1,3 +1,2 @@ from django.contrib import admin -# Register your models here. diff --git a/hotel/hotel_app/apps.py b/hotel/hotel_app/apps.py index 3ccb91a..5543c10 100644 --- a/hotel/hotel_app/apps.py +++ b/hotel/hotel_app/apps.py @@ -1,6 +1,5 @@ from django.apps import AppConfig - class HotelAppConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'hotel_app' + name = 'hotel_app' \ No newline at end of file diff --git a/hotel/hotel_app/migrations/0001_initial.py b/hotel/hotel_app/migrations/0001_initial.py index 203dd51..2dd1710 100644 --- a/hotel/hotel_app/migrations/0001_initial.py +++ b/hotel/hotel_app/migrations/0001_initial.py @@ -1,42 +1,12 @@ -# Generated by Django 5.2.9 on 2025-12-08 15:01 +# Generated by Django 5.2.8 on 2026-01-11 11:23 -import django.db.models.deletion -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): - initial = True - dependencies = [ ] operations = [ - migrations.CreateModel( - name='HotelRooms', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('description', models.TextField()), - ('price', models.IntegerField()), - ('date_create', models.DateTimeField(auto_now_add=True)), - ], - options={ - 'db_table': 'hotel_rooms', - 'ordering': ['-date_create'], - }, - ), - migrations.CreateModel( - name='Reservations', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('check_in_date', models.DateField()), - ('check_out_date', models.DateField()), - ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotel_app.hotelrooms')), - ], - options={ - 'db_table': 'reservations', - 'ordering': ['check_in_date'], - 'indexes': [models.Index(fields=['room', 'check_in_date'], name='reservation_room_id_26bf11_idx')], - }, - ), ] diff --git a/hotel/hotel_app/migrations/0002_initial.py b/hotel/hotel_app/migrations/0002_initial.py new file mode 100644 index 0000000..0f7d8cc --- /dev/null +++ b/hotel/hotel_app/migrations/0002_initial.py @@ -0,0 +1,38 @@ +# Generated by Django 5.2.8 on 2026-01-11 11:23 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('hotel_app', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='HotelRooms', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('description', models.TextField()), + ('price', models.IntegerField()), + ('date_create', models.DateTimeField(auto_now_add=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + ), + migrations.CreateModel( + name='Reservations', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('check_in_date', models.DateField()), + ('check_out_date', models.DateField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('room', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='hotel_app.hotelrooms')), + ], + ), + ] diff --git a/hotel/hotel_app/models.py b/hotel/hotel_app/models.py index 946f25f..c2fae6b 100644 --- a/hotel/hotel_app/models.py +++ b/hotel/hotel_app/models.py @@ -9,11 +9,9 @@ class HotelRooms(models.Model): updated_at = models.DateTimeField(auto_now=True) def __str__(self): - return f"Room #{self.id} - {self.price}$ per night" + return f"Room category: '{self.description}' - {self.price}$ per night" + - class Meta: - db_table = 'hotel_rooms' - ordering = ['-date_create'] class Reservations(models.Model): @@ -27,9 +25,3 @@ class Reservations(models.Model): def __str__(self): return f"Booking #{self.id} - Room {self.room.id} ({self.check_in_date} to {self.check_out_date})" - class Meta: - db_table = 'reservations' - ordering = ['check_in_date'] - indexes = [ - models.Index(fields=['room', 'check_in_date']), - ] \ No newline at end of file diff --git a/hotel/hotel_app/serializers.py b/hotel/hotel_app/serializers.py new file mode 100644 index 0000000..b4da788 --- /dev/null +++ b/hotel/hotel_app/serializers.py @@ -0,0 +1,12 @@ +from rest_framework import serializers +from .models import HotelRooms, Reservations + +class HotelRoomSerializer(serializers.ModelSerializer): + class Meta: + model = HotelRooms + fields = '__all__' + +class ReservationSerializer(serializers.ModelSerializer): + class Meta: + model = Reservations + fields = '__all__' \ No newline at end of file diff --git a/hotel/hotel_app/urls.py b/hotel/hotel_app/urls.py deleted file mode 100644 index 1ac14f2..0000000 --- a/hotel/hotel_app/urls.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.urls import path -from . import views - -urlpatterns = [ - path('', views.index, name='index'), - path('rooms/', views.get_list_hotel_room, name='list_rooms'), - path('rooms/add/', views.new_room, name='new_room'), - path('rooms//delete/', views.del_room, name='del_room'), - path('rooms//bookings/', views.reservations, name='list_reservations'), - path('bookings/create/', views.new_reservation, name='new_booking'), - path('bookings//delete/', views.del_reservation, name='del_booking'), - path('rooms//', views.room_detail, name='get_room'), -] \ No newline at end of file diff --git a/hotel/hotel_app/views.py b/hotel/hotel_app/views.py index e363cba..d029ea3 100644 --- a/hotel/hotel_app/views.py +++ b/hotel/hotel_app/views.py @@ -1,178 +1,35 @@ -from django.http import JsonResponse -from django.shortcuts import render -from django.views.decorators.csrf import csrf_exempt -from hotel_app.models import HotelRooms, Reservations -from datetime import datetime +from rest_framework import generics, filters +from .models import Reservations, HotelRooms +from .serializers import HotelRoomSerializer, ReservationSerializer +class HotelRoomsList(generics.ListCreateAPIView): + queryset = HotelRooms.objects.all() + serializer_class = HotelRoomSerializer + filter_backends = [filters.OrderingFilter] + ordering_fields = ['price', 'created_at'] + ordering = ['price'] -def index(request): - return JsonResponse({'message': 'Hotel API is running'}) -def room_detail(request, room_id): - try: - room = HotelRooms.objects.get(pk=room_id) - room_data = { - 'id': room.id, - 'description': room.description, - 'price': room.price, - 'date_created': room.date_created.strftime('%Y-%m-%d') if hasattr(room, 'date_created') else None - } - return JsonResponse(room_data) - except HotelRooms.DoesNotExist: - return JsonResponse({'error': 'Room not found'}, status=404) +class HotelRoomsUpdate(generics.RetrieveUpdateAPIView): + queryset = HotelRooms.objects.all() + serializer_class = HotelRoomSerializer +class HotelRoomsDestroy(generics.RetrieveDestroyAPIView): + queryset = HotelRooms.objects.all() + serializer_class = HotelRoomSerializer -def get_list_hotel_room(request): - sort_by = request.GET.get('sort_by', None) - order = request.GET.get('order', 'asc') +class ReservationsList(generics.ListCreateAPIView): + queryset = Reservations.objects.all() + serializer_class = ReservationSerializer - if sort_by and sort_by not in ['price', 'date']: - return JsonResponse({'error': 'sort_by must be "price" or "date"'}, status=400) - if order not in ['asc', 'desc']: - return JsonResponse({'error': 'order must be "asc" or "desc"'}, status=400) +class ReservationsUpdate(generics.RetrieveUpdateAPIView): + queryset = Reservations.objects.all() + serializer_class = ReservationSerializer - if sort_by == 'price': - order_field = '-price' if order == 'desc' else 'price' - rooms = HotelRooms.objects.all().order_by(order_field) - elif sort_by == 'date': - order_field = '-date_create' if order == 'desc' else 'date_create' - rooms = HotelRooms.objects.all().order_by(order_field) - else: - rooms = HotelRooms.objects.all() - rooms_list = [ - { - 'id': room.id, - 'description': room.description, - 'price': room.price, - 'date_created': room.date_create.strftime('%Y-%m-%d') - } - for room in rooms - ] +class ReservationsDestroy(generics.RetrieveDestroyAPIView): + queryset = Reservations.objects.all() + serializer_class = ReservationSerializer - return JsonResponse(rooms_list, safe=False) - -@csrf_exempt -def new_room(request): - - if request.method == 'POST': - description = request.POST.get('description', '').strip() - price = request.POST.get('price', '').strip() - - - if not description: - return JsonResponse({'error': 'description is required'}, status=400) - - if not price: - return JsonResponse({'error': 'price is required'}, status=400) - - try: - price = int(price) - if price < 0: - return JsonResponse({'error': 'price must be positive'}, status=400) - except ValueError: - return JsonResponse({'error': 'price must be an integer'}, status=400) - - # Создание номера - room = HotelRooms.objects.create(description=description, price=price) - return JsonResponse({'room_id': room.id}, status=201) - - return JsonResponse({'error': 'POST method required'}, status=405) - -@csrf_exempt -def del_room(request, room_id): - - if request.method not in ['DELETE', 'POST']: - return JsonResponse({'error': 'DELETE or POST method required'}, status=405) - - try: - room = HotelRooms.objects.get(pk=room_id) - room.delete() - return JsonResponse({'message': 'Room deleted'}) - except HotelRooms.DoesNotExist: - return JsonResponse({'error': 'Room not found'}, status=404) - - - -def reservations(request, room_id): - try: - room = HotelRooms.objects.get(pk=room_id) - - reservations = Reservations.objects.filter(room_id=room_id).order_by('check_in_date') - - reservations_list = [ - { - 'booking_id': res.id, - 'date_start': res.check_in_date.strftime('%Y-%m-%d'), - 'date_end': res.check_out_date.strftime('%Y-%m-%d') - } - for res in reservations - ] - - return JsonResponse(reservations_list, safe=False) - - except HotelRooms.DoesNotExist: - return JsonResponse({'error': 'Room not found'}, status=404) - except Exception as e: - return JsonResponse({'error': str(e)}, status=500) - -@csrf_exempt -def new_reservation(request): - if request.method == 'POST': - room_id = request.POST.get('room_id', '').strip() - date_start = request.POST.get('date_start', '').strip() - date_end = request.POST.get('date_end', '').strip() - - - if not room_id: - return JsonResponse({'error': 'room_id is required'}, status=400) - if not date_start: - return JsonResponse({'error': 'date_start is required'}, status=400) - if not date_end: - return JsonResponse({'error': 'date_end is required'}, status=400) - - - try: - start = datetime.strptime(date_start, '%Y-%m-%d').date() - end = datetime.strptime(date_end, '%Y-%m-%d').date() - except ValueError: - return JsonResponse({'error': 'Invalid date format, use YYYY-MM-DD'}, status=400) - - if start >= end: - return JsonResponse({'error': 'date_start must be before date_end'}, status=400) - - try: - room_id = int(room_id) - room = HotelRooms.objects.get(pk=room_id) - except ValueError: - return JsonResponse({'error': 'room_id must be an integer'}, status=400) - except HotelRooms.DoesNotExist: - return JsonResponse({'error': 'Room not found'}, status=404) - - reservation = Reservations.objects.create( - room=room, - check_in_date=start, - check_out_date=end - ) - - return JsonResponse({'booking_id': reservation.id}, status=201) - - return JsonResponse({'error': 'POST method required'}, status=405) - -@csrf_exempt -def del_reservation(request, booking_id): - if request.method not in ['DELETE', 'POST']: - return JsonResponse({'error': 'DELETE or POST method required'}, status=405) - - try: - reservation = Reservations.objects.get(pk=booking_id) - reservation.delete() - return JsonResponse({'message': 'Booking deleted'}) - except Reservations.DoesNotExist: - return JsonResponse({'error': 'Booking not found'}, status=404) - - -def page_not_found(request, exception): - return JsonResponse({'error': 'Endpoint not found'}, status=404) \ No newline at end of file