Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7cae08e
Add 3 redesign proposals for eduzen.ar homepage
claude Mar 15, 2026
3128254
Add Playwright screenshots of all three redesign proposals
claude Mar 15, 2026
2fa2a44
Merge branch 'main' into claude/redesign-proposals-TvgFU
Mar 15, 2026
9686920
Add Django view to serve proposal HTML files locally
claude Mar 15, 2026
cf4b4cd
Merge branch 'claude/redesign-proposals-TvgFU' of github.com:eduzen/w…
Mar 15, 2026
b752a16
feat: wip
Mar 15, 2026
0fa1fe7
feat: enhance blog search functionality with PostgreSQL support
Mar 25, 2026
b93f5fa
full
eduzen Mar 30, 2026
2b86636
feat: enhance justfile and forms for improved functionality
Mar 31, 2026
3e4aebc
feat: enhance justfile and forms for improved functionality
Mar 31, 2026
3e91a21
fix: correct Spanish translation for software engineer description
Apr 1, 2026
64cc922
feat: add decorative waves, floating particles, and B&W photo to hero
Apr 2, 2026
b1e352e
fix: reserve hero name height and fix Spanish e2e contact test
Apr 2, 2026
86630a9
feat: animate waves with dreamy drift + sway, fix mobile wave shape
Apr 2, 2026
4afde32
fix: cleanup from /simplify review — consolidate motion, rename class…
Apr 2, 2026
f115ba2
feat: redesign about and classes pages with warm design system
Apr 3, 2026
d1deaa9
feat: add mentoring page and Teaching nav dropdown
Apr 3, 2026
b82227d
fix: close Teaching dropdown on click away
Apr 3, 2026
4516430
fix: rewrite nav dropdown for Alpine.js v2 compatibility
Apr 3, 2026
b60500b
feat: upgrade Alpine.js from v2.3.0 to v3
Apr 3, 2026
1c16494
fix: cleanup from /simplify review — remove dead CSS, consolidate car…
Apr 3, 2026
46e1b29
fix: E2E language dropdown selector broken by Teaching dropdown
Apr 3, 2026
fa86a4b
fix: language dropdown stale href after HTMX navigation (Alpine v3)
Apr 3, 2026
0f67110
fix: skip Telegram in tests and add request timeout
Apr 3, 2026
ec1cea2
fix: click confirm dialog in E2E tests, fix mypy dict type
Apr 3, 2026
ddb93de
update to django 6
Apr 19, 2026
c8306ae
fix: use named volume for uv venv to prevent rebuilds on every run
eduzen Apr 26, 2026
2d99462
fix: remove django-template-partials dependency, fix test assertions …
eduzen Apr 26, 2026
959f469
feat: more updates
Apr 29, 2026
0ad964a
feat: updates
eduzen May 8, 2026
6786353
feat: updates htmx
eduzen May 8, 2026
4040df5
test: raise per-file coverage to threshold
eduzen May 8, 2026
f5fcd50
feat: remove isort, make py314 default
eduzen May 8, 2026
d2d10c0
feat: add more ruff capabilities
eduzen May 8, 2026
487fc2c
Merge branch 'claude/redesign-proposals-TvgFU' of github.com:eduzen/w…
May 16, 2026
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,8 @@ data/*
secrets

.playwright-mcp/
.screenshots/
screenshots/
.superpowers/

.claude/settings.local.json
19 changes: 7 additions & 12 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ repos:
- id: debug-statements

- repo: https://github.com/crate-ci/typos
rev: v1
rev: v1.46.1
hooks:
- id: typos
exclude: ^.*htmx\.min\..*\.js$
exclude: ^(.*htmx\.min\..*\.js|core/static/core/vendor/.*)$

- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.25
Expand All @@ -34,23 +34,18 @@ repos:
rev: "1.9.0"
hooks:
- id: djade
args: [--target-version, "5.1"]
args: [--target-version, "6.0"]

- repo: https://github.com/adamchainz/django-upgrade
rev: "1.30.0"
hooks:
- id: django-upgrade

- repo: https://github.com/asottile/pyupgrade
rev: v3.21.2
hooks:
- id: pyupgrade
args: [--py313]
args: [--target-version, "6.0"]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.15.6'
rev: 'v0.15.12'
hooks:
- id: ruff-check
args: ["--fix", "--target-version", "py313"]
args: ["--fix", "--target-version", "py314"]
- id: ruff-format
args: ["--target-version", "py313"]
args: ["--target-version", "py314"]
26 changes: 26 additions & 0 deletions blog/apps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
from django.apps import AppConfig
from django.contrib.postgres.search import SearchVector
from django.db import connections
from django.db.models import Model
from django.db.models.signals import post_save


def update_search_vector(sender: type[Model], instance: Model, **kwargs: object) -> None:
"""Keep the PostgreSQL search vector in sync after model saves."""
using = kwargs.get("using")
db_alias = using if isinstance(using, str) else "default"
if connections[db_alias].vendor != "postgresql":
return

sender._default_manager.using(db_alias).filter(pk=instance.pk).update(
search_vector=(
SearchVector("title", weight="A") + SearchVector("summary", weight="B") + SearchVector("text", weight="C")
)
)


class BlogConfig(AppConfig):
name = "blog"

def ready(self) -> None:
post_save.connect(
update_search_vector,
sender=self.get_model("Post"),
dispatch_uid="blog.post.update_search_vector",
weak=False,
)
9 changes: 3 additions & 6 deletions blog/filters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import django_filters
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
from django.contrib.postgres.search import SearchQuery, SearchRank
from django.db.models import QuerySet

from blog.models import Post
Expand All @@ -17,11 +17,8 @@ def filter_search(self, queryset: QuerySet[Post], name: str, value: str) -> Quer
return queryset

search_query = SearchQuery(value)
search_vector = (
SearchVector("title", weight="A") + SearchVector("summary", weight="B") + SearchVector("text", weight="C")
)
return (
queryset.annotate(search=search_vector, rank=SearchRank(search_vector, search_query))
.filter(search=search_query)
queryset.filter(search_vector=search_query)
.annotate(rank=SearchRank("search_vector", search_query))
.order_by("-rank")
)
32 changes: 16 additions & 16 deletions blog/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@
from django import forms
from django.conf import settings
from django.core.mail import send_mail
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _

from blog.services.captcha import verify_captcha

log = logging.getLogger(__name__)

msg = mark_safe(f"<span class='text-white'>{_('Message')}</span>")
email_label = mark_safe(f"<span class='text-white'>{_('Email')}</span>")
name_label = mark_safe(f"<span class='text-white'>{_('Name')}</span>")
captcha_label = mark_safe(f"<span class='text-white'>{_('What color is the red rabbit?')}</span>")

class InvalidCaptchaValidationError(forms.ValidationError):
def __init__(self) -> None:
super().__init__("invalid captcha")


class EmailForm(forms.Form):
Expand Down Expand Up @@ -54,25 +53,26 @@ class AdvanceSearchForm(forms.Form):


class ContactForm(forms.Form):
name = forms.CharField(label=name_label)
email = forms.EmailField(label=email_label)
message = forms.CharField(label=msg, widget=forms.Textarea)
captcha = forms.CharField(label=captcha_label)
name = forms.CharField(label=_("Name"))
email = forms.EmailField(label=_("Email"))
message = forms.CharField(label=_("Message"), widget=forms.Textarea)
captcha = forms.CharField(label=_("What color is the red rabbit?"))

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Field("name", css_class="mt-1 p-2 w-full rounded-md text-gray-800"),
Field("email", css_class="mt-1 p-2 w-full rounded-md text-gray-800"),
Field("message", css_class="mt-1 p-2 w-full resize rounded-md text-gray-800", rows=6),
Field("captcha", css_class="mt-1 p-2 w-full rounded-md text-gray-800"),
Submit("submit", _("Send"), css_class="px-4 py-2 bg-purple-500 rounded hover:bg-pink-300"),
Field("name"),
Field("email"),
Field("message", rows=6),
Field("captcha"),
Submit("submit", _("Send")),
)

def clean_captcha(self) -> str:
# Get the cleaned value (after built-in validation checks)
captcha = self.cleaned_data.get("captcha", "")
if not verify_captcha(captcha):
raise forms.ValidationError("invalid captcha")
raise InvalidCaptchaValidationError()
return captcha
Loading