-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Team attendance register #84
base: main
Are you sure you want to change the base?
Changes from all commits
e39d149
d43baa8
b86860e
18ed88e
ea78a25
931710c
0374b01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from django import forms | ||
|
||
from teams.models import Team, TeamAttendanceEvent, TeamAttendanceEventType | ||
|
||
|
||
class TeamAttendanceLogForm(forms.ModelForm): | ||
type = forms.Select(choices=TeamAttendanceEventType.choices) | ||
comment = forms.CharField(required=False) | ||
team = forms.ModelChoiceField(queryset=Team.objects.all(), widget=forms.HiddenInput()) | ||
|
||
class Meta: | ||
model = TeamAttendanceEvent | ||
fields = ("type", "comment", "team") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Generated by Django 4.2.11 on 2024-12-24 18:40 | ||
|
||
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", "0004_add_team_comment"), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterModelOptions( | ||
name="team", | ||
options={"ordering": ["tla"]}, | ||
), | ||
migrations.CreateModel( | ||
name="TeamAttendanceEvent", | ||
fields=[ | ||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), | ||
( | ||
"type", | ||
models.TextField( | ||
choices=[("AR", "Arrived"), ("LE", "Left"), ("DE", "Delayed"), ("DO", "Dropped Out")], | ||
max_length=2, | ||
), | ||
), | ||
("comment", models.TextField()), | ||
("created_at", models.DateTimeField(auto_now_add=True)), | ||
( | ||
"team", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
related_name="team_attendance_events", | ||
related_query_name="team_attendance_events", | ||
to="teams.team", | ||
), | ||
), | ||
("user", models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), | ||
], | ||
), | ||
] |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -58,3 +58,32 @@ class Meta: | |||||||||||||||||||
|
||||||||||||||||||||
def __str__(self) -> str: | ||||||||||||||||||||
return f"Comment on {self.team.name} at {self.created_at} by {self.author}" | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
class TeamAttendanceEventType(models.TextChoices): | ||||||||||||||||||||
ARRIVED = "AR", "Arrived" | ||||||||||||||||||||
LEFT = "LE", "Left" | ||||||||||||||||||||
DELAYED = "DE", "Delayed" | ||||||||||||||||||||
DROPPED_OUT = "DO", "Dropped Out" | ||||||||||||||||||||
|
||||||||||||||||||||
Comment on lines
+64
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Since these are already small, might as well let the values be human readable
Suggested change
|
||||||||||||||||||||
|
||||||||||||||||||||
class TeamAttendanceEvent(models.Model): | ||||||||||||||||||||
team = models.ForeignKey( | ||||||||||||||||||||
Team, | ||||||||||||||||||||
on_delete=models.CASCADE, | ||||||||||||||||||||
related_name="team_attendance_events", | ||||||||||||||||||||
related_query_name="team_attendance_events", | ||||||||||||||||||||
) | ||||||||||||||||||||
user = models.ForeignKey( | ||||||||||||||||||||
"accounts.User", | ||||||||||||||||||||
on_delete=models.PROTECT, | ||||||||||||||||||||
) | ||||||||||||||||||||
type = models.TextField( | ||||||||||||||||||||
max_length=2, | ||||||||||||||||||||
choices=TeamAttendanceEventType.choices, | ||||||||||||||||||||
) | ||||||||||||||||||||
comment = models.TextField() | ||||||||||||||||||||
created_at = models.DateTimeField(auto_now_add=True) | ||||||||||||||||||||
|
||||||||||||||||||||
def __str__(self) -> str: | ||||||||||||||||||||
return f"Attendance Event: {self.team.name} {self.type} at {self.created_at}" |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -1,6 +1,6 @@ | ||||||||
import django_tables2 as tables | ||||||||
|
||||||||
from .models import Team | ||||||||
from .models import Team, TeamAttendanceEventType | ||||||||
|
||||||||
|
||||||||
class TeamTable(tables.Table): | ||||||||
|
@@ -13,3 +13,23 @@ class Meta: | |||||||
model = Team | ||||||||
exclude = ("id", "pit_location") | ||||||||
order_by = "tla" | ||||||||
|
||||||||
|
||||||||
class TeamAttendanceTable(tables.Table): | ||||||||
name = tables.LinkColumn("teams:team_detail", args=[tables.A("tla")]) | ||||||||
latest_event__0__type = tables.Column("Latest Event") | ||||||||
latest_event__0__comment = tables.Column("Comment") | ||||||||
latest_event__0__created_at = tables.DateTimeColumn(verbose_name="Time") | ||||||||
user = tables.TemplateColumn( | ||||||||
verbose_name="Logged by", | ||||||||
template_code='{{record.latest_event.0.user|default:"—"}}', | ||||||||
) | ||||||||
actions = tables.LinkColumn("teams:team_log_attendance_form", args=[tables.A("tla")], text="Log") | ||||||||
|
||||||||
def render_latest_event__0__type(self, value: str) -> str | None: | ||||||||
lookups = dict(TeamAttendanceEventType.choices) | ||||||||
return lookups.get(value) | ||||||||
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Because this is an enum, I think you can do:
Suggested change
|
||||||||
|
||||||||
class Meta: | ||||||||
model = Team | ||||||||
exclude = ["id", "tla", "is_rookie", "pit_location"] |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -3,25 +3,26 @@ | |||
from typing import Any | ||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin | ||||
from django.db.models import CharField, F, QuerySet, Value | ||||
from django.db.models import CharField, F, Prefetch, QuerySet, Value | ||||
from django.http import HttpResponse, HttpResponseRedirect | ||||
from django.urls import reverse_lazy | ||||
from django.views.generic import DetailView, RedirectView | ||||
from django.views.generic import CreateView, DetailView, ListView, RedirectView | ||||
from django.views.generic.detail import SingleObjectMixin | ||||
from django.views.generic.edit import FormMixin, ProcessFormView | ||||
from django_filters.views import FilterView | ||||
from django_tables2 import SingleTableMixin | ||||
|
||||
from helpdesk.forms import CommentSubmitForm | ||||
from helpdesk.utils import is_filterset_filtered | ||||
from helpdesk.utils import get_object_or_none, is_filterset_filtered | ||||
from tickets.filters import TicketFilter | ||||
from tickets.models import Ticket, TicketEvent | ||||
from tickets.tables import TicketTable | ||||
|
||||
from .filters import TeamFilterset | ||||
from .models import Team, TeamComment | ||||
from .forms import TeamAttendanceLogForm | ||||
from .models import Team, TeamAttendanceEvent, TeamComment | ||||
from .srcomp import srcomp | ||||
from .tables import TeamTable | ||||
from .tables import TeamAttendanceTable, TeamTable | ||||
|
||||
|
||||
class TicketDetailRedirectView(RedirectView): | ||||
|
@@ -172,3 +173,49 @@ def get_entries(self) -> QuerySet[Any]: | |||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]: | ||||
return super().get_context_data(entries=self.get_entries(), **kwargs) | ||||
|
||||
|
||||
class TeamAttendanceView(LoginRequiredMixin, SingleTableMixin, ListView): | ||||
model = Team | ||||
table_class = TeamAttendanceTable | ||||
|
||||
def get_queryset(self) -> QuerySet[Any]: | ||||
return Team.objects.all().prefetch_related( | ||||
Prefetch( | ||||
"team_attendance_events", | ||||
TeamAttendanceEvent.objects.order_by("-created_at")[:1], | ||||
to_attr="latest_event", | ||||
) | ||||
) | ||||
|
||||
|
||||
class TeamAttendanceFormView(LoginRequiredMixin, CreateView): | ||||
http_method_names = ["get", "post"] | ||||
model = TeamAttendanceEvent | ||||
form_class = TeamAttendanceLogForm | ||||
|
||||
def get_initial(self) -> dict[str, Any]: | ||||
return { | ||||
"team": get_object_or_none(Team, tla=self.kwargs["tla"]), | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: Whilst I like the idea, it's probably better to keep the team only in the URL, rather than in the form. |
||||
} | ||||
|
||||
def get_success_url(self) -> str: | ||||
return reverse_lazy("teams:team_list_attendance") | ||||
|
||||
def form_valid(self, form: TeamAttendanceLogForm) -> HttpResponse: | ||||
assert self.request.user.is_authenticated | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: This assertion shouldn't be necessary
Suggested change
|
||||
team = form.cleaned_data["team"] | ||||
team.team_attendance_events.create( | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: You should be able to use https://forum.djangoproject.com/t/modify-form-field-before-saving-it/4090/3 is a good example. |
||||
type=form.cleaned_data["type"], | ||||
comment=form.cleaned_data["comment"], | ||||
user=self.request.user, | ||||
) | ||||
return HttpResponseRedirect(redirect_to=self.get_success_url()) | ||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]: | ||||
context = super().get_context_data(**kwargs) | ||||
context["team"] = self.get_initial()["team"] | ||||
return context | ||||
|
||||
def form_invalid(self, form: TeamAttendanceLogForm) -> HttpResponse: | ||||
return HttpResponse("Please fill out the form correctly.") | ||||
Comment on lines
+220
to
+221
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue: This is a bad idea. If you leave this out, the form will show its own errors |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{% extends "layouts/base_app.html" %} | ||
{% load render_table from django_tables2 %} | ||
{% load crispy_forms_tags %} | ||
|
||
{% block page_title %}Team Attendance{% endblock %} | ||
{% block title %}Team Attendance{% endblock %} | ||
|
||
{% block page_buttons %} | ||
{# <input class="input" type="date">#} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: Is this meant to be commented out? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a WIP thing. I wanted to add the option to filter by day. Not sure if I should keep it. We'd also want to make sure that teams which have dropped out ignore the filter. |
||
{% endblock %} | ||
|
||
{% block content %} | ||
<div class="container"> | ||
<div class="columns"> | ||
<div class="column"> | ||
{% render_table table %} | ||
</div> | ||
</div> | ||
</div> | ||
{% endblock %} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{% extends "layouts/base_app.html" %} | ||
{% load crispy_forms_tags %} | ||
|
||
{% block page_title %} | ||
Log attendance for {{ team }} | ||
{% endblock %} | ||
{% block title %} | ||
Log attendance for {{ team }} | ||
{% endblock %} | ||
|
||
{% block content %} | ||
<form class="block" method="post"> | ||
{% csrf_token %} | ||
{{form|crispy}} | ||
<input class="button is-primary" type="submit" value="Save"> | ||
</form> | ||
{% endblock %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue: These should be the default types for these fields. You should be able to remove these lines entirely.