diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..82ff08d7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.venv +__pycache__ +.git +.pyc +.env +media/ +staticfiles/* diff --git a/.gitignore b/.gitignore index 0b1609c6..f0443d3d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,19 @@ -.idea/ -.vscode/ -*.iml +# Python envs and caches +.venv +__pycache__/ +*.pyc +# Git +.git +.gitignore +# Env and secrets .env -.DS_Store -venv/ -.pytest_cache/ -**__pycache__/ -**db.sqlite3 -media +# Media and static (не включати в образ) +media/ +staticfiles/ +# IDE / editor +.vscode/ +.idea/ +# Test / build artifacts +dist/ +build/ +*.egg-info/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..7fb9d2d6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.11-slim + +ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 + +WORKDIR /app + +COPY requirements.txt . + +RUN pip install --no-cache-dir -r requirements.txt + +COPY . /app + +RUN mkdir -p /app/media/uploads/movies /app/staticfiles \ + && adduser --disabled-password --no-create-home my_user \ + && chown -R my_user:my_user /app \ + && chmod +x /app/entrypoint.sh + +USER my_user + +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/cinema/management/__init__.py b/cinema/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cinema/management/commands/__init__.py b/cinema/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cinema/management/commands/wait_for_db.py b/cinema/management/commands/wait_for_db.py new file mode 100644 index 00000000..90ddd008 --- /dev/null +++ b/cinema/management/commands/wait_for_db.py @@ -0,0 +1,22 @@ +import time +from django.db.utils import OperationalError +from django.db import connections +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = "Waits for DB to be available" # noqa: VNE003 + + def handle(self, *args, **options): + self.stdout.write("Waiting for database...") + + while True: + try: + db_conn = connections["default"] + db_conn.cursor() + except OperationalError: + self.stdout.write("Database unavailable, waiting 1 second") + time.sleep(1) + else: + self.stdout.write(self.style.SUCCESS("Database available!")) + break diff --git a/cinema/migrations/0002_alter_movie_actors_alter_movie_genres_and_more.py b/cinema/migrations/0002_alter_movie_actors_alter_movie_genres_and_more.py new file mode 100644 index 00000000..0612324e --- /dev/null +++ b/cinema/migrations/0002_alter_movie_actors_alter_movie_genres_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 5.2.13 on 2026-04-20 12:46 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cinema', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='movie', + name='actors', + field=models.ManyToManyField(blank=True, related_name='movies', to='cinema.actor'), + ), + migrations.AlterField( + model_name='movie', + name='genres', + field=models.ManyToManyField(blank=True, related_name='movies', to='cinema.genre'), + ), + migrations.AlterField( + model_name='moviesession', + name='cinema_hall', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='movie_sessions', to='cinema.cinemahall'), + ), + migrations.AlterField( + model_name='moviesession', + name='movie', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='movie_sessions', to='cinema.movie'), + ), + migrations.AlterField( + model_name='order', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/cinema_service/settings.py b/cinema_service/settings.py index 90dde772..e89774dd 100644 --- a/cinema_service/settings.py +++ b/cinema_service/settings.py @@ -9,6 +9,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.0/ref/settings/ """ +import os from datetime import timedelta from pathlib import Path @@ -86,8 +87,12 @@ DATABASES = { "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", + "ENGINE": "django.db.backends.postgresql", + "NAME": os.environ["POSTGRES_DB"], + "USER": os.environ["POSTGRES_USER"], + "PASSWORD": os.environ["POSTGRES_PASSWORD"], + "HOST": os.environ["POSTGRES_HOST"], + "PORT": os.environ["POSTGRES_PORT"], } } @@ -131,7 +136,8 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ -STATIC_URL = "static/" +STATIC_URL = "/static/" +STATIC_ROOT = BASE_DIR / "staticfiles" MEDIA_URL = "/media/" MEDIA_ROOT = BASE_DIR / "media" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..6b32e929 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +services: + cinema: + build: + context: . + env_file: + - .env + ports: + - "8000:8000" + command: python manage.py runserver 0.0.0.0:8000 + volumes: + - my_media:/app/media/uploads/movies + depends_on: + - db + + + db: + image: postgres:16.0-alpine3.17 + restart: always + env_file: + - .env + ports: + - "5432:5432" + volumes: + - my_db:/var/lib/postgresql/data + +volumes: + my_db: + my_media: diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 00000000..6603ff63 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e + +python manage.py wait_for_db +python manage.py migrate +python manage.py collectstatic --noinput + +exec "$@" diff --git a/requirements.txt b/requirements.txt index 2dc12c67..faceb627 100644 Binary files a/requirements.txt and b/requirements.txt differ