Skip to content

views

main.views ¤

Views for the main app.

Classes¤

CapacitiesListView ¤

Bases: LoginRequiredMixin, PermissionRequiredMixin, SingleTableMixin, FilterView

View to display the list of capacities.

CapacityPlanningView ¤

Bases: LoginRequiredMixin, TemplateView

View that renders the Capacity Planning page.

Functions¤
get_context_data(**kwargs) ¤

Add HTML components and Bokeh version to the context.

Source code in main/views.py
192
193
194
195
196
197
198
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:  # type: ignore
    """Add HTML components and Bokeh version to the context."""
    context = super().get_context_data(**kwargs)
    layout = plots.create_capacity_planning_layout()
    context.update(plots.html_components_from_plot(layout))
    context["bokeh_version"] = bokeh.__version__
    return context

CostRecoveryView ¤

Bases: LoginRequiredMixin, FormView

View that renders the Cost Recovery page.

Functions¤
form_valid(form) ¤

Generate csv using the dates provided in the form.

Source code in main/views.py
207
208
209
210
211
212
def form_valid(self, form: Form) -> HttpResponse:
    """Generate csv using the dates provided in the form."""
    month = form.cleaned_data["month"]
    year = form.cleaned_data["year"]
    response = report.create_charges_report_for_download(month, year)
    return response
get_context_data(**kwargs) ¤

Add HTML components and Bokeh version to the context.

Source code in main/views.py
214
215
216
217
218
219
220
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:  # type: ignore
    """Add HTML components and Bokeh version to the context."""
    context = super().get_context_data(**kwargs)
    layout = plots.create_cost_recovery_layout()
    context.update(plots.html_components_from_plot(layout))
    context["bokeh_version"] = bokeh.__version__
    return context

CustomBaseDetailView ¤

Bases: LoginRequiredMixin, UpdateView

Detail view based on a read-only form view.

While there is a generic Detail View, it is not rendered nicely easily as the bootstrap theme needs to be applied on a field by field basis. So we use a form view instead, which can easily be styled, and make the form read only.

Functions¤
get_form(form_class=None) ¤

Customize form to make it read-only.

Parameters:

Name Type Description Default
form_class Any | None

The form class to use, if any.

None
Return

A form associated to the model.

Source code in main/views.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def get_form(self, form_class: Any | None = None) -> ModelForm:  # type: ignore
    """Customize form to make it read-only.

    Args:
        form_class: The form class to use, if any.

    Return:
        A form associated to the model.
    """
    form = super().get_form(form_class)

    for field in form.fields.keys():
        form.fields[field].widget.attrs["disabled"] = True
        form.fields[field].widget.attrs["readonly"] = True

    return form

FundingCreateView ¤

Bases: PermissionRequiredMixin, CreateView

View to create a new funding.

FundingDetailView ¤

Bases: PermissionRequiredMixin, CustomBaseDetailView

View to view details of project funding.

Functions¤
get_context_data(**kwargs) ¤

Add funding name to the context, so it is easy to retrieve.

Source code in main/views.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:  # type: ignore
    """Add funding name to the context, so it is easy to retrieve."""
    context = super().get_context_data(**kwargs)
    funding = self.get_object()
    context["funding_name"] = str(funding)

    # Monthly charges table for this funding
    charges_qs = models.MonthlyCharge.objects.filter(funding=funding).order_by(
        "-date"
    )
    charges_table = tables.MonthlyChargeTable(charges_qs)
    RequestConfig(self.request).configure(charges_table)
    context["monthly_charges_table"] = charges_table

    return context

FundingListView ¤

Bases: LoginRequiredMixin, PermissionRequiredMixin, SingleTableMixin, ListView

View to display the funding list for all projects.

FundingUpdateView ¤

Bases: PermissionRequiredMixin, UpdateView

Update view based on a form from the Funding model.

Functions¤
get_success_url() ¤

Django magic function to obtain a dynamic success URL.

Source code in main/views.py
362
363
364
def get_success_url(self):  # type: ignore [no-untyped-def]
    """Django magic function to obtain a dynamic success URL."""
    return reverse_lazy("main:funding_detail", kwargs={"pk": self.object.pk})

ProjectCreateView ¤

Bases: PermissionRequiredMixin, CreateView

View to create a new project.

ProjectDetailView ¤

Bases: PermissionRequiredMixin, CustomBaseDetailView

View to view details of a project.

Functions¤
get_context_data(**kwargs) ¤

Add project name and funding table to the context.

A custom query is used with the funding table, so only the funding for the current project is displayed.

Source code in main/views.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:  # type: ignore
    """Add project name and funding table to the context.

    A custom query is used with the funding table, so only the funding for the
    current project is displayed.
    """
    context = super().get_context_data(**kwargs)
    context["project_name"] = self.get_object().name
    # get funding info for the current project
    funding_source = self.get_object().funding_source.all()
    funding_table = tables.FundingTable(funding_source)
    project_phase = models.ProjectPhase.objects.filter(
        project__name=self.get_object().name
    )
    phase_table = tables.ProjectPhaseTable(project_phase)
    # enables the table to be sorted by column headings
    RequestConfig(self.request).configure(funding_table)
    RequestConfig(self.request).configure(phase_table)

    context["funding_table"] = funding_table
    context["phase_table"] = phase_table
    return context

