diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b7c39d7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +fly.toml +.git/ +*.sqlite3 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cc27296 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +ARG PYTHON_VERSION=3.11-slim-bullseye + +FROM python:${PYTHON_VERSION} + +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# install psycopg2 dependencies. +RUN apt-get update && apt-get install -y \ + libpq-dev \ + gcc \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /code + +WORKDIR /code + +COPY requirements.txt /code/ +RUN pip install --upgrade pip && pip install -r requirements.txt +COPY . /code + +RUN python manage.py collectstatic --noinput + +EXPOSE 8000 + +CMD ["gunicorn", "--bind", ":8000", "--workers", "2", "timesheet.wsgi"] diff --git a/build.sh b/build.sh deleted file mode 100755 index 50bb14e..0000000 --- a/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -# exit on error -set -o errexit - -pip install . - -python manage.py collectstatic --no-input -python manage.py migrate \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e9ce8b4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3.9' + +services: + django-timesheet: + container_name: django-timesheet + build: + context: . + dockerfile: Dockerfile + ports: + - "8000:8000" + volumes: + - ./timesheet:/timesheet + env_file: + - .env + depends_on: + - postgres-timesheet + postgres-timesheet: + container_name: postgres-timesheet + image: postgres:16.1-alpine + ports: + - "5432:5432" + volumes: + - postgres/data:/var/lib/postgresql/data/ + env_file: + - .env \ No newline at end of file diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..4feb0d5 --- /dev/null +++ b/fly.toml @@ -0,0 +1,33 @@ +# fly.toml app configuration file generated for timesheet-hick on 2024-02-04T19:25:37-04:00 +# +# See https://fly.io/docs/reference/configuration/ for information about how to use this file. +# + +app = 'timesheet-hick' +primary_region = 'gru' +console_command = 'manage.py shell' + +[build] + +[deploy] + release_command = 'python manage.py migrate --noinput' + +[env] + PORT = '8000' + +[http_service] + internal_port = 8000 + force_https = true + auto_stop_machines = true + auto_start_machines = true + min_machines_running = 0 + processes = ['app'] + +[[vm]] + cpu_kind = 'shared' + cpus = 1 + memory_mb = 256 + +[[statics]] + guest_path = '/code/static' + url_prefix = '/static/' diff --git a/manage.py b/manage.py index e8777f5..fdf3d68 100755 --- a/manage.py +++ b/manage.py @@ -3,13 +3,10 @@ import os import sys -from dotenv import load_dotenv - def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'timesheet.settings') - load_dotenv() try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/requirements.txt b/requirements.txt index 0f3eb09..2a3a893 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,41 +1,8 @@ -asgiref==3.7.2 -astroid==3.0.2 -black==22.1.0 -blue==0.9.1 -chardet==5.2.0 -click==8.1.7 -colorama==0.4.6 -convertdate==2.4.0 -dill==0.3.7 Django==5.0 -flake8==4.0.1 +dj-database-url==2.1.0 gunicorn==21.2.0 -isort==5.13.2 -lunardate==0.2.2 -mccabe==0.6.1 -mypy-extensions==1.0.0 -packaging==23.2 -pathspec==0.12.1 -Pillow==10.1.0 -platformdirs==4.1.0 -psutil==5.9.7 -psycopg2-binary==2.9.9 -pycodestyle==2.8.0 -pyflakes==2.4.0 -pylint==3.0.3 -pylint-django==2.5.5 -pylint-plugin-utils==0.8.2 -pyluach==2.2.0 -PyMeeus==0.5.12 -python-dateutil==2.8.2 +psycopg2==2.9.9 python-dotenv==1.0.0 reportlab==4.0.8 -ruff==0.1.9 -six==1.16.0 -sqlparse==0.4.4 -taskipy==1.12.2 -timesheet @ file:///home/hick/git/timesheet -tomli==2.0.1 -tomlkit==0.12.3 -whitenoise==6.6.0 workalendar==17.0.0 +whitenoise==6.6.0 diff --git a/timesheet/app/migrations/0009_alter_pointrecord_register_date_and_more.py b/timesheet/app/migrations/0009_alter_pointrecord_register_date_and_more.py new file mode 100644 index 0000000..adae286 --- /dev/null +++ b/timesheet/app/migrations/0009_alter_pointrecord_register_date_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0 on 2024-02-05 00:33 + +import datetime +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0008_pointrecord_is_vacation'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='pointrecord', + name='register_date', + field=models.DateField(default=datetime.date.today), + ), + migrations.AlterUniqueTogether( + name='pointrecord', + unique_together={('user', 'register_date')}, + ), + ] diff --git a/timesheet/app/tests/__init__.py b/timesheet/app/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/timesheet/asgi.py b/timesheet/asgi.py index 2f8dff2..0e93b75 100644 --- a/timesheet/asgi.py +++ b/timesheet/asgi.py @@ -1,9 +1,7 @@ import os from django.core.asgi import get_asgi_application -from dotenv import load_dotenv os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'timesheet.settings') -load_dotenv() application = get_asgi_application() diff --git a/timesheet/settings.py b/timesheet/settings.py index ae3b330..4d2ca40 100644 --- a/timesheet/settings.py +++ b/timesheet/settings.py @@ -1,26 +1,24 @@ import os from pathlib import Path +import dj_database_url +from django.core.management.utils import get_random_secret_key + # 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.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.getenv('SECRET_KEY') +SECRET_KEY = os.getenv('SECRET_KEY', default=get_random_secret_key()) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = bool(int(os.environ.get('DEBUG', 0))) -ALLOWED_HOSTS = [ - h.strip() for h in os.getenv('ALLOWED_HOSTS', '').split(',') if h.strip() -] - -RENDER_EXTERNAL_HOSTNAME = os.environ.get('RENDER_EXTERNAL_HOSTNAME') -if RENDER_EXTERNAL_HOSTNAME: - ALLOWED_HOSTS.append(RENDER_EXTERNAL_HOSTNAME) +APP_NAME = os.environ.get('FLY_APP_NAME') +ALLOWED_HOSTS = ['localhost', '127.0.0.1', f'{APP_NAME}.fly.dev'] +CSRF_TRUSTED_ORIGINS = [f'https://{APP_NAME}.fly.dev'] # Application definition @@ -31,6 +29,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'whitenoise.runserver_nostatic', 'timesheet.app', ] @@ -65,21 +64,23 @@ WSGI_APPLICATION = 'timesheet.wsgi.application' - # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases -DATABASES = { - 'default': { - 'ENGINE': os.getenv('DATABASE_ENGINE'), - 'NAME': os.getenv('DATABASE_NAME'), - 'USER': os.getenv('DATABASE_USER'), - 'PASSWORD': os.getenv('DATABASE_PASSWORD'), - 'HOST': os.getenv('DATABASE_HOST'), - 'PORT': os.getenv('DATABASE_PORT'), +if DEBUG: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } + } +else: + DATABASES = { + 'default': dj_database_url.config( + conn_max_age=600, + conn_health_checks=True, + ), } -} - # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators @@ -99,7 +100,6 @@ }, ] - # Internationalization # https://docs.djangoproject.com/en/5.0/topics/i18n/ @@ -111,22 +111,13 @@ USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ -STATIC_URL = 'static/' - -if not DEBUG: - # Tell Django to copy statics to the `staticfiles` directory - # in your application directory on Render. - STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # noqa +STATIC_URL = '/static/' +STATIC_ROOT = BASE_DIR / 'static' - # Turn on WhiteNoise storage backend that takes care of compressing static files - # and creating unique names for each version so they can safely be cached forever. - STATICFILES_STORAGE = ( - 'whitenoise.storage.CompressedManifestStaticFilesStorage' - ) +STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field diff --git a/timesheet/wsgi.py b/timesheet/wsgi.py index 35f28b1..163760d 100644 --- a/timesheet/wsgi.py +++ b/timesheet/wsgi.py @@ -1,9 +1,7 @@ import os from django.core.wsgi import get_wsgi_application -from dotenv import load_dotenv os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'timesheet.settings') -load_dotenv() application = get_wsgi_application()