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..d98f149 --- /dev/null +++ b/hotel/Dockerfile @@ -0,0 +1,32 @@ +# Используем официальный образ 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 -r requirements.txt uvicorn +COPY .env . + +# Копируем весь проект +COPY . . + + +# Открываем порт +EXPOSE 8000 + +# Запускаем сервер +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 new file mode 100644 index 0000000..dd1ef54 --- /dev/null +++ b/hotel/docker-compose.yml @@ -0,0 +1,40 @@ +services: + db: + image: postgres:18.1 + 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..3f94cd4 --- /dev/null +++ b/hotel/hotel/settings.py @@ -0,0 +1,132 @@ +""" +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 +from decouple import config + +# 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/ + + +SECRET_KEY = config('SECRET_KEY') +DEBUG = config('DEBUG', default= False, cast=bool) + + +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', + 'rest_framework', +] + +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.postgresql', + 'NAME': config('DB_NAME'), + 'USER': config('DB_USER'), + 'PASSWORD': config('DB_PASSWORD'), + 'HOST': config('DB_HOST', default='localhost'), + 'PORT': config('DB_PORT', default='5432'), + } +} + + + +# 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 = 'ru' + +TIME_ZONE = 'Europe/Moscow' + +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' + +REST_FRAMEWORK = { + 'DEFAULT_FILTER_BACKENDS': ['rest_framework.filters.OrderingFilter'], +} diff --git a/hotel/hotel/urls.py b/hotel/hotel/urls.py new file mode 100644 index 0000000..4050a75 --- /dev/null +++ b/hotel/hotel/urls.py @@ -0,0 +1,31 @@ +""" +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, re_path, include +from hotel_app.views import * + + +urlpatterns = [ + path('admin/', admin.site.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()), +] + 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..c6fe108 --- /dev/null +++ b/hotel/hotel_app/admin.py @@ -0,0 +1,2 @@ +from django.contrib import admin + diff --git a/hotel/hotel_app/apps.py b/hotel/hotel_app/apps.py new file mode 100644 index 0000000..5543c10 --- /dev/null +++ b/hotel/hotel_app/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class HotelAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + 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 new file mode 100644 index 0000000..2dd1710 --- /dev/null +++ b/hotel/hotel_app/migrations/0001_initial.py @@ -0,0 +1,12 @@ +# Generated by Django 5.2.8 on 2026-01-11 11:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + ] 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/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..c2fae6b --- /dev/null +++ b/hotel/hotel_app/models.py @@ -0,0 +1,27 @@ +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) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return f"Room category: '{self.description}' - {self.price}$ per night" + + + + +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() + 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/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/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/views.py b/hotel/hotel_app/views.py new file mode 100644 index 0000000..d029ea3 --- /dev/null +++ b/hotel/hotel_app/views.py @@ -0,0 +1,35 @@ +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'] + + +class HotelRoomsUpdate(generics.RetrieveUpdateAPIView): + queryset = HotelRooms.objects.all() + serializer_class = HotelRoomSerializer + +class HotelRoomsDestroy(generics.RetrieveDestroyAPIView): + queryset = HotelRooms.objects.all() + serializer_class = HotelRoomSerializer + + +class ReservationsList(generics.ListCreateAPIView): + queryset = Reservations.objects.all() + serializer_class = ReservationSerializer + + +class ReservationsUpdate(generics.RetrieveUpdateAPIView): + queryset = Reservations.objects.all() + serializer_class = ReservationSerializer + + +class ReservationsDestroy(generics.RetrieveDestroyAPIView): + queryset = Reservations.objects.all() + serializer_class = ReservationSerializer + 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..23b220a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,27 @@ +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 +uv==0.9.17 +decouple==0.0.7 \ No newline at end of file