diff options
-rw-r--r-- | Dockerfile | 34 | ||||
-rw-r--r-- | Makefile | 60 | ||||
-rw-r--r-- | alphabetlearning/pages/apps.py | 4 | ||||
-rw-r--r-- | alphabetlearning/resources/tests/test_forms.py | 8 | ||||
-rw-r--r-- | alphabetlearning/resources/views.py | 26 | ||||
-rw-r--r-- | alphabetlearning/templates/base.html | 4 | ||||
-rw-r--r-- | config/settings/local.py | 2 | ||||
-rw-r--r-- | config/settings/production.py | 10 | ||||
-rw-r--r-- | docker-compose.yaml | 12 | ||||
-rw-r--r-- | pyproject.toml | 22 | ||||
-rw-r--r-- | requirements.txt | 128 | ||||
-rw-r--r-- | uv.lock | 41 |
12 files changed, 142 insertions, 209 deletions
diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9dea447 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# Dockerfile for a Django project + +FROM python:3.12-slim-bookworm + +# The installer requires curl (and certificates) to download the release archive +RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && apt-get install libmagic-dev -y + +# Download the latest installer +ADD https://astral.sh/uv/install.sh /uv-installer.sh + +# Run the installer then remove it +RUN sh /uv-installer.sh && rm /uv-installer.sh + +# Ensure the installed binary is on the `PATH` +ENV PATH="/root/.local/bin/:$PATH" + +ADD . /app + +# Set the working directory to /app +WORKDIR /app + +# Make port 8010 available to the world outside this container +EXPOSE 8020 + +# Define environment variable +ENV DJANGO_SETTINGS_MODULE=config.settings.production +ENV DJANGO_READ_DOT_ENV_FILE=True +ENV DEBUG=False + +RUN uv sync --frozen --no-dev && uv run manage.py collectstatic --noinput + +# run gunicorn when the container launches +# CMD ["uv", "run", "gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8010"] +CMD ["uv", "run", "manage.py", "runserver", "0.0.0.0:8020"] @@ -1,38 +1,30 @@ -all: build run migrate collectstatic - -start: build run migrate collectstatic - -sass: - sass --no-source-map ~/code/python/alphabetlearning/alphabetlearning/static/scss/custom.scss ~/code/python/alphabetlearning/alphabetlearning/static/css/custom.css - -sync-env: - scp .env-prod substracker-web:code/alphabetlearning/.env - -build: - @docker build -f compose/production/django/Dockerfile -t alphabetlearning:latest . - -push: - @docker build -f compose/production/django/Dockerfile -t alphabetlearning:latest . && docker tag alphabetlearning registry.digitalocean.com/twentyfour-registry/pyblackbird && docker push registry.digitalocean.com/twentyfour-registry/pyblackbird - -clean-registry: - @doctl registry garbage-collection start --include-untagged-manifests twentyfour-registry - -run: - @docker run -d --rm --name alphabetlearning_django --env-file .env -p 8080:8080 alphabetlearning - -collectstatic: - @docker run --rm --env-file .env -v .:/app pyblackbird sh -c "python manage.py collectstatic --noinput" - -migrate: - @docker run --rm --env-file .env -v .:/app pyblackbird sh -c "python manage.py makemigrations && python manage.py migrate" - -clean: - @docker stop pyblackbird - @sleep 2 - @docker rmi pyblackbird +deploy-to-staging: +# Define variables +SERVER_USER = jo +SERVER_IP = jo.banded-neon.ts.net +PROJECT_DIR = /home/jo/alphabetlearning + +.PHONY: deploy + +run-development-server: + @echo "Running the development server..." + uv run manage.py runserver + +deploy-to-staging: + @echo "1. Pushing changes to remote repository..." + git push + @echo "2. Logging into the server and pulling latest changes..." + ssh $(SERVER_USER)@$(SERVER_IP) "cd $(PROJECT_DIR) && git pull" + @echo "3. Building the Docker image..." + ssh $(SERVER_USER)@$(SERVER_IP) "cd $(PROJECT_DIR) && docker compose build --no-cache" + @echo "4. Running the Docker container..." + ssh $(SERVER_USER)@$(SERVER_IP) "cd $(PROJECT_DIR) && docker compose up -d" + @echo "5. Migrating database..." + ssh $(SERVER_USER)@$(SERVER_IP) "cd $(PROJECT_DIR) && docker compose exec web uv run manage.py migrate" + @echo "Deployment completed!" test-all: - @pytest -q -s . + @uv run pytest -q -s . test: - @pytest -q -s -m "not slow" + @uv run pytest -q -s -m "not slow" diff --git a/alphabetlearning/pages/apps.py b/alphabetlearning/pages/apps.py index cdd024b..768ce00 100644 --- a/alphabetlearning/pages/apps.py +++ b/alphabetlearning/pages/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class PagesConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'pages' + default_auto_field = "django.db.models.BigAutoField" + name = "alphabetlearning.pages" diff --git a/alphabetlearning/resources/tests/test_forms.py b/alphabetlearning/resources/tests/test_forms.py index aebc110..66d6d43 100644 --- a/alphabetlearning/resources/tests/test_forms.py +++ b/alphabetlearning/resources/tests/test_forms.py @@ -1,14 +1,12 @@ import pytest +from alphabetlearning.resources.factories import ResourceModelFactory +from alphabetlearning.resources.forms import ResourceCreateForm +from alphabetlearning.resources.models import ResourceCategory, ResourceType from django.core.files.uploadedfile import SimpleUploadedFile from django.db import IntegrityError from django.test import TestCase from django.utils.datastructures import MultiValueDict -from alphabetlearning.resources.factories import ResourceModelFactory -from alphabetlearning.resources.forms import ResourceCreateForm -from alphabetlearning.resources.models import ResourceCategory -from alphabetlearning.resources.models import ResourceType - class ResourceCreateFormTest(TestCase): def setUp(self): diff --git a/alphabetlearning/resources/views.py b/alphabetlearning/resources/views.py index 8b0f8c5..508c187 100644 --- a/alphabetlearning/resources/views.py +++ b/alphabetlearning/resources/views.py @@ -9,26 +9,16 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator -from django.db import IntegrityError -from django.db import transaction -from django.shortcuts import get_object_or_404 -from django.shortcuts import redirect -from django.shortcuts import render +from django.db import IntegrityError, transaction +from django.shortcuts import get_object_or_404, redirect, render from . import services -from .forms import ResourceCreateForm -from .forms import ResourceUpdateMetadataForm -from .forms import ResourceUpdatePDFsForm -from .forms import ResourceUpdateThumbnailsForm -from .models import PDFPageSnapshot -from .models import PDFResource -from .models import Resource -from .models import ResourceCategory -from .models import ResourceSubcategory -from .s3 import get_presigned_obj_url -from .s3 import upload_files_to_s3 -from .s3 import upload_snapshotted_pages_to_s3 -from .s3 import upload_to_s3 +from .forms import (ResourceCreateForm, ResourceUpdateMetadataForm, + ResourceUpdatePDFsForm, ResourceUpdateThumbnailsForm) +from .models import (PDFPageSnapshot, PDFResource, Resource, ResourceCategory, + ResourceSubcategory) +from .s3 import (get_presigned_obj_url, upload_files_to_s3, + upload_snapshotted_pages_to_s3, upload_to_s3) logger = logging.getLogger(__name__) diff --git a/alphabetlearning/templates/base.html b/alphabetlearning/templates/base.html index ecab232..2e28333 100644 --- a/alphabetlearning/templates/base.html +++ b/alphabetlearning/templates/base.html @@ -96,12 +96,12 @@ {% if request.user.is_authenticated %} <li class="nav-item"> <a class="nav-link text-dark fw-bold" - href="{% url "payments:cart_detail" %}">Your Basket</a> + href="{% url "payments:cart_detail" %}">Shopping Basket</a> </li> {% else %} <li class="nav-item"> <p class="nav-link text-gray fw-bold"> - Your Basket + Shopping Basket </p> </li> {% endif %} diff --git a/config/settings/local.py b/config/settings/local.py index 07f38f9..9174857 100644 --- a/config/settings/local.py +++ b/config/settings/local.py @@ -14,7 +14,7 @@ SECRET_KEY = env( default="4MZuqVXy7rToppiUhiTBxzlQH8OxPDDmu4rsTqFiyAyLF50WPBVzX5ZFBLpwJPyY", ) # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts -ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] # noqa: S104 +ALLOWED_HOSTS = ["*"] # noqa: S104 # CACHES # ------------------------------------------------------------------------------ diff --git a/config/settings/production.py b/config/settings/production.py index fcc772e..bac2fa5 100644 --- a/config/settings/production.py +++ b/config/settings/production.py @@ -9,7 +9,13 @@ from .base import env # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key SECRET_KEY = env("DJANGO_SECRET_KEY") # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts -ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["alphabetlearning.online"]) +# ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["alphabetlearning.online"]) +ALLOWED_HOSTS = [ + "alphabetlearning.online", + "www.alphabetlearning.online", + "staging.alphabetlearning.online", + "www.staging.alphabetlearning.online", +] # DATABASES # ------------------------------------------------------------------------------ @@ -145,7 +151,7 @@ LOGGING = { "file": { "level": "DEBUG", "class": "logging.FileHandler", - "filename": "/home/django/logfile.log", # Update this path + "filename": "/app/logfile.log", # Update this path }, }, "loggers": { diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..7692ef9 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,12 @@ +services: + web: + # image: haircode/alphabetlearning:latest + build: + context: . + network: host + ports: + - "8020:8020" + volumes: + - .:/app + command: uv run manage.py runserver 0.0.0.0:8020 + diff --git a/pyproject.toml b/pyproject.toml index 4bf01be..0cb6687 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,5 @@ [project] +requires-python = ">=3.11" name = "alphabetlearning" version = "0.1.0" dependencies = [ @@ -38,26 +39,25 @@ dependencies = [ "sqlparse==0.5.0", "markdown==3.6.0", "Werkzeug[watchdog]==3.0.2", - # https://github.com/pallets/werkzeug - "psycopg[c]==3.1.19", # https://github.com/psycopg/psycopg "watchfiles==0.21.0", # https://github.com/samuelcolvin/watchfiles "stripe==11.1.0", "django-stubs[compatible-mypy]>=5.0.4", # "django-ratelimit==4.1.0", - "django-recaptcha==4.0.0" + "django-recaptcha==4.0.0", ] [tool.uv] # https://docs.astral.sh/uv/concepts/dependencies/#development-dependencies dev-dependencies = [ - "factory-boy==3.3.0", # https://github.com/FactoryBoy/factory_boy - "django-coverage-plugin==3.1.0", # https://github.com/nedbat/django_coverage_plugin - "pytest-django==4.8.0", # https://github.com/pytest-dev/pytest-django - "ruff==0.6.4", # https://github.com/astral-sh/ruff - "coverage==7.5.1", # https://github.com/nedbat/coveragepy - "pytest==8.2.0", # https://github.com/pytest-dev/pytest - "pytest-sugar==1.0.0", # https://github.com/Frozenball/pytest-sugar - "pdbpp==0.10.3" + "factory-boy==3.3.0", # https://github.com/FactoryBoy/factory_boy + "django-coverage-plugin==3.1.0", # https://github.com/nedbat/django_coverage_plugin + "pytest-django==4.8.0", # https://github.com/pytest-dev/pytest-django + "ruff==0.6.4", # https://github.com/astral-sh/ruff + "coverage==7.5.1", # https://github.com/nedbat/coveragepy + "pytest==8.2.0", # https://github.com/pytest-dev/pytest + "pytest-sugar==1.0.0", # https://github.com/Frozenball/pytest-sugar + "pdbpp==0.10.3", + "isort>=5.13.2", ] # ==== pytest ==== diff --git a/requirements.txt b/requirements.txt index 63d441d..973627b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,143 +1,59 @@ # This file was autogenerated by uv via the following command: -# uv pip compile pyproject.toml -o requirements.txt -anyio==4.7.0 - # via watchfiles +# uv export --no-dev --no-hashes --format requirements-txt +anyio==4.4.0 argon2-cffi==23.1.0 - # via alphabetlearning (pyproject.toml) argon2-cffi-bindings==21.2.0 - # via argon2-cffi asgiref==3.8.1 - # via - # django - # django-stubs +async-timeout==4.0.3 ; python_full_version < '3.11.3' boto3==1.34.89 - # via alphabetlearning (pyproject.toml) botocore==1.34.162 - # via - # alphabetlearning (pyproject.toml) - # boto3 - # s3transfer -certifi==2024.12.14 - # via requests +certifi==2024.8.30 cffi==1.17.1 - # via argon2-cffi-bindings -charset-normalizer==3.4.1 - # via requests +charset-normalizer==3.3.2 +colorama==0.4.6 ; sys_platform == 'win32' crispy-bootstrap5==2024.2 - # via alphabetlearning (pyproject.toml) django==5.1.4 - # via - # alphabetlearning (pyproject.toml) - # crispy-bootstrap5 - # django-allauth - # django-crispy-forms - # django-model-utils - # django-recaptcha - # django-redis - # django-storages - # django-stubs - # django-stubs-ext django-allauth==0.62.1 - # via alphabetlearning (pyproject.toml) django-crispy-forms==2.1 - # via - # alphabetlearning (pyproject.toml) - # crispy-bootstrap5 django-environ==0.11.2 - # via alphabetlearning (pyproject.toml) django-model-utils==4.5.1 - # via alphabetlearning (pyproject.toml) django-recaptcha==4.0.0 - # via alphabetlearning (pyproject.toml) django-redis==5.4.0 - # via alphabetlearning (pyproject.toml) django-storages==1.14.3 - # via alphabetlearning (pyproject.toml) -django-stubs==5.1.1 - # via alphabetlearning (pyproject.toml) -django-stubs-ext==5.1.1 - # via django-stubs +django-stubs==5.0.4 +django-stubs-ext==5.0.4 gunicorn==22.0.0 - # via alphabetlearning (pyproject.toml) -idna==3.10 - # via - # anyio - # requests +idna==3.8 jmespath==1.0.1 - # via - # boto3 - # botocore markdown==3.6 - # via alphabetlearning (pyproject.toml) -markupsafe==3.0.2 - # via werkzeug -mypy==1.13.0 - # via django-stubs +markupsafe==2.1.5 +mypy==1.11.2 mypy-extensions==1.0.0 - # via mypy -packaging==24.2 - # via gunicorn +packaging==24.1 pdf2image==1.17.0 - # via alphabetlearning (pyproject.toml) pillow==10.3.0 - # via - # alphabetlearning (pyproject.toml) - # pdf2image psycopg==3.1.19 - # via alphabetlearning (pyproject.toml) -psycopg-c==3.1.19 - # via psycopg +psycopg-c==3.1.19 ; implementation_name != 'pypy' pycparser==2.22 - # via cffi pypdf2==3.0.1 - # via alphabetlearning (pyproject.toml) +pypng==0.20220715.0 python-dateutil==2.9.0.post0 - # via botocore -python-dotenv==1.0.1 - # via alphabetlearning (pyproject.toml) python-magic==0.4.27 - # via alphabetlearning (pyproject.toml) python-slugify==8.0.4 - # via alphabetlearning (pyproject.toml) -qrcode==8.0 - # via django-allauth -redis==5.2.1 - # via django-redis +qrcode==7.4.2 +redis==5.0.4 requests==2.32.3 - # via stripe -s3transfer==0.10.4 - # via boto3 -six==1.17.0 - # via python-dateutil +s3transfer==0.10.2 +six==1.16.0 sniffio==1.3.1 - # via anyio sqlparse==0.5.0 - # via - # alphabetlearning (pyproject.toml) - # django stripe==11.1.0 - # via alphabetlearning (pyproject.toml) text-unidecode==1.3 - # via python-slugify -types-pyyaml==6.0.12.20241230 - # via django-stubs +types-pyyaml==6.0.12.20240808 typing-extensions==4.12.2 - # via - # anyio - # django-stubs - # django-stubs-ext - # mypy - # psycopg - # stripe -urllib3==2.3.0 - # via - # botocore - # requests -watchdog==6.0.0 - # via werkzeug +tzdata==2024.1 ; sys_platform == 'win32' +urllib3==2.2.2 +watchdog==5.0.2 watchfiles==0.21.0 - # via alphabetlearning (pyproject.toml) werkzeug==3.0.2 - # via alphabetlearning (pyproject.toml) whitenoise==6.6.0 - # via alphabetlearning (pyproject.toml) @@ -23,7 +23,6 @@ dependencies = [ { name = "markdown" }, { name = "pdf2image" }, { name = "pillow" }, - { name = "psycopg", extra = ["c"] }, { name = "pypdf2" }, { name = "python-magic" }, { name = "python-slugify" }, @@ -39,6 +38,7 @@ dev = [ { name = "coverage" }, { name = "django-coverage-plugin" }, { name = "factory-boy" }, + { name = "isort" }, { name = "pdbpp" }, { name = "pytest" }, { name = "pytest-django" }, @@ -65,7 +65,6 @@ requires-dist = [ { name = "markdown", specifier = "==3.6.0" }, { name = "pdf2image", specifier = "==1.17.0" }, { name = "pillow", specifier = "==10.3.0" }, - { name = "psycopg", extras = ["c"], specifier = "==3.1.19" }, { name = "pypdf2", specifier = "==3.0.1" }, { name = "python-magic", specifier = "==0.4.27" }, { name = "python-slugify", specifier = "==8.0.4" }, @@ -81,6 +80,7 @@ dev = [ { name = "coverage", specifier = "==7.5.1" }, { name = "django-coverage-plugin", specifier = "==3.1.0" }, { name = "factory-boy", specifier = "==3.3.0" }, + { name = "isort", specifier = ">=5.13.2" }, { name = "pdbpp", specifier = "==0.10.3" }, { name = "pytest", specifier = "==8.2.0" }, { name = "pytest-django", specifier = "==4.8.0" }, @@ -505,7 +505,7 @@ name = "fancycompleter" version = "0.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyreadline", marker = "platform_system == 'Windows'" }, + { name = "pyreadline", marker = "sys_platform == 'win32'" }, { name = "pyrepl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/95/649d135442d8ecf8af5c7e235550c628056423c96c4bc6787348bdae9248/fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272", size = 10866 } @@ -544,6 +544,15 @@ wheels = [ ] [[package]] +name = "isort" +version = "5.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 }, +] + +[[package]] name = "jmespath" version = "1.0.1" source = { registry = "https://pypi.org/simple" } @@ -696,30 +705,6 @@ wheels = [ ] [[package]] -name = "psycopg" -version = "3.1.19" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, - { name = "tzdata", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e5/b8/dc85a3b5d3576527c288197de5db85edd141d6ce27fcf73e9f77e871824a/psycopg-3.1.19.tar.gz", hash = "sha256:92d7b78ad82426cdcf1a0440678209faa890c6e1721361c2f8901f0dccd62961", size = 147457 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/cf/172701ea48987548c681e011681dda1d2605131f723e89225477fe7802e9/psycopg-3.1.19-py3-none-any.whl", hash = "sha256:dca5e5521c859f6606686432ae1c94e8766d29cc91f2ee595378c510cc5b0731", size = 179386 }, -] - -[package.optional-dependencies] -c = [ - { name = "psycopg-c", marker = "implementation_name != 'pypy'" }, -] - -[[package]] -name = "psycopg-c" -version = "3.1.19" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/64/7e/1b75babba770e9869d27e2aed23ee5d903c9aa748d8dd1ef783c9cca1de5/psycopg_c-3.1.19.tar.gz", hash = "sha256:8e90f53c430e7d661cb3a9298e2761847212ead1b24c5fb058fc9d0fd9616017", size = 562146 } - -[[package]] name = "pycparser" version = "2.22" source = { registry = "https://pypi.org/simple" } @@ -846,7 +831,7 @@ name = "qrcode" version = "7.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "pypng" }, { name = "typing-extensions" }, ] |