From bdc2baa3535e95500952f17891c23042e1ccd645 Mon Sep 17 00:00:00 2001
From: Bryan Grigorie
Date: Thu, 26 Feb 2026 18:21:16 -0500
Subject: [PATCH 1/3] build: pre-commit hooks and initial run
---
.github/workflows/deploy.yml | 4 ++--
.gitmodules | 1 -
.pre-commit-config.yaml | 17 +++++++++++++++++
README.md | 2 ++
api/serializers.py | 2 +-
api/urls.py | 2 +-
assets/base.scss | 8 ++++----
assets/htmx_global.js | 2 +-
assets/index.js | 2 +-
core/utils/turnstile_utils.py | 2 +-
docker-compose.yml | 4 ++--
home/admin.py | 2 +-
home/models.py | 18 +++++++++---------
home/templates/home/blog.html | 2 +-
home/templates/home/blog_post.html | 2 +-
.../home/partials/blog_post_card.html | 2 +-
.../home/partials/blog_post_list.html | 2 +-
.../home/partials/credential_card.html | 2 +-
home/templates/home/partials/pagination.html | 6 +++---
home/templates/home/partials/project_card.html | 6 +++---
home/templates/home/partials/project_list.html | 2 +-
home/templates/home/projects.html | 2 +-
home/tests/test_models.py | 2 +-
portfolio/admin.py | 2 +-
portfolio/apps.py | 2 +-
portfolio/settings.py | 8 ++++----
sponsors/models.py | 2 +-
sponsors/serializers.py | 2 +-
static/admin/js/preview_text_word_count.js | 8 ++++----
static/css/base.css | 2 +-
static/home/css/blog_post.css | 2 +-
static/home/css/index.css | 2 +-
static/sponsors/css/index.css | 2 +-
templates/base.html | 10 +++++-----
users/admin.py | 2 +-
users/auth_backends.py | 5 ++---
users/context_processors.py | 2 +-
users/templates/users/login.html | 2 +-
users/templates/users/partials/login_form.html | 4 ++--
users/views.py | 4 ++--
40 files changed, 86 insertions(+), 69 deletions(-)
create mode 100644 .pre-commit-config.yaml
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 521efe4..5f0a869 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -11,7 +11,7 @@ on:
jobs:
redeploy:
runs-on: ubuntu-latest
- if: >
+ if: >
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push'
steps:
@@ -23,4 +23,4 @@ jobs:
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: sha256=$hash" \
-d @payload.json \
- ${{ secrets.WEBHOOK_URL }}
\ No newline at end of file
+ ${{ secrets.WEBHOOK_URL }}
diff --git a/.gitmodules b/.gitmodules
index 8b13789..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1 +0,0 @@
-
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..29da577
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,17 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v6.0.0
+ hooks:
+ - id: check-yaml
+ args: [--unsafe]
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ - id: detect-private-key
+ - repo: https://github.com/astral-sh/uv-pre-commit
+ rev: 0.10.6
+ hooks:
+ - id: uv-lock
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.15.3
+ hooks:
+ - id: ruff-check
diff --git a/README.md b/README.md
index cef4e53..cea8466 100644
--- a/README.md
+++ b/README.md
@@ -9,10 +9,12 @@ Personal website that includes a portfolio and a blog. Made with [Django](https:
### Dependencies
- ``pyproject.toml``
- ``package.json``
+- ``.pre-commit-config.yaml`` (Git hooks)
#### Installation
- ``uv sync [--group dev]``
- ``npm install``
+- ``uvx pre-commit install`` (Git hooks)
### Environment variables
#### Always required
diff --git a/api/serializers.py b/api/serializers.py
index b752193..aa4c191 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -16,4 +16,4 @@ class Meta:
'project_download_url',
'image_url',
'date',
- ]
\ No newline at end of file
+ ]
diff --git a/api/urls.py b/api/urls.py
index 317fc53..4dc3355 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -4,4 +4,4 @@
urlpatterns = [
path('project//', views.get_project),
-]
\ No newline at end of file
+]
diff --git a/assets/base.scss b/assets/base.scss
index 1b64a35..b7a08d5 100644
--- a/assets/base.scss
+++ b/assets/base.scss
@@ -99,7 +99,7 @@ $md-breakpoint: map-get($grid-breakpoints, "md");
@media (min-width: $md-breakpoint) {
padding-top: 3rem;
}
-
+
.heading-anchor {
text-decoration: none;
@@ -107,7 +107,7 @@ $md-breakpoint: map-get($grid-breakpoints, "md");
text-decoration: underline;
}
}
-
+
.badge {
&:hover {
box-shadow: 0 1px 2px 1px rgba(0, 0, 0, 0.15);
@@ -215,7 +215,7 @@ $md-breakpoint: map-get($grid-breakpoints, "md");
margin-bottom: 0.75rem;
color: $body-color;
}
-
+
.card-subtitle {
letter-spacing: 0.075em;
}
@@ -291,4 +291,4 @@ $md-breakpoint: map-get($grid-breakpoints, "md");
--bs-btn-active-bg: #{darken($white-accent, 15%)};
--bs-btn-active-border-color: #{darken($white-accent, 15%)};
-}
\ No newline at end of file
+}
diff --git a/assets/htmx_global.js b/assets/htmx_global.js
index 58a7ac1..43766a4 100644
--- a/assets/htmx_global.js
+++ b/assets/htmx_global.js
@@ -1 +1 @@
-window.htmx = require('htmx.org');
\ No newline at end of file
+window.htmx = require('htmx.org');
diff --git a/assets/index.js b/assets/index.js
index 13f4fc0..e46db7d 100644
--- a/assets/index.js
+++ b/assets/index.js
@@ -1,4 +1,4 @@
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
import './base.scss';
import 'htmx.org/dist/htmx.min.js';
-import './htmx_global.js';
\ No newline at end of file
+import './htmx_global.js';
diff --git a/core/utils/turnstile_utils.py b/core/utils/turnstile_utils.py
index 36e7c89..8fa967a 100644
--- a/core/utils/turnstile_utils.py
+++ b/core/utils/turnstile_utils.py
@@ -9,7 +9,7 @@ def validate_turnstile(token, secret, remoteip=None):
}
if remoteip:
data['remoteip'] = remoteip
-
+
try:
response = requests.post(url, data=data, timeout=10)
response.raise_for_status()
diff --git a/docker-compose.yml b/docker-compose.yml
index 6e694fa..7420bdd 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,10 +1,10 @@
services:
portfolio:
- build:
+ build:
context: .
dockerfile: Dockerfile
target: development
- command: >
+ command: >
sh -c "
npm run dev &&
python manage.py migrate &&
diff --git a/home/admin.py b/home/admin.py
index 9835e90..cf18715 100644
--- a/home/admin.py
+++ b/home/admin.py
@@ -5,7 +5,7 @@
class BlogPostAdmin(admin.ModelAdmin):
fieldsets = [
- ('Post content',
+ ('Post content',
{'fields': ['post_title', 'slug', 'preview_text', 'post_body']}
),
('Date information', {'fields': ['date_published']}),
diff --git a/home/models.py b/home/models.py
index 43a2f05..c78cdfd 100644
--- a/home/models.py
+++ b/home/models.py
@@ -27,13 +27,13 @@ def is_about_content(self):
@property
def sorted_tags(self):
return self.tags.all().order_by('name')
-
+
@property
def url(self):
if self.slug:
return reverse('home:blog_post_by_slug', kwargs={'slug': self.slug})
return reverse('home:blog_post_by_id', kwargs={'id': self.id})
-
+
def save(self, *args, **kwargs):
if not self.preview_text:
soup = BeautifulSoup(self.post_body, 'html.parser')
@@ -83,15 +83,15 @@ def __str__(self):
return self.skill_title
def level_text(self):
- if self.skill_level == 1:
+ if self.skill_level == 1:
return "Basic knowledge"
- elif self.skill_level == 2:
+ elif self.skill_level == 2:
return "Novice"
- elif self.skill_level == 3:
+ elif self.skill_level == 3:
return "Intermediate"
- elif self.skill_level == 4:
+ elif self.skill_level == 4:
return "Advanced"
- elif self.skill_level == 5:
+ elif self.skill_level == 5:
return "Expert"
@@ -113,7 +113,7 @@ class Project(models.Model):
def __str__(self):
return self.project_title
-
+
@property
def sorted_tags(self):
return self.tags.all().order_by('name')
@@ -130,7 +130,7 @@ class Experience(models.Model):
def __str__(self):
return f"{self.position} at {self.employer}"
-
+
@property
def sorted_tags(self):
return self.tags.all().order_by('name')
diff --git a/home/templates/home/blog.html b/home/templates/home/blog.html
index 9ca2bd1..37fde42 100644
--- a/home/templates/home/blog.html
+++ b/home/templates/home/blog.html
@@ -25,4 +25,4 @@ Blog
window.scrollTo({top: 0, behavior: 'smooth'});
});
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/home/templates/home/blog_post.html b/home/templates/home/blog_post.html
index aa368b9..826df99 100644
--- a/home/templates/home/blog_post.html
+++ b/home/templates/home/blog_post.html
@@ -37,4 +37,4 @@ {{post.post_title}}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/home/templates/home/partials/blog_post_card.html b/home/templates/home/partials/blog_post_card.html
index bc01235..c9588e6 100644
--- a/home/templates/home/partials/blog_post_card.html
+++ b/home/templates/home/partials/blog_post_card.html
@@ -33,4 +33,4 @@
-
\ No newline at end of file
+
diff --git a/home/templates/home/partials/blog_post_list.html b/home/templates/home/partials/blog_post_list.html
index 1d273a3..53355ba 100644
--- a/home/templates/home/partials/blog_post_list.html
+++ b/home/templates/home/partials/blog_post_list.html
@@ -5,4 +5,4 @@
{% endfor %}
{% include 'home/partials/pagination.html' with page=posts hx_target="#blog-post-list" %}
-
\ No newline at end of file
+
diff --git a/home/templates/home/partials/credential_card.html b/home/templates/home/partials/credential_card.html
index c5f1b7f..6dca2bf 100644
--- a/home/templates/home/partials/credential_card.html
+++ b/home/templates/home/partials/credential_card.html
@@ -30,4 +30,4 @@ {{credential.name}}
{% endif %}
-
\ No newline at end of file
+
diff --git a/home/templates/home/partials/pagination.html b/home/templates/home/partials/pagination.html
index 4f2d0d6..30763df 100644
--- a/home/templates/home/partials/pagination.html
+++ b/home/templates/home/partials/pagination.html
@@ -1,7 +1,7 @@
-
\ No newline at end of file
+
diff --git a/home/templates/home/partials/project_card.html b/home/templates/home/partials/project_card.html
index fb85eaf..b604007 100644
--- a/home/templates/home/partials/project_card.html
+++ b/home/templates/home/partials/project_card.html
@@ -19,11 +19,11 @@ {{ project.project_role }}
{% for tag in project.sorted_tags %}
-
{{tag.name}}
@@ -57,4 +57,4 @@
{{ project.project_role }}
{% endif %}
-
\ No newline at end of file
+
diff --git a/home/templates/home/partials/project_list.html b/home/templates/home/partials/project_list.html
index 3a97292..f691056 100644
--- a/home/templates/home/partials/project_list.html
+++ b/home/templates/home/partials/project_list.html
@@ -5,4 +5,4 @@
{% endfor %}
{% include 'home/partials/pagination.html' with page=projects hx_target="#project-list" %}
-
\ No newline at end of file
+
diff --git a/home/templates/home/projects.html b/home/templates/home/projects.html
index 3f719be..3b929b6 100644
--- a/home/templates/home/projects.html
+++ b/home/templates/home/projects.html
@@ -25,4 +25,4 @@ Projects
window.scrollTo({top: 0, behavior: 'smooth'});
});
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/home/tests/test_models.py b/home/tests/test_models.py
index 6263dcc..0fc08c0 100644
--- a/home/tests/test_models.py
+++ b/home/tests/test_models.py
@@ -14,7 +14,7 @@ def setUp(self):
)
BlogPost.objects.create(
date_published=datetime.now(),
- post_title="Post with 51 words",
+ post_title="Post with 51 words",
post_body="" + ("word " * 51).strip() + "
",
)
BlogPost.objects.create(
diff --git a/portfolio/admin.py b/portfolio/admin.py
index fd3f117..4776178 100644
--- a/portfolio/admin.py
+++ b/portfolio/admin.py
@@ -14,7 +14,7 @@ class CustomAdminSite(admin.AdminSite):
@property
def site_title(self):
return f'{getattr(config, 'BRAND')} Admin'
-
+
@property
def site_header(self):
return self.site_title
diff --git a/portfolio/apps.py b/portfolio/apps.py
index 5d78764..8303645 100644
--- a/portfolio/apps.py
+++ b/portfolio/apps.py
@@ -2,4 +2,4 @@
class CustomAdminConfig(AdminConfig):
- default_site = 'portfolio.admin.CustomAdminSite'
\ No newline at end of file
+ default_site = 'portfolio.admin.CustomAdminSite'
diff --git a/portfolio/settings.py b/portfolio/settings.py
index dfc97dc..c1b8d98 100644
--- a/portfolio/settings.py
+++ b/portfolio/settings.py
@@ -29,9 +29,9 @@
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='.localhost', cast=Csv())
-# FIXING CSRF ISSUE
-SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
-# https://stackoverflow.com/a/71482883/1615284
+# FIXING CSRF ISSUE
+SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+# https://stackoverflow.com/a/71482883/1615284
# https://docs.djangoproject.com/en/5.1/ref/settings/#secure-proxy-ssl-header
USE_X_FORWARDED_HOST = True
@@ -250,5 +250,5 @@
'Global': ('BRAND', 'EMAIL', 'DEFAULT_CURRENCY'),
'Links': ('GITHUB_URL', 'LINKEDIN_URL', 'UPWORK_URL', 'RESUME_URL', 'KOFI_URL', 'PAYPAL_URL'),
'Hero Section': ('HERO_HEADING', 'HERO_SUBHEADING'),
- 'Sponsors': ('SPONSOR_DURATION', 'SPONSOR_DONATION_URL', 'SPONSOR_MESSAGE_MINIMUM_CONTRIBUTION'),
+ 'Sponsors': ('SPONSOR_DURATION', 'SPONSOR_DONATION_URL', 'SPONSOR_MESSAGE_MINIMUM_CONTRIBUTION'),
}
diff --git a/sponsors/models.py b/sponsors/models.py
index 21dd30e..fb6a560 100644
--- a/sponsors/models.py
+++ b/sponsors/models.py
@@ -21,7 +21,7 @@ class Sponsorship(models.Model):
def __str__(self):
return f"{self.name}, {self.amount}, {self.currency}, {self.start_date} - {self.expiration_date}, {self.platform}"
-
+
@property
def display_eligible(self):
# TODO determine eligibility for other currencies
diff --git a/sponsors/serializers.py b/sponsors/serializers.py
index 76b2565..55f2b43 100644
--- a/sponsors/serializers.py
+++ b/sponsors/serializers.py
@@ -31,7 +31,7 @@ class Meta:
'is_public',
'timestamp',
]
-
+
def create(self, validated_data):
start_date = validated_data.get('start_date')
expiration_date = start_date + timedelta(days=getattr(config, 'SPONSOR_DURATION'))
diff --git a/static/admin/js/preview_text_word_count.js b/static/admin/js/preview_text_word_count.js
index 076b06c..e925848 100644
--- a/static/admin/js/preview_text_word_count.js
+++ b/static/admin/js/preview_text_word_count.js
@@ -17,18 +17,18 @@
let over = wordCount - 50;
msg = `${msg} (${over} over the limit)`;
}
-
+
let $display = $(selector);
if (!$display.length) {
const $newDisplay = $(`
-
+
`);
$targetContainer.append($newDisplay);
$display = $(selector);
}
-
+
$display.text(`${wordCount} ${msg}`);
}
// Initialize word count
@@ -37,4 +37,4 @@
// Update word count on events
$field.on('keyup change input', updateWordCount);
});
-})(django.jQuery);
\ No newline at end of file
+})(django.jQuery);
diff --git a/static/css/base.css b/static/css/base.css
index f8bd446..9d9e5a0 100644
--- a/static/css/base.css
+++ b/static/css/base.css
@@ -36,4 +36,4 @@ nav a:hover {
#footer {
height: 50px;
}
-*/
\ No newline at end of file
+*/
diff --git a/static/home/css/blog_post.css b/static/home/css/blog_post.css
index 52bfc74..c00e4e0 100644
--- a/static/home/css/blog_post.css
+++ b/static/home/css/blog_post.css
@@ -5,4 +5,4 @@
.post-tags a {
text-decoration: none;
-}
\ No newline at end of file
+}
diff --git a/static/home/css/index.css b/static/home/css/index.css
index 51b99a1..0ad9a41 100644
--- a/static/home/css/index.css
+++ b/static/home/css/index.css
@@ -5,4 +5,4 @@
.blog-post-card {
min-height: 18.25rem;
-}
\ No newline at end of file
+}
diff --git a/static/sponsors/css/index.css b/static/sponsors/css/index.css
index 489655c..cfd9223 100644
--- a/static/sponsors/css/index.css
+++ b/static/sponsors/css/index.css
@@ -1,4 +1,4 @@
#hero p {
line-height: 1.5;
font-size: 1rem;
-}
\ No newline at end of file
+}
diff --git a/templates/base.html b/templates/base.html
index d6bd1fd..d9442eb 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -22,7 +22,7 @@
{% block head_script %}
{% if ADSENSE_CLIENT %}
- {% comment %} {% endcomment %}
{% endif %}
{% if TURNSTILE_SITE_KEY %}
@@ -32,7 +32,7 @@
{% block extra_title %}{% endblock %}{{ config.BRAND }}
-
+
@@ -118,7 +118,7 @@
{% block content %}
{% endblock %}
-
+