2026-08-02 14:38:12 +00:00
# Tasks
Routes in `backend/tasks/urls.py` . CRUD, comments and attachments are in
`backend/tasks/views/task_views.py` ; Kanban pipelines/stages and the move endpoint in
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`backend/tasks/views/kanban_views.py` ; Kanban **boards** , a separate feature, in
2026-08-02 14:38:12 +00:00
`backend/tasks/views/board_views.py` . Object-level authorization is centralized in
`backend/tasks/access.py` . See [Conventions ](conventions.md ) for pagination, filtering and
response-shape rules assumed rather than repeated here, and [Errors ](errors.md ) for the
404-vs-403 principle referenced below.
## `Task.save()` calls `full_clean()`
Before anything else, one fact that changes how you should read every write endpoint below:
**`Task.save()` explicitly calls `self.full_clean()` ** (`tasks/models.py:402-404`). This is not the
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
default, [Architecture: Data model ](../architecture/data-model.md#conventions ) documents that most
2026-08-02 14:38:12 +00:00
models here (`Lead`, `Opportunity` , `Case` ) define real validation in `clean()` that is never invoked
through the API, because their `save()` methods don't call `full_clean()` . `Task` is one of a small
handful that do (alongside `Comment.save()` and `Attachments.save()` , which enforce that a comment or
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
attachment's `org` matches its parent record's org. Same page). The consequence is the opposite
2026-08-02 14:38:12 +00:00
problem: when `full_clean()` *does* run and finds a violation, it raises Django's own
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`django.core.exceptions.ValidationError` , not DRF's, and [Errors ](errors.md ) states plainly, in its
2026-08-02 14:38:12 +00:00
own opening paragraph, that this codebase's `EXCEPTION_HANDLER` is DRF's default, unmodified, which
does not know how to render that exception. Unless the specific view code path catches it, a
model-level rule violation on a `Task` reaches the client as an unhandled ** `500` **, not a clean `400` .
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
The one rule `Task.clean()` currently enforces is "at most one parent entity". A task may link to
2026-08-02 14:38:12 +00:00
at most one of `account` , `opportunity` , `case` , `lead` (`tasks/models.py:384-400`). The main
create/update surface guards against this twice: `TaskCreateSerializer.validate()`
(`tasks/serializer.py:331-365`) re-implements the same rule and returns a clean `400` with the field
named, *and* `TaskListView.post` / `TaskDetailView.put` / `TaskDetailView.patch` additionally wrap
`serializer.save()` in `try/except django.core.exceptions.ValidationError` as a backstop
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
(`_model_errors`, `tasks/views/task_views.py:56-66` , used at `:277-281,577-581,702-706` ), so for
2026-08-02 14:38:12 +00:00
`POST /api/tasks/` , `PUT /api/tasks/{id}/` and `PATCH /api/tasks/{id}/` specifically, this rule
reaches the client as `{"error": true, "errors": {"account": "..."}}` with `400` today, not a `500` .
**Not every write path shares that backstop.** `PATCH /api/tasks/{id}/move/`
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
(`TaskMoveView.patch`, `tasks/views/kanban_views.py:253-318` ), the Kanban drag-and-drop endpoint,
2026-08-02 14:38:12 +00:00
sets `task.stage` , `task.status` and `task.kanban_order` directly and calls `task.save()` with no
`try/except` around it (`:310`) and without going through `TaskCreateSerializer` at all. `full_clean()`
validates every field on the instance, not only the ones a given endpoint touched, so any model-level
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
violation it finds here, on this task's existing `account` /`opportunity`/`case`/`lead` combination, or
any other `clean_fields()` rule, surfaces as the unhandled `500` described above. This isn't only a
2026-08-02 14:38:12 +00:00
theoretical exposure through the parent-entity fields: `TaskStage.maps_to_status`
(`tasks/models.py:257-263`) is a plain `CharField` , `blank=True, null=True` , with no `choices=`
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
constraint. Free text an admin sets when configuring a pipeline stage, not validated against
2026-08-02 14:38:12 +00:00
`Task.STATUS_CHOICES` at all. When a task moves into a stage that has one set,
`if stage.maps_to_status: task.status = stage.maps_to_status` (`kanban_views.py:297-298`) assigns it
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
straight onto `task.status` . A field that *does* have `choices=` at the model level. A single typo'd
2026-08-02 14:38:12 +00:00
`maps_to_status` on one admin-configured stage turns every future move into that stage into an
unhandled `500` for every user, until an admin notices and fixes the stage. This endpoint has no
equivalent of the two-layer guard the main CRUD endpoints have.
## List tasks
`GET /api/tasks/` (`TaskListView.get`, `tasks/views/task_views.py:233-235` ) returns one paginated
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
list. Visibility is `visible_tasks_qs` (`tasks/access.py:43-48`), admin sees every task in the org;
2026-08-02 14:38:12 +00:00
anyone else sees only tasks they created or are assigned to. This is the same rule the detail view
enforces (`tasks/access.py` module docstring: "the list has to agree with the detail view or the queue
lies").
```json
{
"tasks_count": 24,
"offset": 10,
"tasks": [ { "...": "TaskListSerializer" } ],
"totals": {"count": 24, "open": 18, "overdue": 3, "due_today": 1, "due_this_week": 5, "no_due_date": 6, "unassigned": 2},
"status": [["New", "New"], ["In Progress", "In Progress"], ["Completed", "Completed"]],
"priority": [["Low", "Low"], ["Medium", "Medium"], ["High", "High"]],
"users": [{"id": "...", "user__email": "..."}],
"accounts_list": [ { "...": "AccountSerializer" } ],
"contacts_list": [ { "...": "ContactSerializer" } ]
}
```
(`tasks/views/task_views.py:130-209`.) `totals` is one aggregate query over the whole filtered,
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
visibility-scoped queryset, not the page, `overdue` /`due_today`/`due_this_week`/`no_due_date`/
2026-08-02 14:38:12 +00:00
`unassigned` all additionally require the task still be open (`status != "Completed"`), so a task that
finished late does not count as overdue (`:145-167`). `accounts_list` /`contacts_list` are the full org
catalogues for the parent-entity picker; `?slim=true` omits both (`:203-209`).
Filters, read directly from `request.query_params`
(`tasks/views/task_views.py:86-129`): `title` (contains), `status` (exact), `priority` (exact),
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`assigned_to` (repeatable id list), `tags` (id list), `search` (matches `title` , contains, not a
2026-08-02 14:38:12 +00:00
combined search across other fields the way [Leads ](leads.md#list-leads ) or
[Contacts ](contacts.md#list-contacts ) define one), `due_date__gte` /`due_date__lte`,
`created_at__gte` /`created_at__lte`, `account` , `opportunity` , `case` , `lead` (each an exact parent-id
match), and `cf_<key>` (custom field equals).
## Create a task
`POST /api/tasks/` (`TaskListView.post`, `tasks/views/task_views.py:252-349` ) accepts JSON or
`multipart/form-data` , validated by `TaskCreateSerializer` (`tasks/serializer.py:315-389`; see
[Fields ](#fields )). On success (`200`): `{"error": false, "message": "Task Created Successfully"}` . A
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
validation failure returns `400` with `{"error": true, "errors": {...}}` , including the "at most one
2026-08-02 14:38:12 +00:00
parent" rule and the model's own `full_clean()` backstop, both described [above ](#tasksave-calls-full_clean ).
`account` , `opportunity` , `case` and `lead` are each scoped to the caller's org in
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`TaskCreateSerializer.__init__` (`tasks/serializer.py:328-329`), the comment there notes a plain
2026-08-02 14:38:12 +00:00
`ModelSerializer` would otherwise build each as a `PrimaryKeyRelatedField` over *every* row in the
table, and that a task in one org was proven to accept another org's account, with the list endpoint
then rendering that org's account name back to the requester. `created_by` is listed in
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`Meta.fields` but is `read_only` (`:389`), the comment explains that leaving it writable let a client
2026-08-02 14:38:12 +00:00
name any user in any org as a task's creator, which becomes a privilege-escalation vector the moment
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
the creator clause in `tasks/access.py` is consulted for anything (which it is. See
2026-08-02 14:38:12 +00:00
[Fields ](#fields )).
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`contacts` , `teams` , `assigned_to` and `tags` are **not** part of `TaskCreateSerializer` : the view
2026-08-02 14:38:12 +00:00
resolves each from the request body after the serializer succeeds, org-scoped, `json.loads` 'd when
sent as a string (the same shape [Cases ](cases.md#create-a-case ) uses). *Parsing* is consistent across
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
all four fields on every verb. Tasks does not have the per-field inconsistency
2026-08-02 14:38:12 +00:00
[Leads ](leads.md#fields ) documents. *Writing* is a different story, and the same data-loss trap
[Cases ](cases.md#fields ) documents applies here verbatim: `POST` only adds
(`task_views.py:282,296,308,322`); `PUT` calls `.clear()` on all four **unconditionally** , before
checking whether the body mentioned them at all (`:582,597,610,625`), so a `PUT` that omits
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`assigned_to` unassigns the task from everyone, including, via `visible_tasks_qs`
2026-08-02 14:38:12 +00:00
(`tasks/access.py:43-48`), removing it from those people's task lists entirely; `PATCH` clears a given
M2M only when its key is present in the body (`:709-758`).
## Retrieve, update, delete
`GET /api/tasks/{id}/` (`TaskDetailView.get`, `tasks/views/task_views.py:460-463` ) org-scopes the
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
lookup (`get_task_or_404`, `tasks/access.py:51-65` , a malformed id or a cross-org task both answer
`404` ) and enforces `assert_task_access` before building the response (`:365`): raised, not returned,
2026-08-02 14:38:12 +00:00
specifically because a `Response` object handed back through this method used to be wrapped in a
second `Response` and crash with a `500` instead of answering `403` (comment at `:361-364` ). The
response nests the record under `task_obj` , alongside `attachments` , `comments` , `users_mention` ,
`assigned_data` , `custom_field_definitions` , `users` (admins see every active member; non-admins see
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
only other admins, the same shape [Cases ](cases.md#list-cases ) uses for its assignee picker),
2026-08-02 14:38:12 +00:00
`users_excluding_team` and `teams` .
`PUT /api/tasks/{id}/` (`:545-650`) and `PATCH /api/tasks/{id}/` (`:667-781`) both enforce
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`assert_task_access` : access here has only two tiers, not three the way a case does: ** `access`
2026-08-02 14:38:12 +00:00
(read/write together) is admin · creator · assignee; `delete` is the narrower admin · creator**
(`tasks/access.py:1-10`). The module docstring is explicit about why a task collapses read and write
into one rule where a case does not: a case can have a *watcher* , someone following a ticket without
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
being handed it, and that extra read-only tier has no equivalent on a task; "everyone who may open one
2026-08-02 14:38:12 +00:00
may also work it." `contacts` /`teams`/`assigned_to`/`tags` are re-attached from the request body the
same way `POST` does. Success: `{"error": false, "message": "Task updated Successfully"}` .
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`DELETE /api/tasks/{id}/` (`:797-805`) calls `assert_task_delete_access` : admin or creator only, no
2026-08-02 14:38:12 +00:00
assignee exception, same reasoning as [Cases ](cases.md#retrieve-update-delete ): being assigned a task
is a reason to work it, not to erase it.
## Comments and attachments
The same shape as [Cases ](cases.md#comments-and-attachments ): `POST /api/tasks/{id}/`
(`TaskDetailView.post`, `tasks/views/task_views.py:481-528` ) adds a `comment` and/or a
`task_attachment` file to an existing task, gated by `assert_task_access` . The comment on this method
notes a real trap: validating the comment through `CommentSerializer` fails *silently* here, because
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
that serializer requires `object_id` and `org` , neither of which the client sends; `is_valid()`
2026-08-02 14:38:12 +00:00
returns `False` and the intended write is simply skipped, so the view constructs the `Comment` directly
with Django's ORM instead of going through the serializer (`:488-500`).
`PUT` /`PATCH`/`DELETE /api/tasks/comment/{id}/` (`TaskCommentView`, `:808-946` ) edit or remove an
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
existing comment: admin, or the comment's own author (`request.profile == obj.commented_by`, a
2026-08-02 14:38:12 +00:00
`Profile` -to-`Profile` comparison, correct). ** `PUT` cannot, in practice, edit a comment.** It builds
`CommentSerializer(obj, data=params)` with no `partial=True` (`:851`), and `CommentSerializer.Meta.fields`
includes the plain (not `read_only` ) model fields `object_id` and `org`
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
(`common/serializer.py:239-250`): a body of just `{"comment": "..."}` , everything the documented
2026-08-02 14:38:12 +00:00
`TaskCommentEditSwaggerSerializer` contract asks for, fails validation on the missing `object_id` /`org`
and answers `400` . Only `.patch()` (`partial=True`, `:896` ) can actually change a comment's text.
Separately, because `object_id` /`org` genuinely are writable on `PATCH` , a comment's own author can
supply both there to repoint the comment at a different record, including one in another org or one
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
they cannot otherwise read, `Comment.clean()` (`common/models.py:299-314`) only catches the mismatched
2026-08-02 14:38:12 +00:00
case (`org` doesn't match the new target's `org` ), and only because `Comment.save()` calls
`self.full_clean()` (`:316-318`), which turns that mismatch into an unhandled `500` rather than a `400`
(same failure mode as [above ](#tasksave-calls-full_clean ), on a different model); a *matching* cross-org
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
pair saves without error. Neither behavior is fixed here, reported per this documentation task's
2026-08-02 14:38:12 +00:00
scope. The identical `PUT` /`PATCH` pattern exists on [Cases ](cases.md#comments-and-attachments )' own
comment-edit endpoint too.
`DELETE /api/tasks/attachment/{id}/` (`TaskAttachmentView.delete`, `:949-1000` ) is org-scoped
(`.filter(pk=pk, org=request.profile.org)`, `:975-977` ) and its ownership check compares
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`request.profile.user_id` to `self.object.created_by_id` (`:987`), both fixed here. The comment on
2026-08-02 14:38:12 +00:00
this method documents that both defects (an unscoped lookup across every org's attachments, and a
`Profile` -to-`User` comparison that silently made the endpoint admin-only) were proven live before the
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
fix. See [Architecture: Permissions and roles ](../architecture/permissions-and-roles.md#object-level-checks )
2026-08-02 14:38:12 +00:00
for the current, cross-app state of the same defect pattern in other modules.
## Boards and columns
**Kanban *boards* are a separate feature from a task's own `stage` /`pipeline` fields, with their own
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
model family (`Board`, `BoardColumn` , `BoardTask` , `BoardMember` , `tasks/models.py:16-194` ) and their
2026-08-02 14:38:12 +00:00
own endpoints mounted at `/api/boards/...` , not under `/api/tasks/...` .** Confirmed in
`backend/common/app_urls/__init__.py:26` : `path("boards/", include((board_urlpatterns, "api_boards")))` ,
where `board_urlpatterns` is imported from `tasks/urls.py:58-75` . A `BoardTask` card is not a `Task`
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
row. It has its own title/description/priority/assignees and only optionally references an
2026-08-02 14:38:12 +00:00
`account` /`contact`/`opportunity` for context (`tasks/models.py:116-195`); the `/api/tasks/kanban/` ,
`/api/tasks/pipelines/...` , `/api/tasks/pipelines/{pipeline_id}/stages/` and `/api/tasks/stages/{id}/`
endpoints documented implicitly above (via `stage` in [Fields ](#fields )) operate on real `Task` rows and
are a different Kanban system entirely.
Access to a board is per-board membership, not org-wide: `Board.owner` plus `BoardMember` rows carry a
`role` of `owner` /`admin`/`member` (`tasks/models.py:43-81`). Every endpoint that acts on an *existing*
board checks membership before doing anything (the one exception is creating a board in the first
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
place; `POST /api/boards/` requires nothing beyond org membership, since there's no board to be a
2026-08-02 14:38:12 +00:00
member of yet); some further gate by role:
| Endpoint | Who |
| --- | --- |
| `GET /api/boards/` | Any authenticated member of the org; results are owner-or-member boards only (`board_views.py:82-89`) |
| `POST /api/boards/` | Any org member; creates the board and, by default, three starter columns (`:126-157`) |
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
| `GET /api/boards/{id}/` | Board owner or member (`404` otherwise, not `403` , `:165-192` ) |
2026-08-02 14:38:12 +00:00
| `PUT/PATCH /api/boards/{id}/` | Board `owner` or `admin` role (`:206-271`) |
| `DELETE /api/boards/{id}/` | Board owner only (`:282-299`) |
| `GET /api/boards/{board_id}/columns/` | Board owner or member (`:316-329`) |
| `POST /api/boards/{board_id}/columns/` | Board `owner` or `admin` role (`:355-367`) |
| `GET/POST /api/boards/columns/{column_id}/tasks/` | Any board member (no role check, `:413-455` ) |
| `PUT /api/boards/tasks/{id}/` | Any board member (no role check, `:490-503` ) |
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
| `DELETE /api/boards/tasks/{id}/` | Any board member, the role check lists `owner` /`admin`/`member`, which is every role that can exist (`:570-583`) |
2026-08-02 14:38:12 +00:00
**A malformed board/column/card id is a `500` , not a `404` , on every endpoint in this table.** Every
lookup in `board_views.py` uses Django's `get_object_or_404` against a UUID primary key
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
(`:167,319,358,416,445,493,573`), and `get_object_or_404` only catches the model's `DoesNotExist` , not
2026-08-02 14:38:12 +00:00
the `ValidationError` /`ValueError` a non-UUID string raises against a UUID field. This is the exact bug
class `get_task_or_404` /`get_case_or_404` (`tasks/access.py:51-65`, `cases/access.py:57-71` ) exist to
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
prevent on the rest of this API; boards never adopted that helper. Not fixed here, reported per this
2026-08-02 14:38:12 +00:00
documentation task's scope.
**There is no endpoint to update or delete a single column once created.** `board_urlpatterns`
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
(`tasks/urls.py:58-75`) defines only `GET` /`POST /api/boards/{board_id}/columns/`, a list-and-create
route, with no `columns/{id}/` detail path at all. A column's name, color, order or WIP `limit` cannot
2026-08-02 14:38:12 +00:00
be changed, and a column cannot be removed, through this API today.
**`GET /api/boards/{id}/` does not return each column's cards.** `BoardSerializer.columns` uses
`BoardColumnListSerializer` (`tasks/serializer.py:109-121,123-143`), which reports only a `task_count`
per column, not the cards themselves. To read the actual cards, call
`GET /api/boards/{board_id}/columns/` separately, which uses the full `BoardColumnSerializer` and
nests nested `BoardTaskSerializer` cards under each column (`board_views.py:334-343`).
`PUT /api/boards/tasks/{id}/` (`BoardTaskDetailView.put`, `board_views.py:490-559` ) is also how a card
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
moves between columns: send `column` (validated to be a column of the same board, a column id from
2026-08-02 14:38:12 +00:00
another board, or from another org's board by construction, is a `400` , not a silent no-op,
`:512-529` ) and/or `order` ; the view renumbers both the source and target columns to a dense
`0..n-1` ordering after the move so drag positions survive a reload (`_resequence_column`, `:22-48` ).
`BoardTaskSerializer` 's write surface is deliberately narrow (`tasks/serializer.py:36-90`): a card
write may set `title` , `description` , `order` , `priority` , `due_date` , and assignees (via the
write-only `assigned_to_ids` , applied by the view after save and org-filtered). Everything else is
locked down, by two different mechanisms: `id` , `created_at` , `updated_at` , `completed_at` , `org` ,
`column` , `created_by` , `updated_by` , `contact` and `opportunity` are all in `read_only_fields`
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
(`:67-78`): `column` because moving a card is the dedicated `PUT` flow above, and the CRM-link FKs
2026-08-02 14:38:12 +00:00
because, unfiltered, they exposed the same cross-org `PrimaryKeyRelatedField` gap
`TaskCreateSerializer` was hardened against. `account` reaches the same outcome a different way: it's
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
overridden as a plain `SerializerMethodField()` (`:60`), not listed in `read_only_fields` at all,
2026-08-02 14:38:12 +00:00
overriding a model FK with a method field removes it from the write surface just as effectively,
per the field's own comment (`:82-84`).
Add comprehensive tests for ticket closing, calendar feed, deal board, macros, session caches, and web form models
- Implement tests for closing tickets via menu and status picker, ensuring correct API interactions and error handling.
- Create tests for calendar feed provider, verifying throttling behavior and success responses.
- Add tests for deal board provider, covering reading and moving deals, including edge cases for card movement and filters.
- Enhance macro tests to validate action parsing and application to tickets, including error handling.
- Introduce session cache tests to ensure proper cleanup on sign-out and validate session-scoped providers.
- Extend web form model tests to cover lead rotation functionality, ensuring correct serialization and defaults.
2026-09-27 00:58:09 +00:00
## Calendar feed
From django-crm 1.13.0 each member can subscribe a calendar app (Google Calendar, Outlook, Apple
Calendar) to their open tasks. Management is `CalendarFeedView` and the feed itself
`PublicCalendarFeedView` , both in `backend/common/views/calendar_feed_views.py` ; the iCalendar
writer is `backend/common/calendar_feed.py` and the token model `CalendarFeedToken` in
`backend/common/models.py` .
### Managing your feed
`/api/profile/calendar-feed/` manages the caller's own feed and nobody else's: every query filters
on `org=request.profile.org` and `profile=request.profile` . It is on the credential deny-list in
`common/scopes.py` , so a personal access token or the org API key is refused whatever its scopes
(see [Tokens and API keys ](tokens-and-api-keys.md#what-no-token-may-do-whatever-its-scopes )). A feed
URL keeps working after the token that could have minted it is revoked, so only a signed-in session
may create one.
| Method | Does | Response |
| --- | --- | --- |
| `GET` | Reads the state | `200` `{"error": false, "enabled": true, "created_at": "...", "last_used_at": "..."}` |
| `POST` | Turns the feed on, or replaces it | `201` , the same fields plus `url` |
| `DELETE` | Turns it off | `200` with `enabled: false` |
The URL is returned by `POST` once and cannot be read back: only the SHA-256 of the token is stored.
`POST` on an enabled feed deletes the old token in the same transaction, so the old URL stops working
the moment the new one exists. `last_used_at` is when a calendar app last fetched the feed, moved
forward at most once an hour; it is `null` until the first fetch.
`url` is built from the `DOMAIN_NAME` setting (`common.links.api_url`), the API's public origin,
never from the request. The web app calls the API from its own server at `PUBLIC_DJANGO_API_URL`
(`http://backend:8000` in Docker), so a URL built from the request named a host no calendar service
can reach, and read `http://` behind a TLS terminator. Outside dev the backend refuses to start when
`DOMAIN_NAME` is a loopback or non-absolute URL; see
[Environment variables ](../reference/environment-variables.md ).
### The feed
`GET /api/public/calendar/{token}.ics` takes no credential except the token in the path and answers
`text/calendar` . It is what a calendar app polls, on its own schedule (Google refreshes a subscribed
calendar every few hours), so a change to a task reaches the calendar late.
- **Which tasks.** Status `New` or `In Progress` , with a due date from 90 days ago to 365 days ahead
in the org's timezone. Only the member's own tasks: ones whose `created_by` is their user or that
are assigned to them, for admins too (an admin's feed does not carry the rest of the org), and
never a task outside `visible_tasks_qs` . At most 1,000, keeping the tasks nearest today: tasks due
today or later, soonest first, then overdue tasks, most recent first, until the cap is reached. It
is re-read on every fetch, so an unassignment or a removal from the org applies to the next poll.
- **What an event shows.** One all-day event on the due date, marked free rather than busy, with the
task's title as the summary, `Priority: <priority>` and a link to `/tasks/{id}` in the web app as
the description, and the same link as its `URL` . Nothing else: not the task's own description,
and no contact, account or other record names, because a subscribed calendar is read by a
third-party service on the member's behalf.
- **Misses.** A malformed, unknown, replaced or disabled token, and one whose profile, user or org
is no longer active, all answer the same `404` `{"error": "Not found"}` .
- **Throttles.** `CALENDAR_FEED_THROTTLE_IP` (default `1000/hour` , per client IP; generous because
calendar services fetch from shared addresses) and `CALENDAR_FEED_THROTTLE_TOKEN` (default
`60/hour` , per feed, bucketed on the token's hash). See
[Environment variables ](../reference/environment-variables.md ).
- The response carries `Cache-Control: private, max-age=300` and `X-Robots-Tag: noindex` , and Sentry
events have the token cut from any URL they record.
`/api/public/calendar/` is in `RequireOrgContext.EXEMPT_PATHS` . The view finds the token row (the
`calendar_feed_token` table is not under RLS, like `personal_access_token` ), takes the org from its
profile, sets the RLS context, and only then reads tasks.
The URL is a secret. Anyone holding it can read the titles and priorities of the member's open
tasks without signing in, so share it with nothing but the calendar app, and regenerate it (a new
`POST` ) if it leaks. In the clients the feed is managed at `/profile/calendar-feed` on the web and
**Profile → Calendar feed** on the phone.
2026-08-02 14:38:12 +00:00
## Fields
`TaskCreateSerializer.Meta.fields` (`tasks/serializer.py:367-389`) is what `POST /api/tasks/` ,
`PUT /api/tasks/{id}/` and `PATCH /api/tasks/{id}/` accept.
| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `title` | string, max 200 | **required** | Explicitly forced `required=True` in `__init__` (`:321`), matching the model |
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
| `status` | one of `Task.STATUS_CHOICES` | **required** | `New` , `In Progress` , `Completed` , a different vocabulary from `Case.status` , see [Cases: Fields ](cases.md#fields ) |
| `priority` | one of `Task.PRIORITY_CHOICES` | **required** | `Low` , `Medium` , `High` , a different vocabulary from `Case.priority` |
2026-08-02 14:38:12 +00:00
| `due_date` | date | optional | |
| `description` | text | optional | |
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
| `account` | uuid | optional | Org-scoped; at most one of `account` /`opportunity`/`case`/`lead` may be set. See [above ](#tasksave-calls-full_clean ) |
2026-08-02 14:38:12 +00:00
| `opportunity` | uuid | optional | Same one-parent rule |
| `case` | uuid | optional | Same one-parent rule |
| `lead` | uuid | optional | Same one-parent rule |
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
| `created_by` |. | **read-only** | Listed in `Meta.fields` , but server-derived; see [Create a task ](#create-a-task ) |
2026-08-02 14:38:12 +00:00
Not part of `TaskCreateSerializer` , but accepted in the same request body: `contacts` , `teams` ,
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`assigned_to` , `tags` (each a list of ids, org-scoped. See [above ](#retrieve-update-delete ) for how
2026-08-02 14:38:12 +00:00
*writing* them diverges across verbs); plus `task_attachment` (a multipart file) and `custom_fields`
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
(an object). Neither of the last two goes through the serializer either. The view reads
2026-08-02 14:38:12 +00:00
`custom_fields` straight off `request.data` , validates it against the org's
`CustomFieldDefinition` rows, and passes the cleaned result to `serializer.save(custom_fields=...)`
on all three write verbs (`task_views.py:256-276,556-574,681-699`); `task_attachment` is read off
`request.FILES` the same way it is on [Cases ](cases.md#create-a-case ).
`GET /api/tasks/{id}/` returns the record via `TaskSerializer` ; `GET /api/tasks/` returns each row via
the slimmer `TaskListSerializer` , which additionally drops `description` , `custom_fields` , `contacts`
and `teams` (compare `TaskSerializer.Meta.fields` , `tasks/serializer.py:287-309` , against
`TaskListSerializer.Meta.fields` , `:256-271` ). **Two fields declared on `TaskSerializer` never actually
reach the client.** `task_attachment` and `task_comments` are declared as nested, read-only serializer
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
fields (`:280-281`, listed in `Meta.fields` at `:306-307` ), but `Task` has no attribute by either name.
2026-08-02 14:38:12 +00:00
There is no `GenericRelation` defined anywhere in this codebase (`grep -rn "GenericRelation"` outside
the virtualenv returns nothing), so DRF's attempt to read `task_attachment` /`task_comments` off the
instance raises `AttributeError` , which a read-only field with no explicit default converts into
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`SkipField` : the keys are silently omitted from every response, not emitted empty. The comments and
2026-08-02 14:38:12 +00:00
attachments a client actually needs come from the separate top-level `attachments` /`comments` keys
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
`TaskDetailView.get` builds independently (`:421-430`, [above ](#retrieve-update-delete )), a field
2026-08-02 14:38:12 +00:00
being present in `Meta.fields` is not proof it is ever emitted; check what the model can actually
resolve. ** `stage` and `kanban_order` are not returned by either `GET` , and are not in `Meta.fields`
Refactor comments for clarity and consistency across multiple screens and services
- Updated comments in LeadsListScreen, MoreScreen, TaskDetailScreen, TaskFormScreen, TasksListScreen, TicketAnalyticsScreen, TicketDetailScreen, TicketsListScreen, ApiService, AuthService, CrashReporting, DealCard, TicketCard, MultiSelectSheet, KanbanColumn, StageStepper, TicketApprovalPanel, TicketPropertiesCard, and TicketTimePanel to improve readability and maintain a consistent style.
- Adjusted punctuation and phrasing for better flow and understanding.
2026-08-03 04:06:48 +00:00
on either serializer at all**. They exist only on `TaskKanbanCardSerializer`
2026-08-02 14:38:12 +00:00
(`:519-540`), which backs the Kanban-specific endpoints (`/api/tasks/kanban/`,
`/api/tasks/{id}/move/` ), not the create/update endpoints this page covers.