ProjectPhaseCreateView ¤

Bases: PermissionRequiredMixin, CreateView

View to create a new project phase.

ProjectPhaseDeleteView ¤

Bases: PermissionRequiredMixin, DeleteView

Delete view based on a form from the Project Phase model.

ProjectPhaseDetailView ¤

Bases: PermissionRequiredMixin, CustomBaseDetailView

View to view details of a project.

Functions¤
get_context_data(**kwargs) ¤

Add project name and funding table to the context.

A custom query is used with the funding table, so only the funding for the current project is displayed.

Source code in main/views.py
341
342
343
344
345
346
347
348
349
350
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:  # type: ignore
    """Add project name and funding table to the context.

    A custom query is used with the funding table, so only the funding for the
    current project is displayed.
    """
    context = super().get_context_data(**kwargs)
    context["project_name"] = self.get_object().project.name

    return context

ProjectPhaseUpdateView ¤

Bases: PermissionRequiredMixin, UpdateView

Update view based on a form from the Project Phase model.

Functions¤
get_success_url() ¤

Django magic function to obtain a dynamic success URL.

Source code in main/views.py
386
387
388
389
390
391
def get_success_url(self):  # type: ignore [no-untyped-def]
    """Django magic function to obtain a dynamic success URL."""
    return reverse_lazy(
        "main:project_phase_detail",
        kwargs={"project_pk": self.object.project.pk, "pk": self.object.pk},
    )

ProjectUpdateView ¤

Bases: PermissionRequiredMixin, UpdateView

Update view based on a form from the Project model.

Functions¤
get_success_url() ¤

Django magic function to obtain a dynamic success URL.

Source code in main/views.py
256
257
258
def get_success_url(self):  # type: ignore [no-untyped-def]
    """Django magic function to obtain a dynamic success URL."""
    return reverse_lazy("main:project_detail", kwargs={"pk": self.object.pk})

ProjectsListView ¤

Bases: LoginRequiredMixin, FilterView

View to display the list of projects split in five pre-filtered tables.

Functions¤
get_context_data(**kwargs) ¤

Add multiple pre-filtered tables to the context.

Source code in main/views.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:  # type: ignore
    """Add multiple pre-filtered tables to the context."""
    context = super().get_context_data(**kwargs)

    base_qs = self.get_queryset()

    buckets = [
        ("Active", {"status": "Active"}, "active-"),
        ("Confirmed", {"status": "Confirmed"}, "confirmed-"),
        ("Tentative", {"status": "Tentative"}, "tentative-"),
        ("Finished", {"status": "Finished"}, "finished-"),
        ("Not done", {"status": "Not done"}, "not-done-"),
    ]

    created_tables: list[tuple[str, tables.ProjectTable]] = []
    for title, filt, prefix in buckets:
        qs = base_qs.filter(**filt)
        tbl = tables.ProjectTable(qs, prefix=prefix)
        RequestConfig(self.request).configure(tbl)
        created_tables.append((title, tbl))

    context["tables"] = created_tables
    return context

RegistrationView ¤

Bases: CreateView

View to register new users.

TODO: This is a placeholder for development. When SSO is implemented, this won't be needed since available users will be retrieved automatically.

Functions¤

create_default_project_phase(request) ¤

Create a default project phase.

This view is used to create a default project phase for a project, given the total effort in days and the start and end dates of the project.

If successful, the project detail page will be reloaded to show the new phase. If the project does not have a total effort defined, no phase will be created and the user will be redirected to the project detail page with no changes.

Parameters:

Name Type Description Default
request HttpRequest

The HTTP request object.

required

Returns:

Type Description
HttpResponse

An HTTP response object that redirects to the project detail page.

Source code in main/views.py
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
@login_required
@permission_required("main.create_project_phase", raise_exception=True)
def create_default_project_phase(request: HttpRequest) -> HttpResponse:
    """Create a default project phase.

    This view is used to create a default project phase for a project, given the total
    effort in days and the start and end dates of the project.

    If successful, the project detail page will be reloaded to show the new phase. If
    the project does not have a total effort defined, no phase will be created and the
    user will be redirected to the project detail page with no changes.

    Args:
        request: The HTTP request object.

    Returns:
        An HTTP response object that redirects to the project detail page.
    """
    if request.method == "POST":
        project_name = request.POST.get("project_name")
        project = models.Project.objects.get(name=project_name)

        assert project.start_date is not None
        assert project.end_date is not None
        if days := project.total_effort:
            try:
                models.ProjectPhase.from_days(
                    days=days,
                    project=project,
                    start_date=project.start_date,
                    end_date=project.end_date,
                )
            except Exception as e:
                messages.add_message(
                    request,
                    messages.WARNING,
                    e.messages[0]
                    if hasattr(e, "messages") and len(e.messages) > 0
                    else str(e),
                )
        else:
            messages.add_message(
                request,
                messages.WARNING,
                "No funding defined for this project. Please add a funding source"
                "before creating a project phase.",
            )

        return HttpResponseRedirect(
            reverse_lazy("main:project_detail", kwargs={"pk": project.pk})
        )

    else:
        # If GET, we just go back to the detail page without doing anything or to the
        # projects page if the referer is not defined.
        return HttpResponseRedirect(
            request.META.get("HTTP_REFERER", reverse_lazy("main:projects"))
        )