Skip to content
Draft
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
11 changes: 9 additions & 2 deletions helpdesk/teams/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.contrib import admin

from teams.models import Team, TeamComment, TeamPitLocation
from teams.models import Team, TeamComment, TeamEvent, TeamPitLocation


class TeamPitLocationAdmin(admin.ModelAdmin):
Expand All @@ -14,10 +14,17 @@ class TeamCommentAdmin(admin.StackedInline):
readonly_fields = ("created_at",)


class TeamEventAdmin(admin.StackedInline):
model = TeamEvent
extra = 1

readonly_fields = ("created_at",)


class TeamAdmin(admin.ModelAdmin):
list_display = ("tla", "name", "is_rookie")
list_filter = ("is_rookie",)
inlines = (TeamCommentAdmin,)
inlines = (TeamCommentAdmin, TeamEventAdmin)


admin.site.register(TeamPitLocation, TeamPitLocationAdmin)
Expand Down
10 changes: 9 additions & 1 deletion helpdesk/teams/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django import forms

from teams.models import Team, TeamAttendanceEvent
from teams.models import Team, TeamAttendanceEvent, TeamEvent


class TeamAttendanceLogForm(forms.ModelForm):
Expand All @@ -9,3 +9,11 @@ class TeamAttendanceLogForm(forms.ModelForm):
class Meta:
model = TeamAttendanceEvent
fields = ("type", "comment", "team")


class TeamEventForm(forms.ModelForm):
team = forms.ModelChoiceField(queryset=Team.objects.all(), widget=forms.HiddenInput())

class Meta:
model = TeamEvent
fields = ("type", "comment", "team")
58 changes: 58 additions & 0 deletions helpdesk/teams/migrations/0006_teamevent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 4.2.20 on 2026-03-16 22:25

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("teams", "0005_alter_team_options_teamattendanceevent"),
]

operations = [
migrations.CreateModel(
name="TeamEvent",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"type",
models.TextField(
choices=[
("SAFETY_CHECK_PASS", "Safety Check (Pass)"),
("SAFETY_CHECK_FAIL", "Safety Check (Fail)"),
("REGS_CHECK_PASS", "Regulations Check (Pass)"),
("REGS_CHECK_FAIL", "Regulations Check (Fail)"),
],
max_length=17,
),
),
("comment", models.TextField()),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"team",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="events",
to="teams.team",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
28 changes: 28 additions & 0 deletions helpdesk/teams/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,34 @@ def __str__(self) -> str:
return f"Comment on {self.team.name} at {self.created_at} by {self.author}"


class TeamEventType(models.TextChoices):
SAFETY_CHECK_PASS = "SAFETY_CHECK_PASS", "Safety Check (Pass)"
SAFETY_CHECK_FAIL = "SAFETY_CHECK_FAIL", "Safety Check (Fail)"
REGS_CHECK_PASS = "REGS_CHECK_PASS", "Regulations Check (Pass)"
REGS_CHECK_FAIL = "REGS_CHECK_FAIL", "Regulations Check (Fail)"


class TeamEvent(models.Model):
team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
related_name="events",
)
user = models.ForeignKey(
"accounts.User",
on_delete=models.PROTECT,
)
type = models.CharField(
max_length=max(len(value) for value in TeamEventType.values),
choices=TeamEventType.choices,
)
comment = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self) -> str:
return f"Event: {self.team.name} {self.type} at {self.created_at}"


class TeamAttendanceEventType(models.TextChoices):
ARRIVED = "ARRIVED", "Arrived"
LEFT = "LEFT", "Left"
Expand Down
18 changes: 17 additions & 1 deletion helpdesk/teams/tables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import django_tables2 as tables

from .models import Team, TeamAttendanceEvent, TeamAttendanceEventType
from .models import Team, TeamAttendanceEvent, TeamAttendanceEventType, TeamEvent


class TeamTable(tables.Table):
Expand Down Expand Up @@ -49,3 +49,19 @@ class Meta:
sequence = ("created_at", "type", "comment", "user")
order_by = "-created_at"
exclude = ["id", "team"]


class TeamEventListTable(tables.Table):
type = tables.Column()
comment = tables.Column()
created_at = tables.DateTimeColumn(verbose_name="Time", format="D H:i")
user = tables.TemplateColumn(
verbose_name="Logged by",
template_code='{{record.user|default:"—"}}',
)

