Docker Image and first iteration

This commit is contained in:
boris committed 2026-06-02 12:27:54 +02:00
1 parent 723b64b1c4
commit bd9209ebb7
40 files changed
+26643

No files matched your search

+1
View File
@@ -0,0 +1 @@
+19
View File
@@ -0,0 +1,19 @@
from django.contrib import admin
from .models import PortfolioEntry, Region
@admin.register(Region)
class RegionAdmin(admin.ModelAdmin):
list_display = ("name", "code", "level", "parent")
list_filter = ("level", "parent")
search_fields = ("name", "code")
@admin.register(PortfolioEntry)
class PortfolioEntryAdmin(admin.ModelAdmin):
list_display = ("title", "organization", "entry_type", "is_published", "display_order")
list_filter = ("entry_type", "is_published", "regions")
search_fields = ("title", "organization", "summary")
filter_horizontal = ("regions",)
+7
View File
@@ -0,0 +1,7 @@
from django.apps import AppConfig
class ShowcaseConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "showcase"
+57
View File
@@ -0,0 +1,57 @@
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Region",
fields=[
("code", models.CharField(max_length=8, primary_key=True, serialize=False)),
("name", models.CharField(max_length=120)),
(
"level",
models.CharField(
choices=[("country", "Country"), ("subdivision", "Subdivision")],
max_length=16,
),
),
(
"parent",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="subdivisions",
to="showcase.region",
),
),
],
options={"ordering": ("name",)},
),
migrations.CreateModel(
name="PortfolioEntry",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("entry_type", models.CharField(choices=[("work", "Work"), ("cv", "CV")], max_length=8)),
("title", models.CharField(max_length=160)),
("organization", models.CharField(blank=True, max_length=160)),
("start_date", models.DateField(blank=True, null=True)),
("end_date", models.DateField(blank=True, null=True)),
("summary", models.TextField()),
("url", models.URLField(blank=True)),
("is_published", models.BooleanField(default=False)),
("display_order", models.PositiveIntegerField(default=0)),
("regions", models.ManyToManyField(related_name="entries", to="showcase.region")),
],
options={
"verbose_name_plural": "portfolio entries",
"ordering": ("display_order", "-start_date", "title"),
},
),
]
+37
View File
@@ -0,0 +1,37 @@
import json
from pathlib import Path
from django.db import migrations
def seed_regions(apps, schema_editor):
region_model = apps.get_model("showcase", "Region")
seed_path = Path(__file__).with_name("regions.json")
regions = json.loads(seed_path.read_text())
for region in regions["countries"]:
region_model.objects.update_or_create(
code=region["code"],
defaults={"name": region["name"], "level": "country", "parent": None},
)
for region in regions["subdivisions"]:
region_model.objects.update_or_create(
code=region["code"],
defaults={
"name": region["name"],
"level": "subdivision",
"parent_id": region["parentCode"],
},
)
def unseed_regions(apps, schema_editor):
apps.get_model("showcase", "Region").objects.all().delete()
class Migration(migrations.Migration):
dependencies = [("showcase", "0001_initial")]
operations = [migrations.RunPython(seed_regions, unseed_regions)]
+1
View File
@@ -0,0 +1 @@
File diff suppressed because it is too large. Load diff
+49
View File
@@ -0,0 +1,49 @@
from django.db import models
class Region(models.Model):
class Level(models.TextChoices):
COUNTRY = "country", "Country"
SUBDIVISION = "subdivision", "Subdivision"
code = models.CharField(max_length=8, primary_key=True)
name = models.CharField(max_length=120)
level = models.CharField(max_length=16, choices=Level.choices)
parent = models.ForeignKey(
"self",
blank=True,
null=True,
on_delete=models.CASCADE,
related_name="subdivisions",
)
class Meta:
ordering = ("name",)
def __str__(self):
return self.name
class PortfolioEntry(models.Model):
class EntryType(models.TextChoices):
WORK = "work", "Work"
CV = "cv", "CV"
entry_type = models.CharField(max_length=8, choices=EntryType.choices)
title = models.CharField(max_length=160)
organization = models.CharField(max_length=160, blank=True)
start_date = models.DateField(blank=True, null=True)
end_date = models.DateField(blank=True, null=True)
summary = models.TextField()
url = models.URLField(blank=True)
is_published = models.BooleanField(default=False)
display_order = models.PositiveIntegerField(default=0)
regions = models.ManyToManyField(Region, related_name="entries")
class Meta:
ordering = ("display_order", "-start_date", "title")
verbose_name_plural = "portfolio entries"
def __str__(self):
return self.title
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+26
View File
@@ -0,0 +1,26 @@
{% load static %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Xavier Bukowski - Atlas</title>
<link rel="stylesheet" href="{% static 'showcase/dist/map.css' %}">
</head>
<body>
<header class="site-header">
<a class="site-mark" href="/">Xavier Bukowski<span>.</span></a>
<nav aria-label="Primary navigation">
<a href="#map">Atlas</a>
<a href="#work">Work</a>
<a href="#contact">Contact</a>
</nav>
</header>
<main class="site-shell">
<section id="map" aria-labelledby="map-title">
<div id="portfolio-map-root" data-testid="portfolio-map-root"></div>
</section>
</main>
<script type="module" src="{% static 'showcase/dist/map.js' %}"></script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
+59
View File
@@ -0,0 +1,59 @@
from django.test import TestCase
from django.urls import reverse
from showcase.models import PortfolioEntry, Region
class MapRegionsViewTests(TestCase):
def setUp(self):
self.us = Region.objects.get(code="USA")
self.california = Region.objects.get(code="US-CA")
self.canada = Region.objects.get(code="CAN")
def regions_by_code(self):
response = self.client.get(reverse("map-regions"))
self.assertEqual(response.status_code, 200)
return {region["code"]: region for region in response.json()["regions"]}
def test_homepage_contains_map_mount(self):
response = self.client.get(reverse("home"))
self.assertContains(response, 'id="portfolio-map-root"')
self.assertContains(response, "showcase/dist/map.js")
def test_api_returns_seeded_regions_with_empty_entries(self):
regions = self.regions_by_code()
self.assertIn("USA", regions)
self.assertIn("CAN", regions)
self.assertIn("US-CA", regions)
self.assertEqual(regions["US-CA"]["parentCode"], "USA")
self.assertEqual(regions["USA"]["entries"], [])
def test_country_aggregates_subdivision_entries_and_deduplicates(self):
entry = PortfolioEntry.objects.create(
entry_type=PortfolioEntry.EntryType.WORK,
title="West Coast delivery",
summary="Delivered a regional engagement.",
is_published=True,
)
entry.regions.add(self.us, self.california)
regions = self.regions_by_code()
self.assertEqual(len(regions["USA"]["entries"]), 1)
self.assertEqual(regions["US-CA"]["entries"][0]["title"], entry.title)
def test_api_excludes_unpublished_entries(self):
entry = PortfolioEntry.objects.create(
entry_type=PortfolioEntry.EntryType.CV,
title="Draft entry",
summary="Not visible yet.",
is_published=False,
)
entry.regions.add(self.canada)
regions = self.regions_by_code()
self.assertEqual(regions["CAN"]["entries"], [])
+9
View File
@@ -0,0 +1,9 @@
from django.urls import path
from . import views
urlpatterns = [
path("", views.home, name="home"),
path("api/map/regions/", views.map_regions, name="map-regions"),
]
+51
View File
@@ -0,0 +1,51 @@
from django.http import JsonResponse
from django.shortcuts import render
from .models import PortfolioEntry, Region
def home(request):
return render(request, "showcase/home.html")
def _serialize_entry(entry):
return {
"id": entry.id,
"type": entry.entry_type,
"title": entry.title,
"organization": entry.organization,
"startDate": entry.start_date.isoformat() if entry.start_date else None,
"endDate": entry.end_date.isoformat() if entry.end_date else None,
"summary": entry.summary,
"url": entry.url,
}
def map_regions(request):
entries = PortfolioEntry.objects.filter(is_published=True).prefetch_related("regions")
entries_by_region = {}
for entry in entries:
payload = _serialize_entry(entry)
for region in entry.regions.all():
entries_by_region.setdefault(region.code, {})[entry.id] = payload
regions = list(Region.objects.select_related("parent").prefetch_related("subdivisions"))
payload = []
for region in regions:
related = dict(entries_by_region.get(region.code, {}))
if region.level == Region.Level.COUNTRY:
for subdivision in region.subdivisions.all():
related.update(entries_by_region.get(subdivision.code, {}))
payload.append(
{
"code": region.code,
"name": region.name,
"level": region.level,
"parentCode": region.parent_id,
"entries": list(related.values()),
}
)
return JsonResponse({"regions": payload})