diff --git a/data/team.csv b/data/team.csv
new file mode 100644
index 00000000..9abfda37
--- /dev/null
+++ b/data/team.csv
@@ -0,0 +1,39 @@
+name,group,role,bio,image,email,github,twitter,linkedin
+Dave Horsfall,Team,Project Co-lead,Leads the DIRECT framework and strategy,assets/img/team/dave-horsfall.webp,,https://github.com/davehorsfall,,
+Aleks Nenadic,Team,Project Co-lead,Leads the DIRECT framework and strategy,assets/img/team/aleksandra-nenadic.webp,,https://github.com/anenadic,,
+Phil Reed,Team,Engagement and Adoption Lead,Leads the engagement and adoption activities,assets/img/team/phil-reed.webp,,https://github.com/PhilReedData,,
+Adrian D'Alessandro,Team,WebApp Lead,Oversees WebApp architecture and implementation,assets/img/team/adrian-dalessandro.webp,,https://github.com/AdrianDAlessandro,,
+Ryan Smith,Contributor,,,,,,,
+Andrew Gait,Contributor,,,,,,,
+Bryn Ubald,Contributor,,,,,,,
+Jason Klebes,Contributor,,,,,,,
+Sam Bland,Contributor,WebApp Team,,,,,,
+Barney Dobson,Contributor,,,,,,,
+Saranjeet Kaur,Contributor,WebApp Team,,,,,,
+Aman Goel,Contributor,Framework Team,,,,,,
+Toby Hodges,Contributor,,,,,,,
+GeoDaoyu,Contributor,,,,,,,
+Christopher Cave-Ayland,Contributor,,,,,,,
+Sahil Raja,Contributor,,,,,,,
+Connor Aird,Contributor,,,,,,,
+Emma Hogan,Contributor,Framework Team,,,,,,
+Matt Craddock,Contributor,,,,,,,
+Tamora James,Contributor,WebApp Team,,,,,,
+Becky Osselton,Contributor,,,,,,,
+Eli Chadwick,Contributor,,,,,,,
+Gabriel Hanganu,Contributor,,,,,,,
+Hannah Williams,Contributor,,,,,,,
+Matthew Bluteau,Contributor,,,,,,,
+Patricia Loto,Contributor,,,,,,,
+Camille Santistevan,Contributor,,,,,,,
+Ella Kaye,Contributor,,,,,,,
+Jonathan Cooper,Contributor,Framework Team,,,,,,
+Samantha Wittke,Contributor,,,,,,,
+Sarah Gibson,Contributor,,,,,,,
+Iain Barrass,Contributor,,,,,,,
+Lieke de Boer,Contributor,,,,,,,
+Martin O'Reilly,Contributor,,,,,,,
+Nadine Spychala,Contributor,,,,,,,
+Paul K Korir,Contributor,,,,,,,
+Sean Marshallsay,Contributor,,,,,,,
+Diego Alonso Alvarez,Contributor,,,,,,,
diff --git a/main/static/assets/img/team/adrian-dalessandro.webp b/main/static/assets/img/team/adrian-dalessandro.webp
new file mode 100644
index 00000000..373baf06
Binary files /dev/null and b/main/static/assets/img/team/adrian-dalessandro.webp differ
diff --git a/main/static/assets/img/team/aleksandra-nenadic.webp b/main/static/assets/img/team/aleksandra-nenadic.webp
new file mode 100644
index 00000000..383b71f1
Binary files /dev/null and b/main/static/assets/img/team/aleksandra-nenadic.webp differ
diff --git a/main/static/assets/img/team/dave-horsfall.webp b/main/static/assets/img/team/dave-horsfall.webp
new file mode 100644
index 00000000..9184cf6f
Binary files /dev/null and b/main/static/assets/img/team/dave-horsfall.webp differ
diff --git a/main/static/assets/img/team/phil-reed.webp b/main/static/assets/img/team/phil-reed.webp
new file mode 100644
index 00000000..ee1d6f7b
Binary files /dev/null and b/main/static/assets/img/team/phil-reed.webp differ
diff --git a/main/templates/main/pages/our-team.html b/main/templates/main/pages/our-team.html
new file mode 100644
index 00000000..f2ff6aca
--- /dev/null
+++ b/main/templates/main/pages/our-team.html
@@ -0,0 +1,99 @@
+{% extends "main/base.page.html" %}
+{% load static %}
+{% block title %}
+ Our Team
+{% endblock title %}
+{% block breadcrumb_items %}
+
About
+ Our Team
+{% endblock breadcrumb_items %}
+{% block content %}
+
+
+
Team Members
+
+ {% for member in team_members %}
+
+
+ {% if member.image %}
+

