Time Entry Relationships¶
This page describes how a Time entry’s project, task, and
invoice fields are populated, how that differs between admin
(superuser) and non-admin users, and how new time is automatically
matched to an existing invoice.
Model relationships¶
Time.project->Project(optional foreign key)Time.task->Task(optional foreign key)Time.invoice->Invoice(optional foreign key)Project.default_task->Task(optional foreign key, used as a fallback when a time entry has no task)Invoice.project->Project, plusInvoice.start_date/Invoice.end_datedefine the billing period an invoice covers
How the project field is populated¶
Unlike task, there is no automatic default for project at
the model level. Time.save() (see db/models.py) only defaults
task, never project.
project is a normal, required ModelChoiceField on TimeForm
(db/forms.py), backed by the unfiltered Project.objects.all()
queryset (ordered by Project.Meta.ordering = ["name"]). This field
is never removed or hidden for non-admin users, so:
Both admins and non-admins see the same visible
projectdropdown listing every project in the system.There is no per-user scoping of which projects appear (
Projecthas no owner/user field), and no default is pre-selected.The user must manually choose a project every time they create a time entry, unless a query-string pre-fill applies (see below).
Query-string pre-fill (?invoice_id=)¶
TimeCreateView.get_context_data() (db/views/time.py) will
pre-populate the form’s initial project value when the create
URL includes ?invoice_id=<id> and that invoice has a project set.
This only sets the initial selection; the field remains editable and
this pre-fill only applies if the project field is present on the
form (it always is).
Admin-only dropdown cascade (JavaScript)¶
frontend/src/utils/timeForm.js wires up a live cascade between the
invoice, project, and task dropdowns using the
time_api_invoice / time_api_project / time_api_task JSON
endpoints (db/urls.py):
Changing invoice -> sets
projectto the invoice’s project andtaskto that project’s default task.Changing project -> sets
taskto that project’s default task.Changing task -> sets
projectto that task’s project.
This cascade only runs when all three dropdowns
(``id_invoice``, ``id_project``, ``id_task``) are present on the page.
Since invoice and task are removed from the form for non-admin
users (see below), the cascade is effectively admin-only; non-admins
just get the plain, unfiltered project select with no
auto-population.
Admin vs. non-admin form differences¶
TimeForm.__init__ (db/forms.py) adjusts fields based on the
user passed in from the view:
Admin (superuser):
userfield is shown as a visibleSelect(instead of hidden);invoice,task, andnamefields are shown.Non-admin:
userstays hidden (defaults to the logged-in user);invoice,task, andnamefields are removed entirely from the form.``project``: present and visible for both admins and non-admins; never hidden or removed.
Because non-admins never see task or invoice fields, those are
filled in automatically:
taskfalls back toproject.default_task, or the global default fromTask.get_default_task()(name “Software Development”), viaTime.save().invoiceis filled in by the auto-assignment signal described below.
Auto-assigning new time entries to an invoice¶
Time entries are typically logged after an invoice period has
already been opened (rather than an invoice being created after time
already exists). To support that, a pre_save signal on Time
(db/signals.py::auto_assign_time_to_invoice) automatically attaches
a newly created, unbilled entry to an existing invoice when:
the entry has no
invoiceset yet (manual assignment is always respected and never overridden),it has a
projectset, andit has a
datethat falls within anInvoice.start_date/Invoice.end_daterange for an invoice on that same project.
If more than one invoice matches (e.g. overlapping periods), the
invoice with the most recent start_date is preferred. Saves to an
existing time entry (i.e. one that already has a primary key) never
trigger auto-assignment, only creation does.
See db/tests/test_signals.py::TimeAutoAssignInvoiceSignalTest for
the covered scenarios.