aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Dockerfile34
-rw-r--r--Makefile60
-rw-r--r--alphabetlearning/pages/apps.py4
-rw-r--r--alphabetlearning/resources/tests/test_forms.py8
-rw-r--r--alphabetlearning/resources/views.py26
-rw-r--r--alphabetlearning/templates/base.html4
-rw-r--r--config/settings/local.py2
-rw-r--r--config/settings/production.py10
-rw-r--r--docker-compose.yaml12
-rw-r--r--pyproject.toml22
-rw-r--r--requirements.txt128
-rw-r--r--uv.lock41
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"]
diff --git a/Makefile b/Makefile
index de27106..77d18b4 100644
--- a/Makefile
+++ b/Makefile
@@ -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)
diff --git a/uv.lock b/uv.lock
index 16e22dc..b6bea5e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -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" },
]