+ {% else %}
+
+ {% endif %}
+
{{ member.name }}
+
{{ member.role }}
+
{{ member.bio }}
+
+ {% if member.email %}
+ -
+
+
+
+
+ {% endif %}
+ {% if member.github %}
+ -
+
+
+
+
+ {% endif %}
+ {% if member.twitter %}
+ -
+
+
+
+
+ {% endif %}
+ {% if member.linkedin %}
+ -
+
+
+
+
+ {% endif %}
+
+
+
+ {% endfor %}
+
+
Contributors
+
+ {% for contributor in contributors %}
+ -
+ {% if contributor.github %}
+ {{ contributor.name }}
+ {% else %}
+ {{ contributor.name }}
+ {% endif %}
+
+ {% empty %}
+ - No contributors listed.
+ {% endfor %}
+
+
+
+{% endblock content %}
diff --git a/main/templates/main/snippets/footer.html b/main/templates/main/snippets/footer.html
index 4a39d8d4..14bcf8dd 100644
--- a/main/templates/main/snippets/footer.html
+++ b/main/templates/main/snippets/footer.html
@@ -75,7 +75,8 @@ About
href="{% url 'about' %}">Vision and roadmap
- Our team
+ Our team
dict[str, object]:
return context
+class TeamPageView(TemplateView):
+ """View that renders the team page from CSV."""
+
+ template_name = "main/pages/our-team.html"
+
+ def get_context_data(self, **kwargs: Mapping[str, Any]) -> dict[str, Any]:
+ """Add grouped team and contributor data to the template context."""
+ context = super().get_context_data(**kwargs)
+
+ csv_path = Path("data/team.csv")
+ team_members: list[dict[str, Any]] = []
+ contributors: list[dict[str, Any]] = []
+
+ if not csv_path.exists():
+ context["team_members"] = team_members
+ context["contributors"] = contributors
+ return context
+
+ with open(csv_path, newline="", encoding="utf-8") as csvfile:
+ reader = csv.DictReader(csvfile)
+
+ for row in reader:
+ if not row:
+ continue
+
+ name = (row.get("name") or "").strip()
+ image_path = (row.get("image") or "").strip()
+ member = {
+ "name": name,
+ "role": (row.get("role") or "").strip(),
+ "bio": (row.get("bio") or "").strip(),
+ "image": static(image_path) if image_path else None,
+ "email": (row.get("email") or "").strip(),
+ "github": (row.get("github") or "").strip(),
+ "twitter": (row.get("twitter") or "").strip(),
+ "linkedin": (row.get("linkedin") or "").strip(),
+ }
+
+ group = (row.get("group") or "").strip().lower()
+ if group == "contributor":
+ if name:
+ contributors.append(member)
+ else:
+ team_members.append(member)
+
+ contributors.sort(key=lambda m: cast(str, m["name"]).casefold())
+
+ context["team_members"] = team_members
+ context["contributors"] = contributors
+
+ return context
+
+
class RolesPageView(TemplateView):
"""View that renders the role profiles page."""
diff --git a/tests/main/test_main_views.py b/tests/main/test_main_views.py
index 0ce6d62c..3a5aa81b 100644
--- a/tests/main/test_main_views.py
+++ b/tests/main/test_main_views.py
@@ -645,6 +645,24 @@ def test_provides_required_context(self, client):
assert isinstance(response.context["events"], QuerySet)
+class TestTeamPageView(TemplateOkMixin):
+ """Test suite for the EventsPageView."""
+
+ _template_name = "main/pages/our-team.html"
+
+ def _get_url(self):
+ return reverse("our_team")
+
+ def test_provides_required_context(self, client):
+ """Test that the our team view provides the teams context."""
+ response = client.get(self._get_url())
+ assert response.status_code == HTTPStatus.OK
+ assert "team_members" in response.context
+ assert isinstance(response.context["team_members"], list)
+ assert "contributors" in response.context
+ assert isinstance(response.context["contributors"], list)
+
+
class TestAccountOverviewView(LoginRequiredMixin):
"""Test suite for the account_overview view."""