diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..9e7dc17a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +__pycache__/ +.venv +.github +.env diff --git a/cinema/management/commands/wait_for_db.py b/cinema/management/commands/wait_for_db.py new file mode 100644 index 00000000..6321ed17 --- /dev/null +++ b/cinema/management/commands/wait_for_db.py @@ -0,0 +1,18 @@ +import time +from django.db import connections +from django.db.utils import OperationalError +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + def handle(self, *args, **options): + self.stdout.write("Waiting for database...") + db_conn = None + while not db_conn: + try: + db_conn = connections["default"] + db_conn.ensure_connection() + except OperationalError: + self.stdout.write("Database unavailable, waiting 1 second...") + time.sleep(1) + self.stdout.write(self.style.SUCCESS("Database available!")) 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..94530f55 --- /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-27 17:09 + +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..a40a3699 100644 --- a/cinema_service/settings.py +++ b/cinema_service/settings.py @@ -9,8 +9,10 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.0/ref/settings/ """ + from datetime import timedelta from pathlib import Path +import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -86,12 +88,15 @@ DATABASES = { "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", + "ENGINE": "django.db.backends.postgresql", + "NAME": os.environ.get("POSTGRES_DB"), + "USER": os.environ.get("POSTGRES_USER"), + "PASSWORD": os.environ.get("POSTGRES_PASSWORD"), + "HOST": os.environ.get("POSTGRES_HOST"), + "PORT": os.environ.get("POSTGRES_PORT", "5432"), } } - # Password validation # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators @@ -131,10 +136,11 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ -STATIC_URL = "static/" +STATIC_URL = "/static/" +STATIC_ROOT = "/vol/web/static" MEDIA_URL = "/media/" -MEDIA_ROOT = BASE_DIR / "media" +MEDIA_ROOT = "/vol/web/media" # Default primary key field type # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..e815531f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +services: + db: + image: postgres:15-alpine + restart: unless-stopped + volumes: + - postgres_data:/var/lib/postgresql/data + env_file: + - .env + + app: + build: . + restart: unless-stopped + volumes: + - .:/app + - media_files:/vol/web/media + - static_files:/vol/web/static + ports: + - "8000:8000" + env_file: + - .env + command: > + sh -c "python manage.py wait_for_db && + python manage.py migrate && + python manage.py collectstatic --noinput && + python manage.py runserver 0.0.0.0:8000" + depends_on: + - db + +volumes: + postgres_data: + media_files: + static_files: diff --git a/dockerfile b/dockerfile new file mode 100644 index 00000000..bcfeb407 --- /dev/null +++ b/dockerfile @@ -0,0 +1,28 @@ +FROM python:3.11-alpine + +WORKDIR /app + +COPY requirements.txt . + +RUN apk add --update --no-cache postgresql-client jpeg libjpeg \ + && apk add --update --no-cache --virtual .tmp-build-deps \ + gcc libc-dev postgresql-dev musl-dev zlib-dev jpeg-dev \ + && pip install --no-cache-dir -r requirements.txt \ + && apk del .tmp-build-deps + +RUN mkdir -p /vol/web/media /vol/web/static + +RUN adduser \ + --disabled-password \ + --no-create-home \ + django-user + +RUN chown -R django-user:django-user /vol/ + +RUN chmod -R 755 /vol/web/ + +USER django-user + +COPY . . + +EXPOSE 8000 diff --git a/requirements.txt b/requirements.txt index 2dc12c67..b749994e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,12 @@ -django -flake8 -flake8-quotes -flake8-variables-names -pep8-naming -django-debug-toolbar -djangorestframework -djangorestframework-simplejwt -drf-spectacular -Pillow +Django==5.2.13 +django-debug-toolbar==6.3.0 +djangorestframework==3.17.1 +djangorestframework_simplejwt==5.5.1 +drf-spectacular==0.29.0 +flake8==7.3.0 +flake8-quotes==3.4.0 +flake8-variables-names==0.0.6 +pep8-naming==0.15.1 +pillow==12.2.0 +pyflakes==3.4.0 +psycopg2-binary==2.9.12