Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ jobs:
PGHOST: localhost
PGUSER: postgres
PGPASSWORD: password
TOX_OVERRIDE: "testenv.pass_env=TOX_OVERRIDE,PG*"
RUFF_OUTPUT_FORMAT: github
TOX_OVERRIDE: "testenv.pass_env=TOX_OVERRIDE,PG*,RUFF_OUTPUT_FORMAT"
run: |
pip install $(grep -E "^(tox|tox-uv)==" requirements/local.txt)
echo "::add-matcher::.github/workflows/matchers/ruff.json"
tox -e ${{ matrix.testenv }}
services:
postgres:
Expand Down
14 changes: 14 additions & 0 deletions {{cookiecutter.project_slug}}/.github/workflows/matchers/ruff.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"problemMatcher": [
{
"owner": "ruff",
"pattern": [
{
"regexp": "^(Would reformat): (.+)$",
"message": 1,
"file": 2
}
]
}
]
}
17 changes: 10 additions & 7 deletions {{cookiecutter.project_slug}}/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@ nuke: ## Full wipe of the local environment, uncommitted files, and database.
nuke: venv-check venv-wipe git-full-clean database-drop

reset: ## Reset your local environment. Useful after switching branches, etc.
reset: venv-check venv-wipe install-local fab-get-backup django-migrate django-user-passwords django-dev-createsuperuser
reset: venv-check venv-wipe install-local fab-get-backup django-migrate django-user-passwords django-dev-createsuperuser django-configure-local-sites

full-reset: ## Reset your local environment and download all media files.
full-reset: venv-check venv-wipe install-local fab-get-data django-migrate django-user-passwords django-dev-createsuperuser
full-reset: venv-check venv-wipe install-local fab-get-data django-migrate django-user-passwords django-dev-createsuperuser django-configure-local-sites

clear: ## Like reset but without the wiping of the installs.
clear: fab-get-backup django-migrate django-user-passwords django-dev-createsuperuser
clear: fab-get-backup django-migrate django-user-passwords django-dev-createsuperuser django-configure-local-sites

full-clear: ## Fresh download of remotely stored data including media files.
full-clear: fab-get-data django-migrate django-user-passwords django-dev-createsuperuser
full-clear: fab-get-data django-migrate django-user-passwords django-dev-createsuperuser django-configure-local-sites

check: ## Check for any obvious errors in the project's setup.
check: pipdeptree-check npm-check django-check
Expand Down Expand Up @@ -108,13 +108,13 @@ pip-install-local: venv-check
fab-get-data: fab-get-backup fab-get-media

fab-get-backup:
fab get_backup
fab $${site:-live} get_backup

fab-get-media:
fab get_media
fab $${site:-live} get_media

fab-deploy:
fab deploy
fab $${site:-live} deploy


# Coverage
Expand Down Expand Up @@ -169,6 +169,9 @@ django-user-passwords:
django-migrate:
./manage.py migrate

django-configure-local-sites:
./manage.py configure_local_sites


# NPM
npm-check: npm-install npm-run-production
Expand Down
126 changes: 99 additions & 27 deletions {{cookiecutter.project_slug}}/apps/accounts/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,128 @@
# Generated by Django 3.2.18 on 2023-04-24 13:16
# Generated by Django 5.2.3 on 2025-07-12 14:09

import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.utils.timezone
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
("auth", "0012_alter_user_first_name_max_length"),
]

operations = [
migrations.CreateModel(
name='Group',
fields=[
],
name="Group",
fields=[],
options={
'proxy': True,
'indexes': [],
'constraints': [],
"proxy": True,
"indexes": [],
"constraints": [],
},
bases=('auth.group',),
bases=("auth.group",),
managers=[
('objects', django.contrib.auth.models.GroupManager()),
("objects", django.contrib.auth.models.GroupManager()),
],
),
migrations.CreateModel(
name='User',
name="User",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(blank=True, null=True, verbose_name="last login"),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name="username",
),
),
(
"first_name",
models.CharField(blank=True, max_length=150, verbose_name="first name"),
),
(
"last_name",
models.CharField(blank=True, max_length=150, verbose_name="last name"),
),
(
"email",
models.EmailField(blank=True, max_length=254, verbose_name="email address"),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.group",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.permission",
verbose_name="user permissions",
),
),
],
options={
'db_table': 'auth_user',
"db_table": "auth_user",
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
("objects", django.contrib.auth.models.UserManager()),
],
),
]
18 changes: 18 additions & 0 deletions {{cookiecutter.project_slug}}/apps/core/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,23 @@ def browsersync(request):
return {"BROWSERSYNC_URL": url}


def sentry_config(request):
"""
Add Sentry config to the global template context.
"""

# Only for dynamic configuration - use sentry_config.js for anything static!
config = {}
if settings.SENTRY_ENVIRONMENT:
config["environment"] = settings.SENTRY_ENVIRONMENT
if settings.SENTRY_RELEASE:
config["release"] = settings.SENTRY_RELEASE

return {
"SENTRY_JS_URL": settings.SENTRY_JS_URL,
"SENTRY_JS_CONFIG": config,
}


def demo(request):
return {"DEMO_SITE": settings.DEMO_SITE}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand, CommandError


class Command(BaseCommand):
help = "Updates Django sites hostnames/ports for local development"

def handle(self, *args, **options):
if settings.DEBUG is False:
raise CommandError(
"Command only to be used for local development (DEBUG must be True)"
)

self.stdout.write(self.style.NOTICE("Django sites for this project:\n"))

port = 8000
for site in Site.objects.order_by("id"):
site.domain = f"127.0.0.1:{port}"
site.save()
self.stdout.write(f" {site.id:2d} - {site.name}")
port += 1000

self.stdout.write(
self.style.NOTICE(
"\nTo run a specific site: run SITE_ID=<site_id> ./manage.py runserver"
)
)
Loading