Compare commits

..
4 Commits
7 changed files with 127 additions and 26 deletions
+5
View File
@@ -21,3 +21,8 @@ class CompanyAdmin(admin.ModelAdmin):
@admin.register(models.Project) @admin.register(models.Project)
class ProjectAdmin(admin.ModelAdmin): class ProjectAdmin(admin.ModelAdmin):
list_display = ["name", "short_description"] list_display = ["name", "short_description"]
@admin.register(models.ProjectTag)
class ProjectTagAdmin(admin.ModelAdmin):
list_display = ["name", "color"]
@@ -0,0 +1,26 @@
# Generated by Django 6.1.1 on 2026-09-15 18:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('portfolio', '0004_company_color_alter_company_logo'),
]
operations = [
migrations.CreateModel(
name='ProjectTag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=25, verbose_name='Name')),
('color', models.CharField(default='000000', max_length=6, verbose_name='Color (hex)')),
],
),
migrations.AddField(
model_name='project',
name='tags',
field=models.ManyToManyField(blank=True, related_name='projects', to='portfolio.projecttag', verbose_name='Tags'),
),
]
+25
View File
@@ -57,6 +57,22 @@ class JobTask(models.Model):
return self.job.__str__() return self.job.__str__()
class ProjectTag(models.Model):
name = models.CharField(
verbose_name=_("Name"), max_length=25, blank=False, null=False
)
color = models.CharField(
verbose_name=_("Color (hex)"),
max_length=6,
null=False,
blank=False,
default="000000",
)
def __str__(self):
return self.name
class Project(models.Model): class Project(models.Model):
name = models.CharField( name = models.CharField(
verbose_name=_("Name"), max_length=256, null=False, blank=False verbose_name=_("Name"), max_length=256, null=False, blank=False
@@ -65,3 +81,12 @@ class Project(models.Model):
verbose_name=_("Short description"), max_length=512, blank=False, null=False verbose_name=_("Short description"), max_length=512, blank=False, null=False
) )
url = models.URLField(verbose_name=_("URL"), blank=True, null=False) url = models.URLField(verbose_name=_("URL"), blank=True, null=False)
tags = models.ManyToManyField(
ProjectTag,
verbose_name=_("Tags"),
blank=True,
related_name="projects",
)
def __str__(self):
return self.name
+16 -24
View File
@@ -1,29 +1,21 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="container mt-4"> <div class="container mt-2 mt-md-4">
<div class="row row-deck row-cards"> <form class="mb-2 mb-md-4">
{% for project in object_list %} <div class="form-floating">
<div class="col-md-4"> <input type="text"
{% if project.url %} class="form-control"
<a class="card card-link card-link-pop" id="floating-name"
href="{{ project.url }}" placeholder=""
target="_blank"> name="query"
<div class="card-body"> autocomplete="off"
<h4>{{ project.name }}</h4> hx-target="#projects"
<p>{{ project.short_description }}</p> hx-get="{% url 'projects-query' %}"
</div> hx-trigger="load, input, delay:200ms" />
</a> <label for="floating-name">{% trans "Search" %}</label>
{% else %} </div>
<div class="card"> </form>
<div class="card-body"> <div id="projects"></div>
<h4>{{ project.name }}</h4>
<p>{{ project.short_description }}</p>
</div>
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div> </div>
{% endblock %} {% endblock %}
@@ -0,0 +1,35 @@
<div class="row row-deck row-cards">
{% for project in object_list %}
<div class="col-md-4">
{% if project.url %}
<a class="card card-link card-link-pop"
href="{{ project.url }}"
target="_blank">
<div class="card-body">
<h4>{{ project.name }}</h4>
<p>{{ project.short_description }}</p>
<div>
{% for t in project.tags.all %}
<div class="badge text-white fs-5"
style="background-color: #{{ t.color }}">{{ t }}</div>
{% endfor %}
</div>
</div>
</a>
{% else %}
<div class="card">
<div class="card-body">
<h4>{{ project.name }}</h4>
<p>{{ project.short_description }}</p>
<div>
{% for t in project.tags.all %}
<div class="badge text-white fs-5"
style="background-color: #{{ t.color }}">{{ t }}</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
{% endfor %}
</div>
+1
View File
@@ -5,6 +5,7 @@ from portfolio import views
urlpatterns = [ urlpatterns = [
path("", views.PortfolioView.as_view(), name="portfolio"), path("", views.PortfolioView.as_view(), name="portfolio"),
path("projects/", views.ProjectList.as_view(), name="projects"), path("projects/", views.ProjectList.as_view(), name="projects"),
path("projects/query/", views.ProjectListQuery.as_view(), name="projects-query"),
path("contact/", views.ContactSend.as_view(), name="contact"), path("contact/", views.ContactSend.as_view(), name="contact"),
path("company/<int:pk>/logo/", views.CompanyLogo.as_view(), name="company-logo"), path("company/<int:pk>/logo/", views.CompanyLogo.as_view(), name="company-logo"),
] ]
+19 -2
View File
@@ -1,4 +1,5 @@
from django.conf import settings from django.conf import settings
from django.db.models import Q
from django.http.response import FileResponse, HttpResponseBadRequest from django.http.response import FileResponse, HttpResponseBadRequest
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import DetailView, ListView, TemplateView from django.views.generic import DetailView, ListView, TemplateView
@@ -18,9 +19,8 @@ class PortfolioView(TemplateView):
} }
class ProjectList(ListView): class ProjectList(TemplateView):
template_name = "project_list.html" template_name = "project_list.html"
model = models.Project
class ContactSend(TemplateView): class ContactSend(TemplateView):
@@ -49,3 +49,20 @@ class CompanyLogo(DetailView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
company = self.get_object() company = self.get_object()
return FileResponse(company.logo) return FileResponse(company.logo)
class ProjectListQuery(ListView):
template_name = "project_list_query.html"
model = models.Project
def get_queryset(self):
query = self.request.GET.get("query")
if not query:
return super().get_queryset()
return (
super()
.get_queryset()
.filter(Q(name__contains=query) | Q(tags__name__contains=query))
.distinct()
)