Skip to content

An error message is added if user enters wrong credentials while login and the UI of /accounts/confirm-email page is fixed #15157

An error message is added if user enters wrong credentials while login and the UI of /accounts/confirm-email page is fixed

An error message is added if user enters wrong credentials while login and the UI of /accounts/confirm-email page is fixed #15157

Workflow file for this run

name: CI/CD Optimized
# SECURITY NOTE:
# - pull_request: Used for test/docker-test jobs that don't need write permissions
# - pull_request_target: Used for pre-commit job to enable commenting/labeling on forked PRs
# When using pull_request_target, we explicitly checkout the PR head to run checks on the actual PR code
# WARNING: This allows execution of pre-commit hooks from forked PRs. The hooks are defined in
# .pre-commit-config.yaml which could be modified by the fork. However, this is necessary to
# validate PR code and is consistent with the repository's pre-commit-fix.yaml workflow.
# Maintainers should review PRs for malicious pre-commit hook modifications.
on:
#merge_group:
pull_request_target:
types:
- opened
- synchronize
- reopened
- ready_for_review
push:
branches:
- main
workflow_dispatch:
workflow_run:
workflows: ["Pre-commit fix"]
types:
- completed
env:
FORCE_COLOR: 1
POETRY_CACHE_DIR: ~/.cache/pypoetry
# Default permissions for the workflow
permissions:
contents: read
concurrency:
cancel-in-progress: false
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
jobs:
setup:
name: Setup and Cache Dependencies
runs-on: ubuntu-latest
permissions:
contents: read # Minimal permission for checking out code
outputs:
python-cache-dir: ${{ steps.poetry-cache.outputs.dir }}
steps:
- uses: actions/checkout@v4
- name: Cache pre-commit hooks
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: |
${{ runner.os }}-pre-commit-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11.2
- name: Get Poetry cache directory
id: poetry-cache
run: echo "POETRY_CACHE_DIR=$(poetry config cache-dir)" >> $GITHUB_ENV
- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
~/.cache/pip
.venv
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}-
${{ runner.os }}-poetry-
- name: Print memory usage
run: free -h
pre-commit:
name: Run pre-commit
# Run on pull_request_target (for write permissions to comment/label), push to main, or manual trigger
needs: setup
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
contents: write
actions: write
steps:
- uses: actions/checkout@v4
with:
# For pull_request_target, explicitly checkout the PR head from the fork
# For pull_request, the default behavior already checks out the PR head
ref: ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.pull_request.head.sha || github.ref }}
# For forked PRs with pull_request_target, checkout from the fork repository
# SECURITY: We run pre-commit which only executes linters/formatters, not arbitrary code from the PR
repository: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name || github.repository }}
- uses: actions/setup-python@v5
with:
python-version: 3.11.2
- name: Run pre-commit
id: pre-commit
run: |
pip install pre-commit
set +e
pre-commit run --all-files > pre-commit-output.txt 2>&1
echo "exit_code=$?" >> $GITHUB_OUTPUT
set -e
continue-on-error: true
- name: Comment on PR if pre-commit fails
if: github.event_name == 'pull_request_target' && steps.pre-commit.outputs.exit_code != '0'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const precommitOutput = fs.readFileSync('pre-commit-output.txt', 'utf8');
// Get last 100 lines of output
const lines = precommitOutput.split('\n');
const last100Lines = lines.slice(-100).join('\n');
const truncated = lines.length > 100;
const message = `## ❌ Pre-commit checks failed
The pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them:
\`\`\`bash
# Install pre-commit if you haven't already
pip install pre-commit
# Run pre-commit on all files
pre-commit run --all-files
# Or run pre-commit on staged files only
pre-commit run
\`\`\`
After running these commands, the pre-commit hooks will automatically fix most issues.
Please review the changes, commit them, and push to your branch.
💡 **Tip**: You can set up pre-commit to run automatically on every commit by running:
\`\`\`bash
pre-commit install
\`\`\`
<details>
<summary>Pre-commit output${truncated ? ' (last 100 lines)' : ''}</summary>
\`\`\`
${last100Lines}
\`\`\`
</details>
For more information, see the [pre-commit documentation](https://pre-commit.com/).`;
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: message
});
} catch (error) {
if (error.status === 403) {
core.warning('Permission denied: Cannot comment on PR. Repository may need to enable "Allow GitHub Actions to create and approve pull requests" in Settings > Actions > General.');
} else {
throw error;
}
}
- name: Add pre-commit status label
if: github.event_name == 'pull_request_target'
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pull_number = context.issue.number;
// Determine label based on pre-commit exit code
const preCommitExitCode = '${{ steps.pre-commit.outputs.exit_code }}';
const newLabel = preCommitExitCode === '0' ? 'pre-commit: passed' : 'pre-commit: failed';
const labelColor = preCommitExitCode === '0' ? '0e8a16' : 'e74c3c'; // Green or Red
const description = preCommitExitCode === '0' ? 'Pre-commit checks passed' : 'Pre-commit checks failed';
// Get current labels on the PR
const { data: current } = await github.rest.issues.listLabelsOnIssue({
owner,
repo,
issue_number: pull_number,
per_page: 100
});
const currentNames = new Set(current.map(l => l.name));
// Remove any existing pre-commit labels
const preCommitRegex = /^pre-commit:/i;
for (const name of currentNames) {
if (preCommitRegex.test(name) && name !== newLabel) {
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: pull_number,
name
});
core.info(`Removed label ${name}`);
} catch (err) {
core.warning(`Failed to remove label ${name}: ${err.message}`);
}
}
}
// Ensure the new label exists (create if missing)
async function ensureLabelExists(labelName) {
try {
await github.rest.issues.getLabel({ owner, repo, name: labelName });
} catch (e) {
if (e.status === 404) {
await github.rest.issues.createLabel({
owner,
repo,
name: labelName,
color: labelColor,
description: description,
});
core.info(`Created label ${labelName}`);
} else {
throw e;
}
}
}
await ensureLabelExists(newLabel);
// Add the label if it isn't already present
if (!currentNames.has(newLabel)) {
try {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: pull_number,
labels: [newLabel]
});
core.info(`Applied label ${newLabel} to PR #${pull_number}`);
} catch (error) {
if (error.status === 403) {
core.warning(`Permission denied: Cannot add label to PR. Repository may need to enable "Allow GitHub Actions to create and approve pull requests" in Settings > Actions > General.`);
} else {
throw error;
}
}
} else {
core.info(`Label ${newLabel} already present on PR #${pull_number}`);
}
core.info(`Pre-commit exit code: ${preCommitExitCode}`);
- name: Fail the job if pre-commit failed
if: steps.pre-commit.outputs.exit_code != '0'
run: exit 1
- name: Print memory usage
run: free -h
test:
name: Run Tests
needs: setup
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
test_result: ${{ steps.run-tests.outputs.exit_code }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-python@v5
with:
python-version: 3.11.2
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y xvfb chromium-browser chromium-chromedriver
- run: pip install poetry
- run: poetry lock
- run: poetry install --with dev
- run: poetry run python manage.py collectstatic --noinput
- name: Run tests
id: run-tests
shell: bash
run: |
set +e
poetry run xvfb-run --auto-servernum python manage.py test -v 3 --failfast 2>&1 | tee test-output.txt
echo "exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
exit 0
continue-on-error: true
- name: Upload test output
if: always()
uses: actions/upload-artifact@v4
with:
name: test-output
path: test-output.txt
retention-days: 1
- name: Fail the job if tests failed
if: steps.run-tests.outputs.exit_code != '0'
run: exit 1
label-test-result:
name: Label Test Result
needs: test
if: always() && github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
actions: read
steps:
- name: Download test output
if: needs.test.outputs.test_result != '0'
uses: actions/download-artifact@v4
with:
name: test-output
path: .
continue-on-error: true
- name: Comment on PR if tests failed
if: needs.test.outputs.test_result != '0'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const pull_number = context.issue.number;
if (!pull_number) {
core.warning('No pull request number found in context. Skipping comment.');
return;
}
let testOutput = 'Test output not available.';
try {
testOutput = fs.readFileSync('test-output.txt', 'utf8');
} catch (e) {
core.warning(`Could not read test output: ${e.message}`);
}
// Get last 100 lines of output
const lines = testOutput.split('\n');
const last100Lines = lines.slice(-100).join('\n');
const truncated = lines.length > 100;
const message = `## ❌ Tests failed
The Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests.
### How to run tests locally
\`\`\`bash
# Install dependencies
poetry install --with dev
# Run all tests
poetry run python manage.py test
# Run tests with verbose output
poetry run python manage.py test -v 3
# Run a specific test
poetry run python manage.py test app.tests.TestClass.test_method
\`\`\`
<details>
<summary>Test output${truncated ? ' (last 100 lines)' : ''}</summary>
\`\`\`text
${last100Lines}
\`\`\`
</details>
For more information, see the [Django testing documentation](https://docs.djangoproject.com/en/stable/topics/testing/).`;
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pull_number,
body: message
});
core.info(`Added test failure comment to PR #${pull_number}`);
} catch (error) {
if (error.status === 403) {
core.warning('Permission denied: Cannot comment on PR. Repository may need to enable "Allow GitHub Actions to create and approve pull requests" in Settings > Actions > General.');
} else {
throw error;
}
}
- name: Add test status label
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pull_number = context.issue.number;
// Validate PR context exists
if (!pull_number) {
core.warning('No pull request number found in context. Skipping label.');
return;
}
core.info(`Processing test result label for PR #${pull_number}`);
// Determine label based on test exit code
const testExitCode = '${{ needs.test.outputs.test_result }}';
core.info(`Test exit code from job output: ${testExitCode}`);
const newLabel = testExitCode === '0' ? 'tests: passed' : 'tests: failed';
const labelColor = testExitCode === '0' ? '0e8a16' : 'e74c3c'; // Green or Red
const description = testExitCode === '0' ? 'Django tests passed' : 'Django tests failed';
// Get current labels on the PR
const { data: current } = await github.rest.issues.listLabelsOnIssue({
owner,
repo,
issue_number: pull_number,
per_page: 100
});
const currentNames = new Set(current.map(l => l.name));
// Remove any existing test labels
const testRegex = /^tests:/i;
for (const name of currentNames) {
if (testRegex.test(name) && name !== newLabel) {
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: pull_number,
name
});
core.info(`Removed label ${name}`);
} catch (err) {
core.warning(`Failed to remove label ${name}: ${err.message}`);
}
}
}
// Ensure the new label exists (create if missing)
async function ensureLabelExists(labelName) {
try {
await github.rest.issues.getLabel({ owner, repo, name: labelName });
} catch (e) {
if (e.status === 404) {
await github.rest.issues.createLabel({
owner,
repo,
name: labelName,
color: labelColor,
description: description,
});
core.info(`Created label ${labelName}`);
} else {
throw e;
}
}
}
await ensureLabelExists(newLabel);
// Add the label if it isn't already present
if (!currentNames.has(newLabel)) {
try {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: pull_number,
labels: [newLabel]
});
core.info(`Applied label ${newLabel} to PR #${pull_number}`);
} catch (error) {
if (error.status === 403) {
core.warning(`Permission denied: Cannot add label to PR. Repository may need to enable "Allow GitHub Actions to create and approve pull requests" in Settings > Actions > General.`);
} else {
throw error;
}
}
} else {
core.info(`Label ${newLabel} already present on PR #${pull_number}`);
}
core.info(`Test exit code: ${testExitCode}`)
docker-test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker
run: |
docker --version
# Use Docker Compose v2 (built-in Docker CLI plugin, no manual installation needed)
docker compose version
- name: Build Docker image
run: |
docker build -t my-app .
- name: Run Docker container
run: |
docker run -d --name my-container my-app
- run: docker exec my-container pip install poetry
- run: docker exec my-container poetry lock
- run: docker exec my-container poetry install --without dev --no-interaction
- name: Clean up
run: |
docker stop my-container
docker rm my-container
- name: Print memory usage
run: free -h