class Meta:
model = TeamEvent
sequence = ("created_at", "type", "comment", "user")
order_by = "-created_at"
exclude = ["id", "team"]
2 changes: 2 additions & 0 deletions helpdesk/teams/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
TeamAttendanceView,
TeamDetailAboutView,
TeamDetailCommentsView,
TeamDetailEvents,
TeamDetailTicketsView,
TeamDetailTimelineView,
TeamListView,
Expand All @@ -24,4 +25,5 @@
path("<slug:slug>/comments/post", TeamSubmitCommentFormView.as_view(), name="team_detail_comments_post"),
path("<slug:slug>/tickets", TeamDetailTicketsView.as_view(), name="team_detail_tickets"),
path("<slug:slug>/timeline", TeamDetailTimelineView.as_view(), name="team_detail_timeline"),
path("<slug:slug>/events", TeamDetailEvents.as_view(), name="team_detail_events"),
]
49 changes: 45 additions & 4 deletions helpdesk/teams/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Any

from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import CharField, F, Prefetch, QuerySet, Value
from django.db.models import Case, CharField, F, Prefetch, QuerySet, Value, When
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
Expand All @@ -15,15 +15,16 @@

from helpdesk.forms import CommentSubmitForm
from helpdesk.utils import is_filterset_filtered
from teams.models import TeamEvent, TeamEventType
from tickets.filters import TicketFilter
from tickets.models import Ticket, TicketEvent
from tickets.tables import TicketTable

from .filters import TeamFilterset
from .forms import TeamAttendanceLogForm
from .forms import TeamAttendanceLogForm, TeamEventForm
from .models import Team, TeamAttendanceEvent, TeamComment
from .srcomp import srcomp
from .tables import TeamAttendanceListTable, TeamAttendanceOverviewTable, TeamTable
from .tables import TeamAttendanceListTable, TeamAttendanceOverviewTable, TeamEventListTable, TeamTable


class TicketDetailRedirectView(RedirectView):
Expand Down Expand Up @@ -171,7 +172,23 @@ def get_entries(self) -> QuerySet[Any]:
.values(*fields)
)

return ticket_comments.union(ticket_opens, ticket_resolves, team_comments).order_by("-entry_timestamp")
team_events = (
TeamEvent.objects.filter(team=self.object)
.order_by()
.annotate(
# HACK: Show the display version of the "type" field
entry_type=Case(*[When(type=k, then=Value(v)) for k, v in TeamEventType.choices], default=F("type")),
entry_timestamp=F("created_at"),
entry_user=F("user"),
entry_content=F("comment"),
entry_style_info=Value("", output_field=CharField()),
)
.values(*fields)
)

return ticket_comments.union(ticket_opens, ticket_resolves, team_comments, team_events).order_by(
"-entry_timestamp"
)

def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
return super().get_context_data(entries=self.get_entries(), **kwargs)
Expand Down Expand Up @@ -215,3 +232,27 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
def form_valid(self, form: TeamAttendanceLogForm) -> HttpResponse:
form.instance.user = self.request.user
return super().form_valid(form)


class TeamDetailEvents(LoginRequiredMixin, CreateView):
model = TeamEvent
form_class = TeamEventForm
slug_field = "tla"
template_name = "teams/team_detail_events.html"

def get_success_url(self) -> str:
return reverse_lazy("teams:team_detail_timeline", args=[self.kwargs["slug"]])

def get_initial(self) -> dict[str, Any]:
return {"team": get_object_or_404(Team, tla=self.kwargs["slug"])}

def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
context_data = super().get_context_data(**kwargs)
context_data["team"] = context_data["form"].initial["team"]
context_data["events"] = TeamEvent.objects.filter(team=context_data["team"]).order_by("-created_at").all()
context_data["table"] = TeamEventListTable(context_data["events"])
return context_data

