Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/audit/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .service import log_event

_TRACKED_FIELDS = (
'title', 'project_id', 'category_id', 'group_id',
'title', 'project_id', 'category_id', 'entry_type_id', 'group_id',
'period_kind', 'period_start', 'period_end',
'description', 'is_private', 'is_critical', 'is_highlight', 'highlight_stars',
'is_division_head_only', 'author_id', 'is_archived',
Expand Down
4 changes: 4 additions & 0 deletions apps/core/templates/core/api.html
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ <h2 class="text-base font-semibold text-slate-900">Taxonomy</h2>
{% include "core/partials/_route_row.html" with method="POST" path="/taxonomy/categories/" badge="manager" desc="Create a new category" %}
{% include "core/partials/_route_row.html" with method="GET" path="/taxonomy/categories/&lt;id&gt;/edit/" badge="manager" desc="Edit category form" %}
{% include "core/partials/_route_row.html" with method="POST" path="/taxonomy/categories/&lt;id&gt;/edit/" badge="manager" desc="Save category changes" %}
{% include "core/partials/_route_row.html" with method="GET" path="/taxonomy/entry-types/" badge="manager" desc="Manage entry types — list and add" %}
{% include "core/partials/_route_row.html" with method="POST" path="/taxonomy/entry-types/" badge="manager" desc="Create a new entry type" %}
{% include "core/partials/_route_row.html" with method="GET" path="/taxonomy/entry-types/&lt;id&gt;/edit/" badge="manager" desc="Edit entry type form" %}
{% include "core/partials/_route_row.html" with method="POST" path="/taxonomy/entry-types/&lt;id&gt;/edit/" badge="manager" desc="Save entry type changes" %}
{% include "core/partials/_route_row.html" with method="GET" path="/taxonomy/groups/" badge="manager" desc="Manage work groups — list and add" %}
{% include "core/partials/_route_row.html" with method="POST" path="/taxonomy/groups/" badge="manager" desc="Create a new work group" %}
{% include "core/partials/_route_row.html" with method="GET" path="/taxonomy/groups/&lt;id&gt;/edit/" badge="manager" desc="Edit work group form" %}
Expand Down
4 changes: 2 additions & 2 deletions apps/entries/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

@admin.register(WorkItem)
class WorkItemAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'project', 'category',
list_display = ('title', 'author', 'project', 'category', 'entry_type',
'period_start', 'period_end', 'is_private', 'created_at')
list_filter = ('project', 'category', 'is_private')
list_filter = ('project', 'category', 'entry_type', 'is_private')
search_fields = ('title', 'description', 'author__email', 'author__display_name')
raw_id_fields = ('author',)
filter_horizontal = ('tags',)
Expand Down
6 changes: 4 additions & 2 deletions apps/entries/forms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django import forms
from django.db.models import F

from apps.taxonomy.models import Category, LabPriority, Project, Tag, WorkGroup
from apps.taxonomy.models import Category, EntryType, LabPriority, Project, Tag, WorkGroup

from .models import WorkItem

Expand All @@ -12,7 +12,7 @@ class WorkItemForm(forms.ModelForm):
class Meta:
model = WorkItem
fields = [
'title', 'project', 'category', 'group', 'lab_priority',
'title', 'project', 'category', 'entry_type', 'group', 'lab_priority',
'period_kind', 'period_start', 'period_end',
'description', 'is_private', 'is_critical', 'is_highlight', 'highlight_stars',
'is_division_head_only',
Expand All @@ -29,6 +29,8 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['project'].queryset = Project.objects.filter(is_active=True).order_by('sort_order', 'name')
self.fields['category'].queryset = Category.objects.filter(is_active=True).order_by('sort_order', 'name')
self.fields['entry_type'].queryset = EntryType.objects.filter(is_active=True).order_by('sort_order', 'name')
self.fields['entry_type'].required = False
self.fields['group'].queryset = WorkGroup.objects.filter(is_active=True).order_by('sort_order', 'name')
self.fields['group'].required = False
self.fields['lab_priority'].queryset = LabPriority.objects.filter(is_active=True).order_by('sort_order', 'name')
Expand Down
35 changes: 35 additions & 0 deletions apps/entries/migrations/0008_workitem_entry_type_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 5.2.14 on 2026-06-25 17:07

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


class Migration(migrations.Migration):

dependencies = [
("entries", "0007_add_entry_template"),
("taxonomy", "0004_entrytype"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.AddField(
model_name="workitem",
name="entry_type",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="work_items",
to="taxonomy.entrytype",
),
),
migrations.AddIndex(
model_name="workitem",
index=models.Index(
fields=["entry_type", "-period_end"],
name="entries_wor_entry_t_d54d2e_idx",
),
),
]
7 changes: 7 additions & 0 deletions apps/entries/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ class PeriodKind(models.TextChoices):
on_delete=models.PROTECT,
related_name='work_items',
)
entry_type = models.ForeignKey(
'taxonomy.EntryType',
null=True, blank=True,
on_delete=models.PROTECT,
related_name='work_items',
)
group = models.ForeignKey(
'taxonomy.WorkGroup',
null=True, blank=True,
Expand Down Expand Up @@ -66,6 +72,7 @@ class Meta:
models.Index(fields=['author', '-period_end']),
models.Index(fields=['project', '-period_end']),
models.Index(fields=['category', '-period_end']),
models.Index(fields=['entry_type', '-period_end']),
models.Index(fields=['period_start', 'period_end']),
models.Index(fields=['is_private', '-period_end']),
]
Expand Down
6 changes: 6 additions & 0 deletions apps/entries/templates/entries/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ <h1 class="text-2xl font-bold text-slate-900 mt-1">{{ object.title }}</h1>
<dt class="text-xs font-medium text-slate-500 uppercase tracking-wide">Category</dt>
<dd class="mt-0.5 text-slate-700">{{ object.category.name }}</dd>
</div>
{% if object.entry_type %}
<div>
<dt class="text-xs font-medium text-slate-500 uppercase tracking-wide">Entry Type</dt>
<dd class="mt-0.5 text-slate-700">{{ object.entry_type.name }}</dd>
</div>
{% endif %}
{% if object.group %}
<div>
<dt class="text-xs font-medium text-slate-500 uppercase tracking-wide">Group</dt>
Expand Down
20 changes: 19 additions & 1 deletion apps/entries/templates/entries/form.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ <h1 class="text-2xl font-bold text-slate-900 mt-1">
{% endfor %}
</div>

{# ── Project / Category / Group / Lab Priority ────────────────────────── #}
{# ── Project / Category / Entry Type / Group / Lab Priority ───────────── #}
<div class="px-6 py-5 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<label for="{{ form.project.id_for_label }}" class="block text-sm font-medium text-slate-700 mb-1">
Expand Down Expand Up @@ -80,6 +80,24 @@ <h1 class="text-2xl font-bold text-slate-900 mt-1">
<p class="mt-1 text-xs text-red-600">{{ error }}</p>
{% endfor %}
</div>
<div>
<label for="{{ form.entry_type.id_for_label }}" class="block text-sm font-medium text-slate-700 mb-1">
Entry Type
</label>
<select name="entry_type" id="{{ form.entry_type.id_for_label }}"
class="w-full rounded-md border {% if form.entry_type.errors %}border-red-400{% else %}border-slate-300{% endif %} px-3 py-2 text-sm shadow-sm
focus:border-scd-primary focus:ring-1 focus:ring-scd-primary focus:outline-none">
<option value="">— none —</option>
{% for pk, label in form.fields.entry_type.choices %}
{% if pk %}
<option value="{{ pk }}" {% if form.entry_type.value|stringformat:"s" == pk|stringformat:"s" %}selected{% endif %}>{{ label }}</option>
{% endif %}
{% endfor %}
</select>
{% for error in form.entry_type.errors %}
<p class="mt-1 text-xs text-red-600">{{ error }}</p>
{% endfor %}
</div>
<div>
<label for="{{ form.group.id_for_label }}" class="block text-sm font-medium text-slate-700 mb-1">
Group
Expand Down
7 changes: 6 additions & 1 deletion apps/reports/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.db.models import Q

from apps.entries.models import WorkItem
from apps.taxonomy.models import Category, LabPriority, Project, WorkGroup
from apps.taxonomy.models import Category, EntryType, LabPriority, Project, WorkGroup


class WorkItemFilter(django_filters.FilterSet):
Expand Down Expand Up @@ -38,6 +38,11 @@ class WorkItemFilter(django_filters.FilterSet):
label='Category',
empty_label='All categories',
)
entry_type = django_filters.ModelChoiceFilter(
queryset=EntryType.objects.filter(is_active=True).order_by('sort_order', 'name'),
label='Entry Type',
empty_label='All entry types',
)
lab_priority = django_filters.ModelChoiceFilter(
queryset=LabPriority.objects.filter(is_active=True).order_by('sort_order', 'name'),
label='Lab Priority',
Expand Down
15 changes: 15 additions & 0 deletions apps/reports/templates/reports/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,21 @@ <h2 class="text-sm font-semibold text-slate-700 uppercase tracking-wide">Filters
</select>
</div>

{# Entry Type #}
<div>
<label class="block text-xs font-medium text-slate-500 mb-1">Entry Type</label>
<select name="entry_type"
class="w-full rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm
focus:border-scd-primary focus:ring-1 focus:ring-scd-primary focus:outline-none">
<option value="">All entry types</option>
{% for pk, label in filter.filters.entry_type.field.choices %}
{% if pk %}
<option value="{{ pk }}" {% if filter.form.entry_type.value|stringformat:"s" == pk|stringformat:"s" %}selected{% endif %}>{{ label }}</option>
{% endif %}
{% endfor %}
</select>
</div>

{# Lab Priority #}
<div>
<label class="block text-xs font-medium text-slate-500 mb-1">Lab Priority</label>
Expand Down
10 changes: 9 additions & 1 deletion apps/taxonomy/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.contrib import admin

from .models import Category, LabPriority, Project, Tag, WorkGroup
from .models import Category, EntryType, LabPriority, Project, Tag, WorkGroup


@admin.register(Project)
Expand All @@ -19,6 +19,14 @@ class CategoryAdmin(admin.ModelAdmin):
search_fields = ('name', 'short_code')


@admin.register(EntryType)
class EntryTypeAdmin(admin.ModelAdmin):
list_display = ('name', 'short_code', 'slug', 'is_active', 'sort_order')
list_editable = ('is_active', 'sort_order')
prepopulated_fields = {'slug': ('name',)}
search_fields = ('name', 'short_code')


@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ('name', 'use_count')
Expand Down
11 changes: 10 additions & 1 deletion apps/taxonomy/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django import forms

from .models import Category, LabPriority, Project, WorkGroup
from .models import Category, EntryType, LabPriority, Project, WorkGroup


class ProjectForm(forms.ModelForm):
Expand All @@ -21,6 +21,15 @@ class Meta:
}


class EntryTypeForm(forms.ModelForm):
class Meta:
model = EntryType
fields = ['name', 'short_code', 'is_active', 'sort_order']
widgets = {
'sort_order': forms.NumberInput(attrs={'min': 0}),
}


class WorkGroupForm(forms.ModelForm):
class Meta:
model = WorkGroup
Expand Down
16 changes: 15 additions & 1 deletion apps/taxonomy/management/commands/seed_taxonomy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand

from apps.taxonomy.models import Category, LabPriority, Project
from apps.taxonomy.models import Category, EntryType, LabPriority, Project

PROJECTS = [
{'name': 'DUNE', 'short_code': 'DUNE', 'sort_order': 10},
Expand All @@ -17,6 +17,12 @@
{'name': 'Training', 'short_code': 'TRN', 'sort_order': 40},
]

ENTRY_TYPES = [
{'name': 'Weekly Report', 'short_code': 'WEEKLY', 'sort_order': 10},
{'name': 'Milestone', 'short_code': 'MILESTONE', 'sort_order': 20},
{'name': 'Activity', 'short_code': 'ACTIVITY', 'sort_order': 30},
]

LAB_PRIORITIES = [
{'name': 'Science Mission', 'short_code': 'SCI-MISS', 'sort_order': 10},
{'name': 'DUNE/LBNF', 'short_code': 'DUNE', 'sort_order': 20},
Expand Down Expand Up @@ -46,6 +52,14 @@ def handle(self, *args, **options):
status = 'created' if created else 'exists '
self.stdout.write(f' [{status}] {name}')

self.stdout.write('Seeding entry types…')
for data in ENTRY_TYPES:
name = data['name']
defaults = {k: v for k, v in data.items() if k != 'name'}
_, created = EntryType.objects.get_or_create(name=name, defaults=defaults)
status = 'created' if created else 'exists '
self.stdout.write(f' [{status}] {name}')

self.stdout.write('Seeding lab priorities…')
for data in LAB_PRIORITIES:
name = data['name']
Expand Down
41 changes: 41 additions & 0 deletions apps/taxonomy/migrations/0004_entrytype.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by Django 5.2.14 on 2026-06-25 17:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("taxonomy", "0003_labpriority"),
]

operations = [
migrations.CreateModel(
name="EntryType",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, unique=True)),
("slug", models.SlugField(blank=True, max_length=100, unique=True)),
("short_code", models.CharField(blank=True, max_length=20)),
("is_active", models.BooleanField(db_index=True, default=True)),
(
"sort_order",
models.PositiveSmallIntegerField(db_index=True, default=0),
),
],
options={
"verbose_name": "Entry Type",
"verbose_name_plural": "Entry Types",
"ordering": ["sort_order", "name"],
"abstract": False,
},
),
]
6 changes: 6 additions & 0 deletions apps/taxonomy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ class Meta(TaxonomyBase.Meta):
verbose_name_plural = 'categories'


class EntryType(TaxonomyBase):
class Meta(TaxonomyBase.Meta):
verbose_name = 'Entry Type'
verbose_name_plural = 'Entry Types'


class WorkGroup(TaxonomyBase):
class Meta(TaxonomyBase.Meta):
verbose_name = 'Group'
Expand Down
2 changes: 2 additions & 0 deletions apps/taxonomy/templates/taxonomy/categories.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ <h1 class="text-2xl font-bold text-slate-900">Taxonomy</h1>
class="px-4 py-2 text-sm font-medium text-slate-500 hover:text-slate-700 border-b-2 border-transparent -mb-px">Projects</a>
<a href="{% url 'taxonomy:categories' %}"
class="px-4 py-2 text-sm font-medium text-scd-primary border-b-2 border-scd-primary -mb-px">Categories</a>
<a href="{% url 'taxonomy:entry-types' %}"
class="px-4 py-2 text-sm font-medium text-slate-500 hover:text-slate-700 border-b-2 border-transparent -mb-px">Entry Types</a>
<a href="{% url 'taxonomy:groups' %}"
class="px-4 py-2 text-sm font-medium text-slate-500 hover:text-slate-700 border-b-2 border-transparent -mb-px">Groups</a>
<a href="{% url 'taxonomy:lab-priorities' %}"
Expand Down
29 changes: 29 additions & 0 deletions apps/taxonomy/templates/taxonomy/entry_type_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}Edit Entry Type — SCD Reporting{% endblock %}

{% block content %}
<div class="max-w-lg">
<div class="mb-6">
<a href="{% url 'taxonomy:entry-types' %}"
class="text-sm text-scd-primary hover:underline">← Back to entry types</a>
<h1 class="text-2xl font-bold text-slate-900 mt-2">Edit entry type</h1>
</div>

<div class="bg-white rounded-lg border border-slate-200 shadow-sm px-6 py-5">
<form method="post">
{% csrf_token %}
{% for field in form %}
{% include "components/_form_field.html" %}
{% endfor %}
<div class="flex gap-3 mt-2">
<button type="submit"
class="bg-scd-primary hover:bg-scd-primary-hover text-white text-sm font-medium px-4 py-2 rounded-md transition-colors">
Save
</button>
<a href="{% url 'taxonomy:entry-types' %}"
class="text-sm text-slate-500 hover:underline self-center">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
Loading