Skip to content
Open
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
6 changes: 5 additions & 1 deletion jobs/admin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from django.contrib import admin
from .models import Job
from .models import Job, Tag

@admin.register(Job)
class JobAdmin(admin.ModelAdmin):
list_display = ('title', 'company_name', 'location', 'salary_range', 'is_remote')
list_filter = ('is_remote', 'salary_range')
search_fields = ('title', 'company_name', 'location')

@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
search_fields = ('name',)

admin.site.site_header = "PythonPH Jobs Board"
16 changes: 14 additions & 2 deletions jobs/management/commands/populate_jobs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.core.management.base import BaseCommand
from jobs.models import Job
from jobs.models import Job, Tag
from faker import Faker
import random

Expand All @@ -19,8 +19,9 @@ def handle(self, *args, **kwargs):
fake = Faker()
count = kwargs['count']

# Delete all existing jobs
# Delete all existing jobs and tags
Job.objects.all().delete()
Tag.objects.all().delete()

# Common tech job titles and skills for more realistic data
tech_roles = [
Expand All @@ -38,6 +39,15 @@ def handle(self, *args, **kwargs):
'Davao City', 'Pasig', 'Mandaluyong', 'Pasay'
]

# Create tags
tags = [
Tag(name='Django'),
Tag(name='FastAPI'),
Tag(name='Pandas'),
Tag(name='Automation')
]
Tag.objects.bulk_create(tags)

# Create jobs
for _ in range(count):
# Generate salary range (in thousands)
Expand Down Expand Up @@ -66,6 +76,8 @@ def handle(self, *args, **kwargs):
is_remote=random.choice([True, False, True]) # 66% chance of remote work
)

job.assigned_tags.add(*random.sample(tags, random.choice(range(1, 3)))) # add 1 or 2 tags

self.stdout.write(
self.style.SUCCESS(f'Created job: {job.title} at {job.company_name}')
)
Expand Down
25 changes: 25 additions & 0 deletions jobs/migrations/0003_tag_job_assigned_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 5.1.6 on 2026-04-04 19:27

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('jobs', '0002_job_short_description'),
]

operations = [
migrations.CreateModel(
name='Tag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True)),
],
),
migrations.AddField(
model_name='job',
name='assigned_tags',
field=models.ManyToManyField(blank=True, to='jobs.tag'),
),
]
16 changes: 16 additions & 0 deletions jobs/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
from django.db import models
from django.db.models.functions import Lower


class Tag(models.Model):
name = models.CharField(max_length=100, unique=True)

def __str__(self):
return self.name

class Meta:
constraints = [
models.UniqueConstraint(
Lower('name'),
name='case insensitive uniqueness check'
),
]

class Job(models.Model):
title = models.CharField(max_length=200)
company_name = models.CharField(max_length=200)
Expand All @@ -11,6 +26,7 @@ class Job(models.Model):
is_remote = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
assigned_tags = models.ManyToManyField(Tag, blank=True)

def __str__(self):
return f"{self.title} at {self.company_name}"
Expand Down
10 changes: 10 additions & 0 deletions jobs/templates/content.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
</div>
</form>

<!-- Display Tag -->
{% if tag_query %}
<div class="card-title mb-8">Showing jobs tagged '{{ tag_query }}':</div>
{% endif %}

<!-- Job Listings -->
<div class="space-y-4">
{% for job in object_list %}
Expand All @@ -43,6 +48,11 @@
<div class="badge badge-primary">Remote</div>
{% endif %}
<div class="badge badge-outline">{{ job.salary_range }}</div>
{% for tag in job.tags %}
<div class="badge badge-outline">
<a href="{% url 'job_index' %}?tag={{ tag }}">{{ tag }}</a>
</div>
{% endfor %}
</div>
<p class="mt-4 text-base-content/70">
{{ job.short_description }}
Expand Down
5 changes: 5 additions & 0 deletions jobs/templates/job_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ <h2 class="text-xl text-base-content/70 mb-4">{{ job.company_name }}</h2>
<div class="badge badge-primary">Remote</div>
{% endif %}
<div class="badge badge-outline">{{ job.salary_range }}</div>
{% for tag in job.tags %}
<div class="badge badge-outline">
<a href="{% url 'job_index' %}?tag={{ tag }}">{{ tag }}</a>
</div>
{% endfor %}
</div>
</div>
<button class="btn btn-primary">Apply Now</button>
Expand Down
4 changes: 2 additions & 2 deletions jobs/templates/pagination.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<div class="pagination text-center">
<span class="page-links">
{% if page_obj.has_previous %}
<a class="btn" href="{% url 'job_index' %}?page={{ page_obj.previous_page_number }}"><</a>
<a class="btn" href="{% url 'job_index' %}{% querystring page=page_obj.previous_page_number %}"><</a>
{% endif %}
{% if page_obj.has_next %}
<a class="btn" href="{% url 'job_index' %}?page={{ page_obj.next_page_number }}">></a>
<a class="btn" href="{% url 'job_index' %}{% querystring page=page_obj.next_page_number %}"&{{ request.GET.urlencode }}>></a>
{% endif %}
</span>
</div>
Expand Down
20 changes: 17 additions & 3 deletions jobs/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.views.generic import ListView, DetailView
from django.db.models import Q, Count, Max
from .models import Job
from django.db.models import Q, Count, Max, OuterRef
from django.contrib.postgres.expressions import ArraySubquery
from .models import Job, Tag


class JobIndex(ListView):
Expand All @@ -9,20 +10,28 @@ class JobIndex(ListView):
paginate_by = 10

def get_queryset(self):
queryset = super().get_queryset()
queryset = super().get_queryset().annotate(
tags = ArraySubquery(Tag.objects.filter(job=OuterRef('pk')).values('name'))
)
search_query = self.request.GET.get('search', '')
tag_query = self.request.GET.get('tag', '')
if search_query:
queryset = queryset.filter(
Q(title__icontains=search_query) |
Q(company_name__icontains=search_query)
)
if tag_query:
queryset = queryset.filter(
tags__contains=[tag_query]
)
return queryset

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["job_count"] = self.get_queryset().count()
context["company_count"] = Job.objects.values('company_name').distinct().count()
context["search_query"] = self.request.GET.get('search', '')
context["tag_query"] = self.request.GET.get('tag', '')
context["description"] = 'Python.PH Jobs Board'
context["page_type"] = 'website'
return context
Expand All @@ -32,6 +41,11 @@ class JobDetail(DetailView):
template_name = "job_detail.html"
context_object_name = 'job'

def get_queryset(self):
return super().get_queryset().annotate(
tags = ArraySubquery(Tag.objects.filter(job=OuterRef('pk')).values('name'))
)

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add related jobs from the same company
Expand Down