def form_valid(self, form: TeamEventForm) -> HttpResponse:
form.instance.user = self.request.user
return super().form_valid(form)
3 changes: 3 additions & 0 deletions helpdesk/templates/inc/nav/team-detail-buttons.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<a class="button is-primary" href="{% url 'tickets:ticket_create' %}?team={{ object.tla }}">
New Ticket for {{ object.tla }}
</a>
6 changes: 6 additions & 0 deletions helpdesk/templates/inc/nav/team-tabs.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
<span>About</span>
</a>
</li>
<li {% if active == "events" %}class="is-active"{% endif %}>
<a href="{% url 'teams:team_detail_events' team.tla %}">
<span class="icon is-small"><i class="fas fa-circle-info" aria-hidden="true"></i></span>
<span>Events</span>
</a>
</li>
<li {% if active == "tickets" %}class="is-active"{% endif %}>
<a href="{% url 'teams:team_detail_tickets' team.tla %}">
<span class="icon is-small"><i class="fas fa-ticket" aria-hidden="true"></i></span>
Expand Down
6 changes: 2 additions & 4 deletions helpdesk/templates/teams/team_detail_about.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
{% block title %}{{object.tla}}: {{ object.name }}{% endblock %}

{% block page_buttons %}
<a class="button is-primary" href="{% url 'tickets:ticket_create' %}?team={{ object.tla }}">
New Ticket for {{ object.tla }}
</a>
{% include "inc/nav/team-detail-buttons.html" %}
{% endblock %}

{% block content %}
Expand All @@ -29,4 +27,4 @@
</div>
{% endif %}
</div>
{% endblock %}
{% endblock %}
6 changes: 2 additions & 4 deletions helpdesk/templates/teams/team_detail_comments.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
{% block title %}{{object.tla}}: {{ object.name }}{% endblock %}

{% block page_buttons %}
<a class="button is-primary" href="{% url 'tickets:ticket_create' %}?team={{ object.tla }}">
New Ticket for {{ object.tla }}
</a>
{% include "inc/nav/team-detail-buttons.html" %}
{% endblock %}

{% block content %}
Expand All @@ -34,4 +32,4 @@
</form>
</div>
</article>
{% endblock %}
{% endblock %}
33 changes: 33 additions & 0 deletions helpdesk/templates/teams/team_detail_events.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{% extends "layouts/base_app.html" %}
{% load render_table from django_tables2 %}
{% load crispy_forms_tags %}

{% block page_title %}{{team.tla}}: {{ team.name }}{% endblock %}
{% block title %}{{team.tla}}: {{ team.name }}{% endblock %}

{% block page_buttons %}
{% include "inc/nav/team-detail-buttons.html" %}
{% endblock %}

{% block content %}
{% include "inc/nav/team-tabs.html" with active="events" %}
<div class="container">
{% if events %}
{% render_table table %}
{% else %}
<article class="message">
<div class="message-body">No events have been logged for this team.</div>
</article>
{% endif %}

<article class="message is-info mt-5">
<div class="message-body">
<form class="block" method="post">
{% csrf_token %}
{{form|crispy}}
<input class="button is-primary" type="submit" value="Save">
</form>
</div>
</article>
</div>
{% endblock %}
8 changes: 3 additions & 5 deletions helpdesk/templates/teams/team_detail_tickets.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
{% block title %}{{object.tla}}: {{ object.name }}{% endblock %}

{% block page_buttons %}
<button class="button is-warning js-modal-trigger" data-target="modal-filter">Filter</button>
<a class="button is-primary" href="{% url 'tickets:ticket_create' %}?team={{ object.tla }}">
New Ticket for {{ object.tla }}
</a>
<button class="button is-secondary js-modal-trigger" data-target="modal-filter">Filter</button>
{% include "inc/nav/team-detail-buttons.html" %}
{% endblock %}

{% block content %}
Expand All @@ -24,4 +22,4 @@
</div>
</div>
{% include "inc/modals/filter.html" %}
{% endblock %}
{% endblock %}
7 changes: 2 additions & 5 deletions helpdesk/templates/teams/team_detail_timeline.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,12 @@
{% block title %}{{object.tla}}: {{ object.name }}{% endblock %}

{% block page_buttons %}
<a class="button is-primary" href="{% url 'tickets:ticket_create' %}?team={{ object.tla }}">
New Ticket for {{ object.tla }}
</a>
{% include "inc/nav/team-detail-buttons.html" %}
{% endblock %}

{% block content %}
{% include "inc/nav/team-tabs.html" with active="timeline" team=object %}
<div class="container">
<p class="block">This page is experimental, full details can be found on individual tickets.</p>
{% for entry in entries %}
<article class="message {{entry.entry_style_info}}">
<div class="message-header">
Expand All @@ -28,4 +25,4 @@
</article>
{% endfor %}
</div>
{% endblock %}
{% endblock %}
Loading