Gantt Views
Gantt views display tasks on a timeline with support for scheduling, dependencies, and resource management.
Features
Section titled “Features”- Multiple time scales: Day, week, month, and quarter views
- Drag-and-drop: Reschedule tasks by dragging
- Resize handles: Change task duration by resizing
- Dependencies: Visual arrows connecting related tasks
- Grouping: Organize tasks by project, resource, or any field
Basic Gantt View
Section titled “Basic Gantt View”- data_type: UiView name: task_gantt_view identifier: task_gantt_view type: Gantt model: ProjectTask arch: - default_scale: week content: - type: field name: name display_mode: title - type: field name: planned_start display_mode: start - type: field name: planned_end display_mode: endArch Configuration
Section titled “Arch Configuration”The first element in arch can include configuration properties:
| Property | Type | Default | Description |
|---|---|---|---|
default_scale | String | "week" | Initial time scale: day, week, month, quarter |
row_height | Number | 36 | Height of each task row in pixels |
bar_height | Number | 24 | Height of task bars in pixels |
reschedule_method | String | — | Model method that owns a drag, so dependents move with it (see Rescheduling a dependency chain) |
reschedule_modes | List | — | The choices a planner may pick from for what happens downstream (see Letting the planner choose) |
- default_scale: month row_height: 40 bar_height: 28 content: [...]Display Modes
Section titled “Display Modes”Fields in Gantt views use display_mode to define their purpose:
| Mode | Purpose | Field Type | Required |
|---|---|---|---|
title | Task name displayed on bar | Char | Yes |
start | Start date for bar positioning | Date/Datetime | Yes |
end | End date for bar width | Date/Datetime | No (defaults to start + 1 day) |
progress | Progress percentage (0-100) for fill | Float/Integer | No |
resource | Row grouping (resource/project) | ManyToOne | No |
dependency | Task dependencies for arrows | ManyToMany | No |
color | Bar color override | Char (hex) or Selection | No |
Full-Featured Example
Section titled “Full-Featured Example”- data_type: UiView name: project_task_gantt_view identifier: project_task_gantt_view type: Gantt model: ProjectTask arch: - default_scale: week content: - type: field name: name display_mode: title - type: field name: planned_start display_mode: start - type: field name: planned_end display_mode: end - type: field name: progress display_mode: progress - type: field name: project display_mode: resource - type: field name: depends_on display_mode: dependency - type: field name: color display_mode: colorResource Scheduling
Section titled “Resource Scheduling”For resource allocation views (e.g., employee scheduling, room bookings):
- data_type: UiView name: booking_gantt_view identifier: booking_gantt_view type: Gantt model: ResourceBooking arch: - default_scale: day content: - type: field name: title display_mode: title - type: field name: start_datetime display_mode: start - type: field name: end_datetime display_mode: end - type: field name: resource display_mode: resource - type: field name: booking_type display_mode: colorProgress Tracking
Section titled “Progress Tracking”Show task completion with the progress display mode:
- type: field name: progress display_mode: progressThe progress field should contain a value from 0-100. The task bar will show a darker fill representing the completed portion.
Task Dependencies
Section titled “Task Dependencies”To show dependency arrows between tasks:
- Create a ManyToMany field on your model pointing to itself:
class ProjectTask(Model): depends_on = ManyToMany( "ProjectTask", through="FkTaskDependency", related_name="dependents", description="Dependencies" )
class FkTaskDependency(Model): task = ManyToOne("ProjectTask", related_name="dependency_links") depends_on = ManyToOne("ProjectTask", related_name="dependent_links")- Add the field with
display_mode: "dependency":
- type: field name: depends_on display_mode: dependencyDependencies render as arrows from the end of the predecessor task to the start of the dependent task (finish-to-start).
Rescheduling a dependency chain
Section titled “Rescheduling a dependency chain”Drawing an arrow does not enforce it. By default a drag writes only the record that was dragged, so a user can move a task past its dependent and leave the arrow pointing backwards — nothing raises, and the schedule simply looks right while being wrong.
Declare reschedule_method to hand the whole move to a model method instead:
- default_scale: week reschedule_method: gantt_reschedule content: [...]async def gantt_reschedule(self, scheduled_start=None, scheduled_end=None): """Move this record and whatever the dependency graph requires.""" ... return {"type": "notify", "color": "green", "message": t("Rescheduled.")}The method receives the dragged record and its new dates, and is responsible for the entire move. It runs on the server on purpose: the chart can only see the tasks it fetched, so a dependent outside the visible window — or filtered out of the board — would silently not move if the cascade were computed in the browser. Doing it in one call also makes the move atomic, which matters because a cascade that half-applies leaves a schedule nobody can tell is broken.
Return an action result and the board shows it; a cascade’s normal
outcome is partial (“moved 4 operations, 1 already under way stayed put”), which a bare
success cannot express. Raise UserError only for a move that is genuinely impossible.
Prefer constraining a drop to refusing it. A drag that would break a constraint is usually better answered by moving the record as far as the constraint allows and saying so — the bar lands against whatever stopped it, which shows the rule instead of describing it. Refusing reads fine in isolation and becomes unusable wherever records are packed back-to-back, because then every drag in that direction is a violation and the user can never move anything at all.
Without the key the previous behaviour is unchanged — the drag writes just the one record.
Letting the planner choose
Section titled “Letting the planner choose”There is no single right answer to what should happen downstream of a bar that moved.
Pushing a whole job out, absorbing a delay into slack, and moving one record in order to
fix the rest by hand are all real intentions, so a board can offer the choice. Declare
reschedule_modes and the header grows a picker; the chosen value is passed to your
method as mode:
- default_scale: week reschedule_method: gantt_reschedule reschedule_modes: - Shift the Chain - Re-plan What Follows - Move Only This content: [...]async def gantt_reschedule(self, scheduled_start=None, scheduled_end=None, mode=None): mode = resolve_reschedule_mode(mode, self.RESCHEDULE_MODES) ...The three that ship have settled meanings, and a board that implements them should keep to those meanings — a planner who learns the picker on one board should not be surprised by it on another:
| Mode | What a drag does |
|---|---|
Shift the Chain | Every dependent moves by the same signed delta, so gaps the planner deliberately left survive. The safe default for pushing a job out whole. |
Re-plan What Follows | Everything downstream is re-derived from the links into it and re-placed against real capacity and working calendars. A delay is absorbed into whatever slack exists instead of being propagated. |
Move Only This | The dates are written exactly as dropped and nothing else moves — no clamping either. Only offer it if you also report what it broke (below). |
Declare only what your method implements. The picker is built from this list, so a mode named here but not handled offers the planner a choice that raises, and one handled but not named can never be chosen. Nothing at runtime compares the two — pin them with a test that reads the view’s declared list and compares it to the method’s own.
The first entry is the default, used when a planner has not chosen (and when an older client sends no mode at all). A choice is remembered per user, per database and per model.
These strings stay in English on the wire. They are dispatch values, not copy: the arch is translated per user before it reaches the browser, so a translatable mode would come back as a word the model has never heard of. The board renders each through the translator at display time instead, which is where a label belongs.
Reporting what a move broke
Section titled “Reporting what a move broke”A mode that leaves links violated is only defensible if the damage is visible. Return the
broken links as violations alongside the usual notify keys and the board draws those
arrows in alarm until the next reschedule:
return { "type": "notify", "color": "orange", "message": t_plural( "{task} moved. {names} now conflicts with it.", "{task} moved. {names} now conflict with it.", count=len(broken), task=self.name, names=summarize_list(names), ), "violations": [{"from": predecessor_id, "to": successor_id}, ...],}The model reports this rather than the chart working it out, because whether a bar starting the moment another ends is a conflict depends on what the record means: a board planning in datetimes says no, one planning in inclusive whole days says yes. Only the model knows which, and two answers to that question would drift.
Grouping
Section titled “Grouping”Use display_mode: "resource" to organize tasks by rows:
- type: field name: assigned_user display_mode: resource- Tasks with the same group value appear together
- Group headers are collapsible
- Ungrouped tasks appear at the bottom
Custom Colors
Section titled “Custom Colors”Override task bar colors with:
- type: field name: color display_mode: colorThe field can contain:
- Hex color codes:
"#FF5733" - Selection values that map to colors
If no color is specified, bars use automatically generated colors based on task ID.
Window Action Configuration
Section titled “Window Action Configuration”Include Gantt in your action’s available modes:
- data_type: WindowAction identifier: project_tasks_action name: Tasks model: ProjectTask modes: List, Kanban, Gantt, Form default_view: project_task_list_view views: - - R - - project_task_list_view - project_task_kanban_view - project_task_gantt_view - project_task_form_viewUser Interaction
Section titled “User Interaction”Time Scale
Section titled “Time Scale”Users can switch between scales using the toolbar buttons:
- Day: Hour-by-hour view
- Week: Day-by-day view
- Month: Week-by-week view
- Quarter: Month-by-month view
Navigation
Section titled “Navigation”- Arrow buttons: Move forward/backward by one time unit
- Today button: Jump to current date
Task Operations
Section titled “Task Operations”- Click: Open task form
- Drag: Move task to new dates (updates start and end)
- Resize left edge: Change start date
- Resize right edge: Change end date
Model Requirements
Section titled “Model Requirements”For a model to work well with Gantt views:
class ProjectTask(Model): _verbose_name = "Task"
# Required fields name = Char(max_length=255, required=True) planned_start = Date(description="Start Date") planned_end = Date(description="End Date")
# Optional enhancements progress = Float(default=0, description="Progress %") project = ManyToOne("Project", related_name="tasks") assigned_user = ManyToOne("User", related_name="tasks") color = Char(max_length=7, description="Color") depends_on = ManyToMany( "ProjectTask", through="FkTaskDependency", related_name="dependents" )Next Steps
Section titled “Next Steps”- List Views - Table displays
- Kanban Views - Board displays
- Calendar Views - Date-based displays