App: Projects¶
Phase 2. Manages and displays portfolio projects dynamically via Django models and admin.
Responsibility¶
- Activated in Phase 2
ProjectandTagmodels managed through Django admin- HTMX-powered tag filtering without full page reloads
Models¶
# apps/projects/models.py
class Tag(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(unique=True)
def __str__(self):
return self.name
class Project(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
description = models.TextField() # short — used in cards
body = models.TextField() # long write-up — used in detail view
tags = models.ManyToManyField(Tag, blank=True)
github_url = models.URLField(blank=True)
live_url = models.URLField(blank=True)
featured = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
URL Patterns¶
| URL | View | Name |
|---|---|---|
/projects/ |
ProjectListView |
projects:list |
/projects/<slug>/ |
ProjectDetailView |
projects:detail |
HTMX Pattern: Tag Filtering¶
Clicking a tag makes a GET request with a query parameter. The server detects it's an HTMX request and returns only the project grid fragment, not the full page.
<!-- Tag filter buttons -->
<button hx-get="{% url 'projects:list' %}?tag={{ tag.slug }}"
hx-target="#project-grid"
hx-swap="innerHTML"
hx-push-url="true">
{{ tag.name }}
</button>
<!-- Target container -->
<div id="project-grid">
{% include "partials/_project_grid.html" %}
</div>
# apps/projects/views.py
async def project_list(request):
tag_slug = request.GET.get('tag')
projects = Project.objects.all()
if tag_slug:
projects = projects.filter(tags__slug=tag_slug)
projects = await projects.alist()
if request.htmx:
return render(request, 'partials/_project_grid.html', {'projects': projects})
return render(request, 'projects/list.html', {'projects': projects})
hx-push-url="true" updates the browser's URL to /projects/?tag=django, making the
filtered state bookmarkable and shareable.