diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..45e4f1a2 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,58 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose +{ + "name": "Docker Compose Web", + + // Update the 'dockerComposeFile' list if you have more compose files or use different names. + // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. + "dockerComposeFile": [ + "../docker-compose.yml", + "docker-compose.yml" + ], + + // The 'service' property is the name of the service for the container that VS Code should + // use. Update this value and .devcontainer/docker-compose.yml to the real service name. + "service": "web", + + // The optional 'workspaceFolder' property is the path VS Code should open by default when + // connected. This is typically a file mount in .devcontainer/docker-compose.yml + "workspaceFolder": "/srv/app/", + + // Prevent the the "Git: fatal: detected dubious ownership in repository" error + "postStartCommand": "git config --global --add safe.directory /srv/app", + + "customizations": { + "vscode": { + "extensions": [ + "GitHub.copilot", + "ms-python.python", + "ms-python.black-formatter", + "ms-python.isort" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "bash" + } + } + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Uncomment the next line if you want start specific services in your Docker Compose config. + // "runServices": [], + + // Uncomment the next line if you want to keep your containers running after VS Code shuts down. + // "shutdownAction": "none", + + // Uncomment the next line to run commands after the container is created. + // "postCreateCommand": "cat /etc/os-release", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "devcontainer" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 00000000..df9d6fb3 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3.8' +services: + # Update this to the name of the service you want to work with in your docker-compose.yml file + web: + # Uncomment if you want to override the service's Dockerfile to one in the .devcontainer + # folder. Note that the path of the Dockerfile and context is relative to the *primary* + # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile" + # array). The sample below assumes your primary file is in the root of your project. + # + # build: + # context: . + # dockerfile: .devcontainer/Dockerfile + + volumes: + # Update this to wherever you want VS Code to mount the folder of your project + # - ..:/srv/app:cached + + # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust. + # cap_add: + # - SYS_PTRACE + # security_opt: + # - seccomp:unconfined + + # Overrides default command so things don't shut down after the process ends. + command: /bin/sh -c "while sleep 1000; do :; done" + diff --git a/.gitignore b/.gitignore index 23076b01..db54a35c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ htmlcov latest.dump node_modules public/static/dist +*.pyc +__pycache__/ +.DS_Store \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..7ba9c9ec --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Django Runserver Attach", + "type": "python", + "request": "attach", + "port": 5678, + "host": "localhost", + "justMyCode": false, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "/srv/app" + } + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..56a40ef9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "python.testing.pytestArgs": [ + "." + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + }, + }, + "isort.args": [ + "--profile", + "black" + ], +} diff --git a/README.md b/README.md index 2a383626..71a45d1b 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,32 @@ stop # Stop all docker-compose services test # Run the Django test runner without coverage ``` +## Using VS Code + +### Install the required extensions +* Docker +* Dev Containers + +### Start Project in a Dev Container +* In a terminal run docker compose with `docker compose up` or run the command "Docker: Compose Up" in VS Code. +* Then run the command "Dev Containers: Attach to Running Container..." and choose the "/web" container + +### Debugging Views +* Open the project in a dev container (see above section) +* Set a breakpoint in a Django View +* Go to Run and Debug (CMD+SHIFT+D) +* Click the run icon next to "Django Runserver Attach" +* If you get a prompt that says "Configured debug type 'python' is not supported." Then click "Install python Extension" + and then click the "Install in Dev Container" button. +* In your browser go to the view that has the breakpoint and VS Code should pause at your breakpoint when it's hit. + +### Running Tests +* Open the project in a dev container (see above section) +* Hit CMD+SHIFT+P and search for "Testing: Focus on Test Explorer View". If this doesn't show up make sure you install + the python extension in the dev container. +* Find a test and run it. + + ## Deploying to Production The Django base site is designed to be production ready because it comes with a production diff --git a/config/docker/Dockerfile.web b/config/docker/Dockerfile.web index d1f8a346..17df77ab 100644 --- a/config/docker/Dockerfile.web +++ b/config/docker/Dockerfile.web @@ -1,7 +1,7 @@ # ------------------------------------------------------------ # STAGE 1: Build Python requirements layer # ------------------------------------------------------------ -FROM python:3-buster as python-requirements +FROM python:3-bullseye as python-requirements ARG ENV_NAME=dev @@ -13,8 +13,7 @@ ENV \ VIRTUAL_ENV=/opt/venv RUN set -ex \ - && python3 -m venv $VIRTUAL_ENV \ - && $VIRTUAL_ENV/bin/pip install -U setuptools wheel pip + && python3 -m venv --upgrade-deps $VIRTUAL_ENV # Install Python packages COPY config/requirements/${ENV_NAME}_lock.txt ./config/requirements/${ENV_NAME}_lock.txt @@ -27,18 +26,19 @@ RUN set -ex \ # ------------------------------------------------------------ # STAGE 2: Dev layer # ------------------------------------------------------------ -FROM python:3-slim-buster as dev +FROM python:3-slim-bullseye as dev + +ARG ENV_NAME=dev # Set the locale # libxml2-dev is needed for uwsgi in the production stage # mime-support install (/etc/mime.types) which is needded uWSGI to server static files with the correct mime types RUN apt-get update \ - && apt-get install -y locales libxml2-dev mime-support \ + && apt-get install -y --no-install-recommends locales libxml2-dev mime-support \ && echo "LC_ALL=en_US.UTF-8" >> /etc/environment \ && echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen \ && echo "LANG=en_US.UTF-8" > /etc/locale.conf \ - && locale-gen en_US.UTF-8 \ - && rm -rf /var/lib/apt/lists/* + && locale-gen en_US.UTF-8 ENV VIRTUAL_ENV=/opt/venv ENV LANG=en_US.UTF-8 \ @@ -51,8 +51,12 @@ ENV LANG=en_US.UTF-8 \ WORKDIR /srv/app +# Only install Git if it's the dev environment +RUN if [ "$ENV_NAME" = "dev" ]; then apt-get install -y --no-install-recommends git; fi \ + && rm -rf /var/lib/apt/lists/* + RUN set -ex \ - && groupadd -r app && useradd --uid=1000 --create-home --home-dir=/home/app --no-log-init -r -g app app \ + && groupadd -r app && useradd --uid=2000 --create-home --home-dir=/home/app --no-log-init -r -g app app \ && echo "\nsource ./config/bash/bashrc" >> /home/app/.bashrc COPY --from=python-requirements --chown=app:app $VIRTUAL_ENV $VIRTUAL_ENV diff --git a/config/requirements/dev.in b/config/requirements/dev.in index 0a851cb4..1157a132 100644 --- a/config/requirements/dev.in +++ b/config/requirements/dev.in @@ -5,6 +5,7 @@ -r prod.in bandit[toml] black +debugpy django-debug-toolbar django-stubs django-test-plus diff --git a/config/requirements/dev_lock.txt b/config/requirements/dev_lock.txt index 96251b49..2e13745f 100644 --- a/config/requirements/dev_lock.txt +++ b/config/requirements/dev_lock.txt @@ -437,6 +437,25 @@ cryptography==40.0.2 \ cssbeautifier==1.14.7 \ --hash=sha256:be7f1ea7a7b009f0172c2c0d0bebb2d136346e786f7182185ea944affb52135a # via djlint +debugpy==1.6.6 \ + --hash=sha256:0ea1011e94416e90fb3598cc3ef5e08b0a4dd6ce6b9b33ccd436c1dffc8cd664 \ + --hash=sha256:23363e6d2a04d726bbc1400bd4e9898d54419b36b2cdf7020e3e215e1dcd0f8e \ + --hash=sha256:23c29e40e39ad7d869d408ded414f6d46d82f8a93b5857ac3ac1e915893139ca \ + --hash=sha256:549ae0cb2d34fc09d1675f9b01942499751d174381b6082279cf19cdb3c47cbe \ + --hash=sha256:70ab53918fd907a3ade01909b3ed783287ede362c80c75f41e79596d5ccacd32 \ + --hash=sha256:72687b62a54d9d9e3fb85e7a37ea67f0e803aaa31be700e61d2f3742a5683917 \ + --hash=sha256:78739f77c58048ec006e2b3eb2e0cd5a06d5f48c915e2fc7911a337354508110 \ + --hash=sha256:7aa7e103610e5867d19a7d069e02e72eb2b3045b124d051cfd1538f1d8832d1b \ + --hash=sha256:87755e173fcf2ec45f584bb9d61aa7686bb665d861b81faa366d59808bbd3494 \ + --hash=sha256:9b5d1b13d7c7bf5d7cf700e33c0b8ddb7baf030fcf502f76fc061ddd9405d16c \ + --hash=sha256:a771739902b1ae22a120dbbb6bd91b2cae6696c0e318b5007c5348519a4211c6 \ + --hash=sha256:b9c2130e1c632540fbf9c2c88341493797ddf58016e7cba02e311de9b0a96b67 \ + --hash=sha256:be596b44448aac14eb3614248c91586e2bc1728e020e82ef3197189aae556115 \ + --hash=sha256:c05349890804d846eca32ce0623ab66c06f8800db881af7a876dc073ac1c2225 \ + --hash=sha256:de4a045fbf388e120bb6ec66501458d3134f4729faed26ff95de52a754abddb1 \ + --hash=sha256:dff595686178b0e75580c24d316aa45a8f4d56e2418063865c114eef651a982e \ + --hash=sha256:f6383c29e796203a0bba74a250615ad262c4279d398e89d895a69d3069498305 + # via -r config/requirements/dev.in decorator==5.1.1 \ --hash=sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330 \ --hash=sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186 diff --git a/docker-compose.yml b/docker-compose.yml index 76c8b55a..e65a1658 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,13 +29,14 @@ services: target: dev dockerfile: config/docker/Dockerfile.web - command: sh -c "./manage.py migrate --noinput && ./manage.py runserver 0.0.0.0:8000" + command: sh -c "./manage.py migrate --noinput && /opt/venv/bin/python -Xfrozen_modules=off manage.py runserver 0.0.0.0:8000" volumes: - .:/srv/app:cached ports: - "8000:8000" + - "5678:5678" depends_on: - node diff --git a/manage.py b/manage.py index 9f2c58b8..44f3330c 100755 --- a/manage.py +++ b/manage.py @@ -7,6 +7,21 @@ def main() -> None: """Run administrative tasks.""" os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") + + from django.conf import settings + + if ( + settings.DEBUG + and os.environ.get("PYCHARM_HOSTED") != "1" + and os.environ.get("RUN_MAIN") + or os.environ.get("WERKZEUG_RUN_MAIN") + ): + import debugpy + + # Use "nosec" inline comment to ignore security check because this doesn't run in production + debugpy.listen(("0.0.0.0", 5678)) # nosec + print("Debugpy attached!") + try: from django.core.management import execute_from_command_line except ImportError as exc: